contextMenu.tsx 5.5 KB

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