contextMenu.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. import {useMemo} from 'react';
  2. import styled from '@emotion/styled';
  3. import {openAddToDashboardModal} from 'sentry/actionCreators/modal';
  4. import {DropdownMenu} from 'sentry/components/dropdownMenu';
  5. import {IconEllipsis} from 'sentry/icons';
  6. import {t} from 'sentry/locale';
  7. import {space} from 'sentry/styles/space';
  8. import {Organization} from 'sentry/types';
  9. import {MetricDisplayType, MetricsQuery} from 'sentry/utils/metrics';
  10. import {MRIToField, parseMRI} from 'sentry/utils/metrics/mri';
  11. import useOrganization from 'sentry/utils/useOrganization';
  12. import usePageFilters from 'sentry/utils/usePageFilters';
  13. import useProjects from 'sentry/utils/useProjects';
  14. import useRouter from 'sentry/utils/useRouter';
  15. import {Dataset, EventTypes} from 'sentry/views/alerts/rules/metric/types';
  16. import {DashboardWidgetSource, WidgetType} from 'sentry/views/dashboards/types';
  17. type ContextMenuProps = {
  18. displayType: MetricDisplayType;
  19. metricsQuery: MetricsQuery;
  20. };
  21. export function MetricWidgetContextMenu({metricsQuery, displayType}: ContextMenuProps) {
  22. const organization = useOrganization();
  23. const createAlertUrl = useCreateAlertUrl(organization, metricsQuery);
  24. const handleAddQueryToDashboard = useHandleAddQueryToDashboard(
  25. organization,
  26. metricsQuery,
  27. displayType
  28. );
  29. if (!organization.features.includes('ddm-experimental')) {
  30. return null;
  31. }
  32. return (
  33. <StyledDropdownMenuControl
  34. items={[
  35. {
  36. key: 'add-alert',
  37. label: t('Create Alert'),
  38. disabled: !createAlertUrl,
  39. to: createAlertUrl,
  40. },
  41. {
  42. key: 'add-dashoard',
  43. label: t('Add to Dashboard'),
  44. disabled: !handleAddQueryToDashboard,
  45. onAction: handleAddQueryToDashboard,
  46. },
  47. ]}
  48. triggerProps={{
  49. 'aria-label': t('Widget actions'),
  50. size: 'xs',
  51. borderless: true,
  52. showChevron: false,
  53. icon: <IconEllipsis direction="down" size="sm" />,
  54. }}
  55. position="bottom-end"
  56. />
  57. );
  58. }
  59. function useHandleAddQueryToDashboard(
  60. organization: Organization,
  61. {projects, environments, datetime, op, mri, groupBy, query}: MetricsQuery,
  62. displayType?: MetricDisplayType
  63. ) {
  64. const router = useRouter();
  65. const {start, end, period} = datetime;
  66. return useMemo(() => {
  67. if (!mri || !op) {
  68. return undefined;
  69. }
  70. const field = MRIToField(mri, op);
  71. const limit = !groupBy?.length ? 1 : 10;
  72. const widgetQuery = {
  73. name: '',
  74. aggregates: [field],
  75. columns: groupBy ?? [],
  76. fields: [field],
  77. conditions: query ?? '',
  78. orderby: '',
  79. };
  80. const urlWidgetQuery = new URLSearchParams({
  81. ...widgetQuery,
  82. aggregates: field,
  83. fields: field,
  84. columns: groupBy?.join(',') ?? '',
  85. }).toString();
  86. const widgetAsQueryParams = {
  87. source: DashboardWidgetSource.DDM,
  88. start,
  89. end,
  90. statsPeriod: period,
  91. defaultWidgetQuery: urlWidgetQuery,
  92. defaultTableColumns: [],
  93. defaultTitle: 'DDM Widget',
  94. environment: environments,
  95. displayType,
  96. project: projects,
  97. };
  98. return () =>
  99. openAddToDashboardModal({
  100. organization,
  101. selection: {
  102. projects,
  103. environments,
  104. datetime,
  105. },
  106. widget: {
  107. title: 'DDM Widget',
  108. displayType,
  109. widgetType: WidgetType.METRICS,
  110. limit,
  111. queries: [widgetQuery],
  112. },
  113. router,
  114. widgetAsQueryParams,
  115. location: router.location,
  116. });
  117. }, [
  118. datetime,
  119. displayType,
  120. end,
  121. environments,
  122. groupBy,
  123. mri,
  124. op,
  125. organization,
  126. period,
  127. projects,
  128. query,
  129. router,
  130. start,
  131. ]);
  132. }
  133. function useCreateAlertUrl(organization: Organization, metricsQuery: MetricsQuery) {
  134. const projects = useProjects();
  135. const pageFilters = usePageFilters();
  136. const selectedProjects = pageFilters.selection.projects;
  137. const firstProjectSlug =
  138. selectedProjects.length > 0 &&
  139. projects.projects.find(p => p.id === selectedProjects[0].toString())?.slug;
  140. return useMemo(() => {
  141. if (
  142. !firstProjectSlug ||
  143. !metricsQuery.mri ||
  144. !metricsQuery.op ||
  145. parseMRI(metricsQuery.mri)?.useCase !== 'custom'
  146. ) {
  147. return undefined;
  148. }
  149. return {
  150. pathname: `/organizations/${organization.slug}/alerts/new/metric/`,
  151. query: {
  152. // Needed, so alerts-create also collects environment via event view
  153. createFromDiscover: true,
  154. dataset: Dataset.GENERIC_METRICS,
  155. eventTypes: EventTypes.TRANSACTION,
  156. aggregate: MRIToField(metricsQuery.mri, metricsQuery.op as string),
  157. referrer: 'ddm',
  158. // Event type also needs to be added to the query
  159. query: `${metricsQuery.query} event.type:transaction`.trim(),
  160. environment: metricsQuery.environments,
  161. project: firstProjectSlug,
  162. },
  163. };
  164. }, [
  165. firstProjectSlug,
  166. metricsQuery.environments,
  167. metricsQuery.mri,
  168. metricsQuery.op,
  169. metricsQuery.query,
  170. organization.slug,
  171. ]);
  172. }
  173. const StyledDropdownMenuControl = styled(DropdownMenu)`
  174. margin: ${space(1)};
  175. `;