Skip to content

Commit

Permalink
Rares/score quality (#1324)
Browse files Browse the repository at this point in the history
# What this PR does

#118 

## Checklist

- [x] Tests updated
- [ ] Documentation added
- [x] `CHANGELOG.md` updated
  • Loading branch information
teodosii authored Mar 13, 2023
1 parent 1ca47bd commit 623d254
Show file tree
Hide file tree
Showing 28 changed files with 1,135 additions and 186 deletions.
13 changes: 7 additions & 6 deletions src/components/ScheduleCounter/ScheduleCounter.module.css
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
.root {
font-size: 12px;
line-height: 16px;
padding: 3px 4px;
}

.root__type_link {
padding: 2px 4px;
background: rgba(27, 133, 94, 0.15);
border: 1px solid var(--success-text-color);
border: 1px solid var(--tag-border-success);
border-radius: 2px;
}

.root__type_warning {
padding: 2px 4px;
background: rgba(245, 183, 61, 0.18);
border: 1px solid var(--warning-text-color);
border: 1px solid var(--tag-border-warning);
border-radius: 2px;
}

.text__type_link,
.icon__type_link {
color: var(--success-text-color);
color: var(--tag-text-success);
}

.text__type_warning,
.icon__type_warning {
color: var(--warning-text-color);
color: var(--tag-text-warning);
}

.tooltip {
Expand Down
16 changes: 6 additions & 10 deletions src/components/ScheduleCounter/ScheduleCounter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,19 @@ interface ScheduleCounterProps {
count: number;
tooltipTitle: string;
tooltipContent: React.ReactNode;
onHover: () => void;
addPadding?: boolean;
onHover?: () => void;
}

const typeToIcon = {
link: 'link',
warning: 'exclamation-triangle',
};

const typeToColor = {
link: 'success',
warning: 'warning',
};

const cx = cn.bind(styles);

const ScheduleCounter: FC<ScheduleCounterProps> = (props) => {
const { type, count, tooltipTitle, tooltipContent, onHover } = props;
const { type, count, tooltipTitle, tooltipContent, onHover, addPadding } = props;

return (
<Tooltip
Expand All @@ -37,16 +33,16 @@ const ScheduleCounter: FC<ScheduleCounterProps> = (props) => {
content={
<div className={cx('tooltip', { [`tooltip__type_${type}`]: true })}>
<VerticalGroup>
<Text type={typeToColor[type]}>{tooltipTitle}</Text>
<Text type="secondary">{tooltipTitle}</Text>
<Text type="secondary">{tooltipContent}</Text>
</VerticalGroup>
</div>
}
>
<div className={cx('root', { [`root__type_${type}`]: true })} onMouseEnter={onHover}>
<div className={cx('root', { [`root__type_${type}`]: true }, { padding: addPadding })} onMouseEnter={onHover}>
<HorizontalGroup spacing="xs">
<Icon className={cx('icon', { [`icon__type_${type}`]: true })} name={typeToIcon[type] as IconName} />
<Text type={typeToColor[type] as TextType}>{count}</Text>
<Text className={cx('text', { [`text__type_${type}`]: true })}>{count}</Text>
</HorizontalGroup>
</div>
</Tooltip>
Expand Down
46 changes: 0 additions & 46 deletions src/components/ScheduleQuality/ScheduleQuality.module.css

This file was deleted.

39 changes: 39 additions & 0 deletions src/components/ScheduleQuality/ScheduleQuality.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
$score-primary: rgba(27, 133, 94, 0.15);
$score-warning: rgba(245, 183, 61, 0.18);
$score-danger: rgba(209, 14, 92, 0.15);

.root {
display: flex;
flex-direction: row;
align-items: center;
gap: 5px;
}

.quality {
line-height: 16px;
}

.link {
text-decoration: none !important;
}

.tag {
font-size: 12px;
padding: 4px 10px 3px 10px;

&--danger {
background-color: $score-danger;
color: var(--tag-text-danger);
border: 1px solid var(--tag-border-danger);
}
&--warning {
background-color: $score-warning;
color: var(--tag-text-warning);
border: 1px solid var(--tag-border-warning);
}
&--primary {
background-color: $score-primary;
color: var(--tag-text-success);
border: 1px solid var(--tag-border-success);
}
}
185 changes: 110 additions & 75 deletions src/components/ScheduleQuality/ScheduleQuality.tsx
Original file line number Diff line number Diff line change
@@ -1,96 +1,131 @@
import React, { FC, useCallback, useState } from 'react';
import React, { FC, useEffect, useState } from 'react';

import { HorizontalGroup, VerticalGroup, Icon, IconButton, Tooltip } from '@grafana/ui';
import { Tooltip, VerticalGroup } from '@grafana/ui';
import cn from 'classnames/bind';

import PluginLink from 'components/PluginLink/PluginLink';
import ScheduleCounter from 'components/ScheduleCounter/ScheduleCounter';
import { ScheduleQualityDetails } from 'components/ScheduleQualityDetails/ScheduleQualityDetails';
import Tag from 'components/Tag/Tag';
import Text from 'components/Text/Text';
import { Schedule, ScheduleScoreQualityResponse, ScheduleScoreQualityResult } from 'models/schedule/schedule.types';
import { useStore } from 'state/useStore';

import styles from './ScheduleQuality.module.css';
import styles from './ScheduleQuality.module.scss';

const cx = cn.bind(styles);

interface ScheduleQualityProps {
quality: number;
schedule: Schedule;
lastUpdated: number;
}

const cx = cn.bind(styles);
const ScheduleQuality: FC<ScheduleQualityProps> = ({ schedule, lastUpdated }) => {
const { scheduleStore } = useStore();
const [qualityResponse, setQualityResponse] = useState<ScheduleScoreQualityResponse>(undefined);

const ScheduleQuality: FC<ScheduleQualityProps> = (props) => {
const { quality } = props;
useEffect(() => {
if (schedule.id) {
fetchScoreQuality();
}
}, [schedule.id, lastUpdated]);

return (
<Tooltip placement="bottom-end" interactive content={<SheduleQualityDetails quality={quality} />}>
<div className={cx('root')}>
<HorizontalGroup spacing="sm">
<Text type="secondary">Quality:</Text>
<Text type="primary">{Math.floor(quality * 100)}%</Text>
</HorizontalGroup>
</div>
</Tooltip>
);
};
if (!qualityResponse) {
return null;
}

interface ScheduleQualityDetailsProps {
quality: number;
}
const relatedEscalationChains = scheduleStore.relatedEscalationChains[schedule.id];

const SheduleQualityDetails = (props: ScheduleQualityDetailsProps) => {
const { quality } = props;
return (
<>
<div className={cx('root')}>
{relatedEscalationChains?.length > 0 && schedule?.number_of_escalation_chains > 0 && (
<ScheduleCounter
type="link"
addPadding
count={schedule.number_of_escalation_chains}
tooltipTitle="Used in escalations"
tooltipContent={
<VerticalGroup spacing="sm">
{relatedEscalationChains.map((escalationChain) => (
<div key={escalationChain.pk}>
<PluginLink query={{ page: 'escalations', id: escalationChain.pk }} className={cx('link')}>
<Text type="link">{escalationChain.name}</Text>
</PluginLink>
</div>
))}
</VerticalGroup>
}
/>
)}

const [expanded, setExpanded] = useState<boolean>(false);
{schedule.warnings?.length > 0 && (
<ScheduleCounter
type="warning"
addPadding
count={schedule.warnings.length}
tooltipTitle="Warnings"
tooltipContent={
<VerticalGroup spacing="none">
{schedule.warnings.map((warning, index) => (
<Text type="primary" key={index}>
{warning}
</Text>
))}
</VerticalGroup>
}
/>
)}

const type = quality > 0.8 ? 'success' : 'warning';
<Tooltip
placement="bottom-start"
interactive
content={
<ScheduleQualityDetails quality={qualityResponse} getScheduleQualityString={getScheduleQualityString} />
}
>
<div className={cx('u-cursor-default')}>
<Tag className={cx('tag', getTagClass())}>
Quality: <strong>{getScheduleQualityString(qualityResponse.total_score)}</strong>
</Tag>
</div>
</Tooltip>
</div>
</>
);

const qualityPercent = quality * 100;
function getScheduleQualityString(score: number): ScheduleScoreQualityResult {
if (score < 20) {
return ScheduleScoreQualityResult.Bad;
}
if (score < 40) {
return ScheduleScoreQualityResult.Low;
}
if (score < 60) {
return ScheduleScoreQualityResult.Medium;
}
if (score < 80) {
return ScheduleScoreQualityResult.Good;
}
return ScheduleScoreQualityResult.Great;
}

const handleExpandClick = useCallback(() => {
setExpanded((expanded) => !expanded);
}, []);
async function fetchScoreQuality() {
await Promise.all([
scheduleStore.getScoreQuality(schedule.id).then((qualityResponse) => setQualityResponse(qualityResponse)),
scheduleStore.updateRelatedEscalationChains(schedule.id),
]);
}

return (
<div className={cx('details')}>
<VerticalGroup>
<Text type="secondary">Schedule quality</Text>
<div className={cx('progress')}>
<div
style={{ width: `${qualityPercent}%` }}
className={cx('progress-filler', {
[`progress-filler__type_${type}`]: true,
})}
>
<div
className={cx('quality-text', {
[`quality-text__type_${type}`]: true,
})}
>
{qualityPercent}%
</div>{' '}
</div>
</div>
{type === 'success' && (
<Text type="primary">
You are doing a great job! <br />
Schedule is well balanced for all members.
</Text>
)}
{type === 'warning' && <Text type="primary">Your schedule has balance problems.</Text>}
<hr style={{ width: '100%' }} />
<VerticalGroup>
<HorizontalGroup justify="space-between">
<HorizontalGroup spacing="sm">
<Icon name="info-circle" />
<Text type="secondary">Calculation methodology</Text>
</HorizontalGroup>
<IconButton name="angle-down" onClick={handleExpandClick} />
</HorizontalGroup>
{expanded && (
<Text type="secondary">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer elementum purus egestas porta ultricies.
Sed quis maximus sem. Phasellus semper pulvinar sapien ac euismod.
</Text>
)}
</VerticalGroup>
</VerticalGroup>
</div>
);
function getTagClass() {
if (qualityResponse?.total_score < 20) {
return 'tag--danger';
}
if (qualityResponse?.total_score < 60) {
return 'tag--warning';
}
return 'tag--primary';
}
};

export default ScheduleQuality;
Loading

0 comments on commit 623d254

Please sign in to comment.