123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- import {Fragment, type ReactElement} from 'react';
- import styled from '@emotion/styled';
- import moment from 'moment-timezone';
- import Duration from 'sentry/components/duration';
- import GlobalSelectionLink from 'sentry/components/globalSelectionLink';
- import Link from 'sentry/components/links/link';
- import {StatusIndicator} from 'sentry/components/statusIndicator';
- import {t} from 'sentry/locale';
- import {space} from 'sentry/styles/space';
- import {ActivationConditionType} from 'sentry/types/alerts';
- import type {Organization} from 'sentry/types/organization';
- import getDuration from 'sentry/utils/duration/getDuration';
- import getDynamicText from 'sentry/utils/getDynamicText';
- import {capitalize} from 'sentry/utils/string/capitalize';
- import {COMPARISON_DELTA_OPTIONS} from 'sentry/views/alerts/rules/metric/constants';
- import {StyledDateTime} from 'sentry/views/alerts/rules/metric/details/styles';
- import {AlertRuleThresholdType} from 'sentry/views/alerts/rules/metric/types';
- import type {ActivityType, Incident} from 'sentry/views/alerts/types';
- import {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';
- type MetricAlertActivityProps = {
- incident: Incident;
- organization: Organization;
- };
- function MetricAlertActivity({organization, incident}: MetricAlertActivityProps) {
- // NOTE: while _possible_, we should never expect an incident to _not_ have a status_change activity
- const activities: ActivityType[] = (incident.activities ?? []).filter(
- activity => activity.type === IncidentActivityType.STATUS_CHANGE
- );
- const statusValues = [String(IncidentStatus.CRITICAL), String(IncidentStatus.WARNING)];
- // TODO: kinda cheating with the forced `!`. Is there a better way to type this?
- const latestActivity: ActivityType = activities.find(activity =>
- statusValues.includes(String(activity.value))
- )!;
- const isCritical = Number(latestActivity.value) === IncidentStatus.CRITICAL;
- // Find the _final_ most recent activity _after_ our triggered activity
- // This exists for the `CLOSED` state (or any state NOT WARNING/CRITICAL)
- const finalActivity = activities.find(
- activity => activity.previousValue === latestActivity.value
- );
- const activityDuration = (
- finalActivity ? moment(finalActivity.dateCreated) : moment()
- ).diff(moment(latestActivity.dateCreated), 'milliseconds');
- const triggerLabel = isCritical ? 'critical' : 'warning';
- const curentTrigger = incident.alertRule.triggers.find(
- trigger => trigger.label === triggerLabel
- );
- const timeWindow = getDuration(incident.alertRule.timeWindow * 60);
- const alertName = capitalize(
- AlertWizardAlertNames[getAlertTypeFromAggregateDataset(incident.alertRule)]
- );
- const project = incident.alertRule.projects[0];
- const activation = incident.activation;
- let activationBlock: ReactElement | null = null;
- // TODO: Split this string check into a separate component
- if (activation) {
- let condition;
- let activator;
- switch (activation.conditionType) {
- case String(ActivationConditionType.RELEASE_CREATION):
- condition = 'Release';
- activator = (
- <GlobalSelectionLink
- to={{
- pathname: `/organizations/${
- organization.slug
- }/releases/${encodeURIComponent(activation.activator)}/`,
- query: {project: project},
- }}
- style={{textOverflow: 'ellipsis', overflowX: 'inherit'}}
- >
- {activation.activator}
- </GlobalSelectionLink>
- );
- break;
- case String(ActivationConditionType.DEPLOY_CREATION):
- condition = 'Deploy';
- activator = activation.activator;
- break;
- default:
- condition = '--';
- }
- activationBlock = (
- <Fragment>
- from {condition} {activator}
- </Fragment>
- );
- }
- return (
- <Fragment>
- <Cell>
- {latestActivity.value && (
- <StatusIndicator
- status={isCritical ? 'error' : 'warning'}
- tooltipTitle={t('Status: %s', isCritical ? t('Critical') : t('Warning'))}
- />
- )}
- <Link
- to={{
- pathname: alertDetailsLink(organization, incident),
- query: {alert: incident.identifier},
- }}
- >
- #{incident.identifier}
- </Link>
- </Cell>
- <Cell>
- {/* If an alert rule is a % comparison based detection type */}
- {incident.alertRule.detectionType !== 'dynamic' &&
- incident.alertRule.comparisonDelta && (
- <Fragment>
- {alertName} {curentTrigger?.alertThreshold}%
- {t(
- ' %s in %s compared to the ',
- incident.alertRule.thresholdType === AlertRuleThresholdType.ABOVE
- ? t('higher')
- : t('lower'),
- timeWindow
- )}
- {COMPARISON_DELTA_OPTIONS.find(
- ({value}) => value === incident.alertRule.comparisonDelta
- )?.label ?? COMPARISON_DELTA_OPTIONS[0].label}
- </Fragment>
- )}
- {/* If an alert rule is a static detection type */}
- {incident.alertRule.detectionType !== 'dynamic' &&
- !incident.alertRule.comparisonDelta && (
- <Fragment>
- {alertName}{' '}
- {incident.alertRule.thresholdType === AlertRuleThresholdType.ABOVE
- ? t('above')
- : t('below')}{' '}
- {curentTrigger?.alertThreshold || '_'} {t('within')} {timeWindow}
- {activationBlock}
- </Fragment>
- )}
- {/* If an alert rule is a dynamic detection type */}
- {incident.alertRule.detectionType === 'dynamic' && (
- <Fragment>
- {t('Detected an anomaly in the query for ')}
- {alertName}
- </Fragment>
- )}
- </Cell>
- <Cell>
- {activityDuration &&
- getDynamicText({
- value: <Duration abbreviation seconds={activityDuration / 1000} />,
- fixed: '30s',
- })}
- </Cell>
- <Cell>
- <StyledDateTime
- date={getDynamicText({
- value: incident.dateCreated,
- fixed: 'Mar 4, 2022 10:44:13 AM UTC',
- })}
- year
- seconds
- timeZone
- />
- </Cell>
- </Fragment>
- );
- }
- export default MetricAlertActivity;
- const Cell = styled('div')`
- display: flex;
- align-items: center;
- white-space: nowrap;
- font-size: ${p => p.theme.fontSizeMedium};
- padding: ${space(1)};
- overflow-x: hidden;
- `;
|