pageHeaderActions.tsx 6.6 KB

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