contextMenu.tsx 5.3 KB

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