header.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import isPropValid from '@emotion/is-prop-valid';
  2. import styled from '@emotion/styled';
  3. import Access from 'sentry/components/acl/access';
  4. import SnoozeAlert from 'sentry/components/alerts/snoozeAlert';
  5. import {Breadcrumbs} from 'sentry/components/breadcrumbs';
  6. import ButtonBar from 'sentry/components/buttonBar';
  7. import {LinkButton} from 'sentry/components/core/button';
  8. import IdBadge from 'sentry/components/idBadge';
  9. import * as Layout from 'sentry/components/layouts/thirds';
  10. import {IconCopy, IconEdit} from 'sentry/icons';
  11. import {t} from 'sentry/locale';
  12. import type {Organization} from 'sentry/types/organization';
  13. import type {Project} from 'sentry/types/project';
  14. import {makeAlertsPathname} from 'sentry/views/alerts/pathnames';
  15. import type {MetricRule} from 'sentry/views/alerts/rules/metric/types';
  16. import {getAlertRuleActionCategory} from 'sentry/views/alerts/rules/utils';
  17. import {AlertWizardAlertNames} from 'sentry/views/alerts/wizard/options';
  18. import {getAlertTypeFromAggregateDataset} from 'sentry/views/alerts/wizard/utils';
  19. type Props = {
  20. hasMetricRuleDetailsError: boolean;
  21. onSnooze: (nextState: {
  22. snooze: boolean;
  23. snoozeCreatedBy?: string;
  24. snoozeForEveryone?: boolean;
  25. }) => void;
  26. organization: Organization;
  27. project?: Project;
  28. rule?: MetricRule;
  29. };
  30. function DetailsHeader({
  31. hasMetricRuleDetailsError,
  32. rule,
  33. organization,
  34. project,
  35. onSnooze,
  36. }: Props) {
  37. const isRuleReady = !!rule && !hasMetricRuleDetailsError;
  38. const ruleTitle = rule && !hasMetricRuleDetailsError ? rule.name : '';
  39. const settingsLink = rule
  40. ? makeAlertsPathname({
  41. path: `/metric-rules/${project?.slug ?? rule?.projects?.[0]}/${rule.id}/`,
  42. organization,
  43. })
  44. : '#';
  45. const duplicateLink = {
  46. pathname: makeAlertsPathname({
  47. path: `/new/metric/`,
  48. organization,
  49. }),
  50. query: {
  51. project: project?.slug,
  52. duplicateRuleId: rule?.id,
  53. createFromDuplicate: 'true',
  54. referrer: 'metric_rule_details',
  55. },
  56. };
  57. const isSnoozed = rule?.snooze ?? false;
  58. const ruleType =
  59. rule &&
  60. getAlertTypeFromAggregateDataset({
  61. aggregate: rule.aggregate,
  62. dataset: rule.dataset,
  63. });
  64. return (
  65. <Layout.Header>
  66. <Layout.HeaderContent>
  67. <Breadcrumbs
  68. crumbs={[
  69. {
  70. label: t('Alerts'),
  71. to: makeAlertsPathname({
  72. path: `/rules/`,
  73. organization,
  74. }),
  75. },
  76. {
  77. label: ruleType
  78. ? t('%s Metric Alert', AlertWizardAlertNames[ruleType])
  79. : t('Metric Alert'),
  80. },
  81. ]}
  82. />
  83. <RuleTitle data-test-id="incident-rule-title" loading={!isRuleReady}>
  84. {project && (
  85. <IdBadge
  86. project={project}
  87. avatarSize={28}
  88. hideName
  89. avatarProps={{hasTooltip: true, tooltip: project.slug}}
  90. />
  91. )}
  92. {ruleTitle}
  93. </RuleTitle>
  94. </Layout.HeaderContent>
  95. <Layout.HeaderActions>
  96. <ButtonBar gap={1}>
  97. {rule && project && (
  98. <Access access={['alerts:write']}>
  99. {({hasAccess}) => (
  100. <SnoozeAlert
  101. isSnoozed={isSnoozed}
  102. onSnooze={onSnooze}
  103. ruleId={rule.id}
  104. projectSlug={project.slug}
  105. ruleActionCategory={getAlertRuleActionCategory(rule)}
  106. hasAccess={hasAccess}
  107. type="metric"
  108. />
  109. )}
  110. </Access>
  111. )}
  112. <LinkButton size="sm" icon={<IconCopy />} to={duplicateLink}>
  113. {t('Duplicate')}
  114. </LinkButton>
  115. <LinkButton size="sm" icon={<IconEdit />} to={settingsLink}>
  116. {t('Edit Rule')}
  117. </LinkButton>
  118. </ButtonBar>
  119. </Layout.HeaderActions>
  120. </Layout.Header>
  121. );
  122. }
  123. export default DetailsHeader;
  124. const RuleTitle = styled(Layout.Title, {
  125. shouldForwardProp: p => typeof p === 'string' && isPropValid(p) && p !== 'loading',
  126. })<{loading: boolean}>`
  127. ${p => p.loading && 'opacity: 0'};
  128. `;