import * as React from 'react'; import styled from '@emotion/styled'; import memoize from 'lodash/memoize'; import Access from 'sentry/components/acl/access'; import MenuItemActionLink from 'sentry/components/actions/menuItemActionLink'; import ActorAvatar from 'sentry/components/avatar/actorAvatar'; import Button from 'sentry/components/button'; import ButtonBar from 'sentry/components/buttonBar'; import Confirm from 'sentry/components/confirm'; import DateTime from 'sentry/components/dateTime'; import DropdownLink from 'sentry/components/dropdownLink'; import ErrorBoundary from 'sentry/components/errorBoundary'; import IdBadge from 'sentry/components/idBadge'; import Link from 'sentry/components/links/link'; import TimeSince from 'sentry/components/timeSince'; import Tooltip from 'sentry/components/tooltip'; import {IconArrow, IconDelete, IconEllipsis, IconSettings} from 'sentry/icons'; import {t, tct} from 'sentry/locale'; import overflowEllipsis from 'sentry/styles/overflowEllipsis'; import space from 'sentry/styles/space'; import {Actor, Organization, Project} from 'sentry/types'; import getDynamicText from 'sentry/utils/getDynamicText'; import type {Color} from 'sentry/utils/theme'; import {AlertRuleThresholdType} from 'sentry/views/alerts/incidentRules/types'; import AlertBadge from '../alertBadge'; import {CombinedMetricIssueAlerts, IncidentStatus} from '../types'; import {isIssueAlert} from '../utils'; type Props = { rule: CombinedMetricIssueAlerts; projects: Project[]; projectsLoaded: boolean; orgId: string; organization: Organization; onDelete: (projectId: string, rule: CombinedMetricIssueAlerts) => void; // Set of team ids that the user belongs to userTeams: Set; }; /** * Memoized function to find a project from a list of projects */ const getProject = memoize((slug: string, projects: Project[]) => projects.find(project => project.slug === slug) ); function RuleListRow({ rule, projectsLoaded, projects, orgId, onDelete, userTeams, }: Props) { const activeIncident = rule.latestIncident?.status !== undefined && [IncidentStatus.CRITICAL, IncidentStatus.WARNING].includes( rule.latestIncident.status ); function renderLastIncidentDate(): React.ReactNode { if (isIssueAlert(rule)) { return null; } if (!rule.latestIncident) { return '-'; } if (activeIncident) { return (
{t('Triggered ')}
); } return (
{t('Resolved ')}
); } function renderAlertRuleStatus(): React.ReactNode { if (isIssueAlert(rule)) { return null; } const criticalTrigger = rule.triggers.find(({label}) => label === 'critical'); const warningTrigger = rule.triggers.find(({label}) => label === 'warning'); const resolvedTrigger = rule.resolveThreshold; const trigger = activeIncident && rule.latestIncident?.status === IncidentStatus.CRITICAL ? criticalTrigger : warningTrigger ?? criticalTrigger; let iconColor: Color = 'green300'; let iconDirection: 'up' | 'down' | undefined; let thresholdTypeText = activeIncident && rule.thresholdType === AlertRuleThresholdType.ABOVE ? t('Above') : t('Below'); if (activeIncident) { iconColor = trigger?.label === 'critical' ? 'red300' : trigger?.label === 'warning' ? 'yellow300' : 'green300'; iconDirection = rule.thresholdType === AlertRuleThresholdType.ABOVE ? 'up' : 'down'; } else { // Use the Resolved threshold type, which is opposite of Critical iconDirection = rule.thresholdType === AlertRuleThresholdType.ABOVE ? 'down' : 'up'; thresholdTypeText = rule.thresholdType === AlertRuleThresholdType.ABOVE ? t('Below') : t('Above'); } return ( {`${thresholdTypeText} ${ rule.latestIncident || (!rule.latestIncident && !resolvedTrigger) ? trigger?.alertThreshold?.toLocaleString() : resolvedTrigger?.toLocaleString() }`} ); } const slug = rule.projects[0]; const editLink = `/organizations/${orgId}/alerts/${ isIssueAlert(rule) ? 'rules' : 'metric-rules' }/${slug}/${rule.id}/`; const detailsLink = `/organizations/${orgId}/alerts/rules/details/${rule.id}/`; const ownerId = rule.owner?.split(':')[1]; const teamActor = ownerId ? {type: 'team' as Actor['type'], id: ownerId, name: ''} : null; const canEdit = ownerId ? userTeams.has(ownerId) : true; const alertLink = isIssueAlert(rule) ? ( rule.name ) : ( {rule.name} ); const IssueStatusText: Record = { [IncidentStatus.CRITICAL]: t('Critical'), [IncidentStatus.WARNING]: t('Warning'), [IncidentStatus.CLOSED]: t('Resolved'), [IncidentStatus.OPENED]: t('Resolved'), }; return ( {alertLink} {!isIssueAlert(rule) && renderLastIncidentDate()} {renderAlertRuleStatus()} {teamActor ? : '-'} {({hasAccess}) => ( } /> } >
  • {t('Edit')}
  • onDelete(slug, rule)} > {t('Delete')}
    {/* Small screen actions */} onDelete(slug, rule)} >