123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211 |
- import {Fragment} from 'react';
- import {css} from '@emotion/react';
- import styled from '@emotion/styled';
- import capitalize from 'lodash/capitalize';
- import moment from 'moment-timezone';
- import CollapsePanel from 'sentry/components/collapsePanel';
- import DateTime from 'sentry/components/dateTime';
- import Duration from 'sentry/components/duration';
- import Link from 'sentry/components/links/link';
- import PanelTable from 'sentry/components/panels/panelTable';
- import StatusIndicator from 'sentry/components/statusIndicator';
- import {t, tn} from 'sentry/locale';
- import {space} from 'sentry/styles/space';
- import type {Organization} from 'sentry/types';
- import {getDuration} from 'sentry/utils/formatters';
- import getDynamicText from 'sentry/utils/getDynamicText';
- import useOrganization from 'sentry/utils/useOrganization';
- import {COMPARISON_DELTA_OPTIONS} from 'sentry/views/alerts/rules/metric/constants';
- import {AlertRuleThresholdType} from 'sentry/views/alerts/rules/metric/types';
- import type {ActivityType} from 'sentry/views/alerts/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;
- 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.find(
- activity => activity.value === `${IncidentStatus.CRITICAL}`
- );
- const warningActivity = activities.find(
- activity => activity.value === `${IncidentStatus.WARNING}`
- );
- const triggeredActivity: ActivityType = criticalActivity
- ? criticalActivity!
- : warningActivity!;
- const isCritical = Number(triggeredActivity.value) === IncidentStatus.CRITICAL;
- // Find duration by looking at the difference between the previous and current activity timestamp
- const nextActivity = activities.find(
- ({previousValue}) => previousValue === triggeredActivity.value
- );
- const activityDuration = (
- nextActivity ? moment(nextActivity.dateCreated) : moment()
- ).diff(moment(triggeredActivity.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)]
- );
- return (
- <Fragment>
- <Cell>
- {triggeredActivity.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>
- {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>
- ) : (
- <Fragment>
- {alertName}{' '}
- {incident.alertRule.thresholdType === AlertRuleThresholdType.ABOVE
- ? t('above')
- : t('below')}{' '}
- {curentTrigger?.alertThreshold} {t('in')} {timeWindow}
- </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>
- );
- }
- type Props = {
- incidents?: Incident[];
- };
- function MetricHistory({incidents}: Props) {
- const organization = useOrganization();
- const filteredIncidents = (incidents ?? []).filter(
- incident => incident.activities?.length
- );
- const numOfIncidents = filteredIncidents.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}
- >
- {filteredIncidents.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};
- `;
- const Cell = styled('div')`
- display: flex;
- align-items: center;
- white-space: nowrap;
- font-size: ${p => p.theme.fontSizeMedium};
- padding: ${space(1)};
- `;
|