pageHeaderActions.tsx 6.5 KB

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