contextMenu.tsx 5.9 KB

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