pageHeaderActions.tsx 7.0 KB

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