contextMenu.tsx 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. import {useMemo} from 'react';
  2. import * as Sentry from '@sentry/react';
  3. import {openAddToDashboardModal, openModal} from 'sentry/actionCreators/modal';
  4. import {navigateTo} from 'sentry/actionCreators/navigation';
  5. import type {MenuItemProps} from 'sentry/components/dropdownMenu';
  6. import {DropdownMenu} from 'sentry/components/dropdownMenu';
  7. import {
  8. IconClose,
  9. IconCopy,
  10. IconDashboard,
  11. IconEllipsis,
  12. IconSettings,
  13. IconSiren,
  14. } from 'sentry/icons';
  15. import {t} from 'sentry/locale';
  16. import type {Organization} from 'sentry/types';
  17. import {trackAnalytics} from 'sentry/utils/analytics';
  18. import {isCustomMeasurement, isCustomMetric} from 'sentry/utils/metrics';
  19. import {
  20. convertToDashboardWidget,
  21. encodeWidgetQuery,
  22. getWidgetAsQueryParams,
  23. getWidgetQuery,
  24. } from 'sentry/utils/metrics/dashboard';
  25. import {hasDDMFeature} from 'sentry/utils/metrics/features';
  26. import type {MetricDisplayType, MetricsQuery} from 'sentry/utils/metrics/types';
  27. import useOrganization from 'sentry/utils/useOrganization';
  28. import useRouter from 'sentry/utils/useRouter';
  29. import {useDDMContext} from 'sentry/views/ddm/context';
  30. import {CreateAlertModal} from 'sentry/views/ddm/createAlertModal';
  31. import {OrganizationContext} from 'sentry/views/organizationContext';
  32. type ContextMenuProps = {
  33. displayType: MetricDisplayType;
  34. metricsQuery: MetricsQuery;
  35. widgetIndex: number;
  36. };
  37. export function MetricQueryContextMenu({
  38. metricsQuery,
  39. displayType,
  40. widgetIndex,
  41. }: ContextMenuProps) {
  42. const organization = useOrganization();
  43. const router = useRouter();
  44. const {removeWidget, duplicateWidget, widgets} = useDDMContext();
  45. const createAlert = useMemo(
  46. () => getCreateAlert(organization, metricsQuery),
  47. [metricsQuery, organization]
  48. );
  49. const createDashboardWidget = useCreateDashboardWidget(
  50. organization,
  51. metricsQuery,
  52. displayType
  53. );
  54. const canDelete = widgets.length > 1;
  55. const items = useMemo<MenuItemProps[]>(
  56. () => [
  57. {
  58. leadingItems: [<IconCopy key="icon" />],
  59. key: 'duplicate',
  60. label: t('Duplicate'),
  61. onAction: () => {
  62. trackAnalytics('ddm.widget.duplicate', {
  63. organization,
  64. });
  65. Sentry.metrics.increment('ddm.widget.duplicate');
  66. duplicateWidget(widgetIndex);
  67. },
  68. },
  69. {
  70. leadingItems: [<IconSiren key="icon" />],
  71. key: 'add-alert',
  72. label: t('Create Alert'),
  73. disabled: !createAlert,
  74. onAction: () => {
  75. trackAnalytics('ddm.create-alert', {
  76. organization,
  77. source: 'widget',
  78. });
  79. Sentry.metrics.increment('ddm.widget.alert');
  80. createAlert?.();
  81. },
  82. },
  83. {
  84. leadingItems: [<IconDashboard key="icon" />],
  85. key: 'add-dashoard',
  86. label: t('Add to Dashboard'),
  87. disabled: !createDashboardWidget,
  88. onAction: () => {
  89. trackAnalytics('ddm.add-to-dashboard', {
  90. organization,
  91. source: 'widget',
  92. });
  93. Sentry.metrics.increment('ddm.widget.dashboard');
  94. createDashboardWidget?.();
  95. },
  96. },
  97. {
  98. leadingItems: [<IconSettings key="icon" />],
  99. key: 'settings',
  100. label: t('Metric Settings'),
  101. disabled: !isCustomMetric({mri: metricsQuery.mri}),
  102. onAction: () => {
  103. trackAnalytics('ddm.widget.settings', {
  104. organization,
  105. });
  106. Sentry.metrics.increment('ddm.widget.settings');
  107. navigateTo(
  108. `/settings/projects/:projectId/metrics/${encodeURIComponent(
  109. metricsQuery.mri
  110. )}`,
  111. router
  112. );
  113. },
  114. },
  115. {
  116. leadingItems: [<IconClose key="icon" />],
  117. key: 'delete',
  118. label: t('Remove Query'),
  119. disabled: !canDelete,
  120. onAction: () => {
  121. Sentry.metrics.increment('ddm.widget.delete');
  122. removeWidget(widgetIndex);
  123. },
  124. },
  125. ],
  126. [
  127. createAlert,
  128. createDashboardWidget,
  129. metricsQuery.mri,
  130. canDelete,
  131. organization,
  132. duplicateWidget,
  133. widgetIndex,
  134. router,
  135. removeWidget,
  136. ]
  137. );
  138. if (!hasDDMFeature(organization)) {
  139. return null;
  140. }
  141. return (
  142. <DropdownMenu
  143. items={items}
  144. triggerProps={{
  145. 'aria-label': t('Widget actions'),
  146. size: 'md',
  147. showChevron: false,
  148. icon: <IconEllipsis direction="down" size="sm" />,
  149. }}
  150. position="bottom-end"
  151. />
  152. );
  153. }
  154. export function getCreateAlert(organization: Organization, metricsQuery: MetricsQuery) {
  155. if (
  156. !metricsQuery.mri ||
  157. !metricsQuery.op ||
  158. isCustomMeasurement(metricsQuery) ||
  159. !organization.access.includes('alerts:write')
  160. ) {
  161. return undefined;
  162. }
  163. return function () {
  164. return openModal(deps => (
  165. <OrganizationContext.Provider value={organization}>
  166. <CreateAlertModal metricsQuery={metricsQuery} {...deps} />
  167. </OrganizationContext.Provider>
  168. ));
  169. };
  170. }
  171. export function useCreateDashboardWidget(
  172. organization: Organization,
  173. metricsQuery: MetricsQuery,
  174. displayType?: MetricDisplayType
  175. ) {
  176. const router = useRouter();
  177. const {projects, environments, datetime} = metricsQuery;
  178. return useMemo(() => {
  179. if (!metricsQuery.mri || !metricsQuery.op || isCustomMeasurement(metricsQuery)) {
  180. return undefined;
  181. }
  182. const widgetQuery = getWidgetQuery(metricsQuery);
  183. const urlWidgetQuery = encodeWidgetQuery(widgetQuery);
  184. const widgetAsQueryParams = getWidgetAsQueryParams(
  185. metricsQuery,
  186. urlWidgetQuery,
  187. displayType
  188. );
  189. return () =>
  190. openAddToDashboardModal({
  191. organization,
  192. selection: {
  193. projects,
  194. environments,
  195. datetime,
  196. },
  197. widget: convertToDashboardWidget(metricsQuery, displayType),
  198. router,
  199. widgetAsQueryParams,
  200. location: router.location,
  201. });
  202. }, [metricsQuery, datetime, displayType, environments, organization, projects, router]);
  203. }