pageHeaderActions.tsx 6.2 KB

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