header.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  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 {Button} from 'sentry/components/button';
  7. import ButtonBar from 'sentry/components/buttonBar';
  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 type {MetricRule} from 'sentry/views/alerts/rules/metric/types';
  15. import {getAlertRuleActionCategory} from 'sentry/views/alerts/rules/utils';
  16. import {isIssueAlert} from '../../../utils';
  17. type Props = {
  18. hasMetricRuleDetailsError: boolean;
  19. onSnooze: (nextState: {
  20. snooze: boolean;
  21. snoozeCreatedBy?: string;
  22. snoozeForEveryone?: boolean;
  23. }) => void;
  24. organization: Organization;
  25. project?: Project;
  26. rule?: MetricRule;
  27. };
  28. function DetailsHeader({
  29. hasMetricRuleDetailsError,
  30. rule,
  31. organization,
  32. project,
  33. onSnooze,
  34. }: Props) {
  35. const isRuleReady = !!rule && !hasMetricRuleDetailsError;
  36. const ruleTitle = rule && !hasMetricRuleDetailsError ? rule.name : '';
  37. const settingsLink =
  38. rule &&
  39. `/organizations/${organization.slug}/alerts/${
  40. isIssueAlert(rule) ? 'rules' : 'metric-rules'
  41. }/${project?.slug ?? rule?.projects?.[0]}/${rule.id}/`;
  42. const duplicateLink = {
  43. pathname: `/organizations/${organization.slug}/alerts/new/metric/`,
  44. query: {
  45. project: project?.slug,
  46. duplicateRuleId: rule?.id,
  47. createFromDuplicate: true,
  48. referrer: 'metric_rule_details',
  49. },
  50. };
  51. const isSnoozed = rule?.snooze ?? false;
  52. return (
  53. <Layout.Header>
  54. <Layout.HeaderContent>
  55. <Breadcrumbs
  56. crumbs={[
  57. {label: t('Alerts'), to: `/organizations/${organization.slug}/alerts/rules/`},
  58. {label: ruleTitle},
  59. ]}
  60. />
  61. <RuleTitle data-test-id="incident-rule-title" loading={!isRuleReady}>
  62. {project && (
  63. <IdBadge
  64. project={project}
  65. avatarSize={28}
  66. hideName
  67. avatarProps={{hasTooltip: true, tooltip: project.slug}}
  68. />
  69. )}
  70. {ruleTitle}
  71. </RuleTitle>
  72. </Layout.HeaderContent>
  73. <Layout.HeaderActions>
  74. <ButtonBar gap={1}>
  75. {rule && project && (
  76. <Access access={['alerts:write']}>
  77. {({hasAccess}) => (
  78. <SnoozeAlert
  79. isSnoozed={isSnoozed}
  80. onSnooze={onSnooze}
  81. ruleId={rule.id}
  82. projectSlug={project.slug}
  83. ruleActionCategory={getAlertRuleActionCategory(rule)}
  84. hasAccess={hasAccess}
  85. type="metric"
  86. />
  87. )}
  88. </Access>
  89. )}
  90. <Button size="sm" icon={<IconCopy />} to={duplicateLink}>
  91. {t('Duplicate')}
  92. </Button>
  93. <Button size="sm" icon={<IconEdit />} to={settingsLink}>
  94. {t('Edit Rule')}
  95. </Button>
  96. </ButtonBar>
  97. </Layout.HeaderActions>
  98. </Layout.Header>
  99. );
  100. }
  101. export default DetailsHeader;
  102. const RuleTitle = styled(Layout.Title, {
  103. shouldForwardProp: p => typeof p === 'string' && isPropValid(p) && p !== 'loading',
  104. })<{loading: boolean}>`
  105. ${p => p.loading && 'opacity: 0'};
  106. `;