pageHeaderActions.tsx 6.0 KB

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