pageHeaderActions.tsx 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. import {useCallback, useMemo} from 'react';
  2. import styled from '@emotion/styled';
  3. import * as Sentry from '@sentry/react';
  4. import {navigateTo} from 'sentry/actionCreators/navigation';
  5. import Feature from 'sentry/components/acl/feature';
  6. import {Button} from 'sentry/components/button';
  7. import ButtonBar from 'sentry/components/buttonBar';
  8. import {DropdownMenu} from 'sentry/components/dropdownMenu';
  9. import {CreateMetricAlertFeature} from 'sentry/components/metrics/createMetricAlertFeature';
  10. import {getQuerySymbol} from 'sentry/components/metrics/querySymbol';
  11. import {
  12. IconBookmark,
  13. IconDashboard,
  14. IconEllipsis,
  15. IconSettings,
  16. IconSiren,
  17. } from 'sentry/icons';
  18. import {t} from 'sentry/locale';
  19. import {trackAnalytics} from 'sentry/utils/analytics';
  20. import {isCustomMeasurement} from 'sentry/utils/metrics';
  21. import {hasCustomMetricsExtractionRules} from 'sentry/utils/metrics/features';
  22. import {formatMRI} from 'sentry/utils/metrics/mri';
  23. import {MetricExpressionType, type MetricsQueryWidget} from 'sentry/utils/metrics/types';
  24. import {middleEllipsis} from 'sentry/utils/string/middleEllipsis';
  25. import useOrganization from 'sentry/utils/useOrganization';
  26. import useRouter from 'sentry/utils/useRouter';
  27. import {useMetricsContext} from 'sentry/views/metrics/context';
  28. import {getCreateAlert} from 'sentry/views/metrics/metricQueryContextMenu';
  29. import {useCreateDashboard} from 'sentry/views/metrics/useCreateDashboard';
  30. import {useFormulaDependencies} from 'sentry/views/metrics/utils/useFormulaDependencies';
  31. import {openExtractionRuleCreateModal} from 'sentry/views/settings/projectMetrics/metricsExtractionRuleCreateModal';
  32. interface Props {
  33. addCustomMetric: () => void;
  34. showAddMetricButton: boolean;
  35. }
  36. export function PageHeaderActions({showAddMetricButton, addCustomMetric}: Props) {
  37. const router = useRouter();
  38. const organization = useOrganization();
  39. const formulaDependencies = useFormulaDependencies();
  40. const {isDefaultQuery, setDefaultQuery, widgets, showQuerySymbols, isMultiChartMode} =
  41. useMetricsContext();
  42. const createDashboard = useCreateDashboard(
  43. widgets,
  44. formulaDependencies,
  45. isMultiChartMode
  46. );
  47. const handleToggleDefaultQuery = useCallback(() => {
  48. if (isDefaultQuery) {
  49. Sentry.metrics.increment('ddm.remove-default-query');
  50. trackAnalytics('ddm.remove-default-query', {
  51. organization,
  52. });
  53. setDefaultQuery(null);
  54. } else {
  55. Sentry.metrics.increment('ddm.set-default-query');
  56. trackAnalytics('ddm.set-default-query', {
  57. organization,
  58. });
  59. setDefaultQuery(router.location.query);
  60. }
  61. }, [isDefaultQuery, organization, router.location.query, setDefaultQuery]);
  62. const items = useMemo(
  63. () => [
  64. {
  65. leadingItems: [<IconDashboard key="icon" />],
  66. key: 'add-dashboard',
  67. label: (
  68. <Feature
  69. organization={organization}
  70. hookName="feature-disabled:dashboards-edit"
  71. features="dashboards-edit"
  72. >
  73. {({hasFeature}) => (
  74. <AddToDashboardItem disabled={!hasFeature}>
  75. {t('Add to Dashboard')}
  76. </AddToDashboardItem>
  77. )}
  78. </Feature>
  79. ),
  80. onAction: () => {
  81. if (!organization.features.includes('dashboards-edit')) {
  82. return;
  83. }
  84. trackAnalytics('ddm.add-to-dashboard', {
  85. organization,
  86. source: 'global',
  87. });
  88. createDashboard();
  89. },
  90. },
  91. {
  92. leadingItems: [<IconSettings key="icon" />],
  93. key: 'metrics-settings',
  94. label: t('Metrics Settings'),
  95. onAction: () => navigateTo(`/settings/projects/:projectId/metrics/`, router),
  96. },
  97. ],
  98. [createDashboard, organization, router]
  99. );
  100. const alertItems = useMemo(
  101. () =>
  102. widgets
  103. .filter(
  104. (query): query is MetricsQueryWidget =>
  105. query.type === MetricExpressionType.QUERY
  106. )
  107. .map((widget, index) => {
  108. const createAlert = getCreateAlert(organization, {
  109. query: widget.query,
  110. mri: widget.mri,
  111. groupBy: widget.groupBy,
  112. aggregation: widget.aggregation,
  113. });
  114. return {
  115. leadingItems: showQuerySymbols
  116. ? [<span key="symbol">{getQuerySymbol(widget.id)}:</span>]
  117. : [],
  118. key: `add-alert-${index}`,
  119. label: widget.mri
  120. ? `${widget.aggregation}(${middleEllipsis(formatMRI(widget.mri), 60, /\.|-|_/)})`
  121. : t('Select a metric to create an alert'),
  122. tooltip: isCustomMeasurement({mri: widget.mri})
  123. ? t('Custom measurements cannot be used to create alerts')
  124. : undefined,
  125. disabled: !createAlert,
  126. onAction: () => {
  127. trackAnalytics('ddm.create-alert', {
  128. organization,
  129. source: 'global',
  130. });
  131. createAlert?.();
  132. },
  133. };
  134. }),
  135. [organization, showQuerySymbols, widgets]
  136. );
  137. return (
  138. <ButtonBar gap={1}>
  139. {showAddMetricButton &&
  140. (hasCustomMetricsExtractionRules(organization) ? (
  141. <Button
  142. priority="primary"
  143. onClick={() => openExtractionRuleCreateModal({})}
  144. size="sm"
  145. >
  146. {t('Add New Metric')}
  147. </Button>
  148. ) : (
  149. <Button priority="primary" onClick={() => addCustomMetric()} size="sm">
  150. {t('Add Custom Metrics')}
  151. </Button>
  152. ))}
  153. <Button
  154. size="sm"
  155. icon={<IconBookmark isSolid={isDefaultQuery} />}
  156. onClick={handleToggleDefaultQuery}
  157. >
  158. {isDefaultQuery ? t('Remove Default') : t('Save as default')}
  159. </Button>
  160. <CreateMetricAlertFeature>
  161. {({hasFeature}) =>
  162. alertItems.length === 1 ? (
  163. <Button
  164. size="sm"
  165. icon={<IconSiren />}
  166. disabled={!alertItems[0].onAction || !hasFeature}
  167. onClick={alertItems[0].onAction}
  168. >
  169. {t('Create Alert')}
  170. </Button>
  171. ) : (
  172. <DropdownMenu
  173. items={alertItems}
  174. triggerLabel={t('Create Alert')}
  175. isDisabled={!hasFeature}
  176. triggerProps={{
  177. size: 'sm',
  178. showChevron: false,
  179. icon: <IconSiren direction="down" size="sm" />,
  180. }}
  181. position="bottom-end"
  182. />
  183. )
  184. }
  185. </CreateMetricAlertFeature>
  186. <DropdownMenu
  187. items={items}
  188. triggerProps={{
  189. 'aria-label': t('Page actions'),
  190. size: 'sm',
  191. showChevron: false,
  192. icon: <IconEllipsis direction="down" size="xs" />,
  193. }}
  194. position="bottom-end"
  195. />
  196. </ButtonBar>
  197. );
  198. }
  199. const AddToDashboardItem = styled('div')<{disabled: boolean}>`
  200. color: ${p => (p.disabled ? p.theme.disabled : p.theme.textColor)};
  201. `;