pageHeaderActions.tsx 6.5 KB

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