pageHeaderActions.tsx 6.4 KB

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