123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- import {css} from '@emotion/react';
- import styled from '@emotion/styled';
- import moment from 'moment-timezone';
- import CollapsePanel from 'sentry/components/collapsePanel';
- import DateTime from 'sentry/components/dateTime';
- import Duration from 'sentry/components/duration';
- import ErrorBoundary from 'sentry/components/errorBoundary';
- import Link from 'sentry/components/links/link';
- import PanelTable from 'sentry/components/panels/panelTable';
- import StatusIndicator from 'sentry/components/statusIndicator';
- import {t, tct, tn} from 'sentry/locale';
- import space from 'sentry/styles/space';
- import {Organization} from 'sentry/types';
- import getDynamicText from 'sentry/utils/getDynamicText';
- import {AlertRuleThresholdType} from 'sentry/views/alerts/rules/metric/types';
- import {Incident, IncidentActivityType, IncidentStatus} from 'sentry/views/alerts/types';
- import {alertDetailsLink} from 'sentry/views/alerts/utils';
- import {AlertWizardAlertNames} from 'sentry/views/alerts/wizard/options';
- import {getAlertTypeFromAggregateDataset} from 'sentry/views/alerts/wizard/utils';
- const COLLAPSE_COUNT = 3;
- function getTriggerName(value: string | null) {
- if (value === `${IncidentStatus.WARNING}`) {
- return t('Warning');
- }
- if (value === `${IncidentStatus.CRITICAL}`) {
- return t('Critical');
- }
- // Otherwise, activity type is not status change
- return '';
- }
- type MetricAlertActivityProps = {
- incident: Incident;
- organization: Organization;
- };
- function MetricAlertActivity({organization, incident}: MetricAlertActivityProps) {
- const activities = (incident.activities ?? []).filter(
- activity => activity.type === IncidentActivityType.STATUS_CHANGE
- );
- const criticalActivity = activities.filter(
- activity => activity.value === `${IncidentStatus.CRITICAL}`
- );
- const warningActivity = activities.filter(
- activity => activity.value === `${IncidentStatus.WARNING}`
- );
- const triggeredActivity = criticalActivity.length
- ? criticalActivity[0]
- : warningActivity[0];
- const currentTrigger = getTriggerName(triggeredActivity.value);
- const nextActivity = activities.find(
- ({previousValue}) => previousValue === triggeredActivity.value
- );
- const activityDuration = (
- nextActivity ? moment(nextActivity.dateCreated) : moment()
- ).diff(moment(triggeredActivity.dateCreated), 'milliseconds');
- const threshold =
- activityDuration !== null &&
- tct('[duration]', {
- duration: <Duration abbreviation seconds={activityDuration / 1000} />,
- });
- const warningThreshold = incident.alertRule.triggers
- .filter(trigger => trigger.label === 'warning')
- .map(trig => trig.alertThreshold);
- const criticalThreshold = incident.alertRule.triggers
- .filter(trigger => trigger.label === 'critical')
- .map(trig => trig.alertThreshold);
- return (
- <ErrorBoundary>
- <Title data-test-id="alert-title">
- <StatusIndicator
- status={currentTrigger.toLocaleLowerCase()}
- tooltipTitle={tct('Status: [level]', {level: currentTrigger})}
- />
- <Link
- to={{
- pathname: alertDetailsLink(organization, incident),
- query: {alert: incident.identifier},
- }}
- >
- {tct('#[id]', {id: incident.identifier})}
- </Link>
- </Title>
- <Cell>
- {tct('[title] [selector] [threshold]', {
- title:
- AlertWizardAlertNames[getAlertTypeFromAggregateDataset(incident.alertRule)],
- selector:
- incident.alertRule.thresholdType === AlertRuleThresholdType.ABOVE
- ? 'above'
- : 'below',
- threshold: currentTrigger === 'Warning' ? warningThreshold : criticalThreshold,
- })}
- </Cell>
- <Cell>
- {getDynamicText({
- value: threshold,
- fixed: '30s',
- })}
- </Cell>
- <StyledDateTime
- date={getDynamicText({
- value: incident.dateCreated,
- fixed: 'Mar 4, 2022 10:44:13 AM UTC',
- })}
- year
- seconds
- timeZone
- />
- </ErrorBoundary>
- );
- }
- type Props = {
- organization: Organization;
- incidents?: Incident[];
- };
- function MetricHistory({organization, incidents}: Props) {
- const numOfIncidents = (incidents ?? []).length;
- return (
- <CollapsePanel
- items={numOfIncidents}
- collapseCount={COLLAPSE_COUNT}
- disableBorder={false}
- buttonTitle={tn('Hidden Alert', 'Hidden Alerts', numOfIncidents - COLLAPSE_COUNT)}
- >
- {({isExpanded, showMoreButton}) => (
- <div>
- <StyledPanelTable
- headers={[t('Alert'), t('Reason'), t('Duration'), t('Date Triggered')]}
- isEmpty={!numOfIncidents}
- emptyMessage={t('No alerts triggered during this time.')}
- expanded={numOfIncidents <= COLLAPSE_COUNT || isExpanded}
- >
- {incidents &&
- incidents.map((incident, idx) => {
- if (idx >= COLLAPSE_COUNT && !isExpanded) {
- return null;
- }
- return (
- <MetricAlertActivity
- key={idx}
- incident={incident}
- organization={organization}
- />
- );
- })}
- </StyledPanelTable>
- {showMoreButton}
- </div>
- )}
- </CollapsePanel>
- );
- }
- export default MetricHistory;
- const StyledPanelTable = styled(PanelTable)<{expanded: boolean; isEmpty: boolean}>`
- grid-template-columns: max-content 1fr repeat(2, max-content);
- & > div {
- padding: ${space(1)} ${space(2)};
- }
- div:last-of-type {
- padding: ${p => p.isEmpty && `48px ${space(1)}`};
- }
- ${p =>
- !p.expanded &&
- css`
- margin-bottom: 0px;
- border-bottom-left-radius: 0px;
- border-bottom-right-radius: 0px;
- border-bottom: none;
- `}
- `;
- const StyledDateTime = styled(DateTime)`
- color: ${p => p.theme.gray300};
- font-size: ${p => p.theme.fontSizeMedium};
- display: flex;
- justify-content: flex-start;
- padding: ${space(1)} ${space(2)} !important;
- `;
- const Title = styled('div')`
- display: flex;
- align-items: center;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- width: 100%;
- font-size: ${p => p.theme.fontSizeMedium};
- padding: ${space(1)};
- `;
- const Cell = styled('div')`
- display: flex;
- align-items: center;
- white-space: nowrap;
- font-size: ${p => p.theme.fontSizeMedium};
- padding: ${space(1)};
- `;
|