pageHeaderActions.tsx 6.4 KB

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