contextMenu.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import {useMemo} from 'react';
  2. import styled from '@emotion/styled';
  3. import {urlEncode} from '@sentry/utils';
  4. import {openAddToDashboardModal, openModal} from 'sentry/actionCreators/modal';
  5. import {DropdownMenu} from 'sentry/components/dropdownMenu';
  6. import {IconDashboard, IconEllipsis, IconSiren} from 'sentry/icons';
  7. import {t} from 'sentry/locale';
  8. import {space} from 'sentry/styles/space';
  9. import {Organization} from 'sentry/types';
  10. import {
  11. getFieldFromMetricsQuery,
  12. isCustomMeasurement,
  13. isCustomMetric,
  14. MetricDisplayType,
  15. MetricsQuery,
  16. } from 'sentry/utils/metrics';
  17. import {hasDDMFeature} from 'sentry/utils/metrics/features';
  18. import useOrganization from 'sentry/utils/useOrganization';
  19. import useRouter from 'sentry/utils/useRouter';
  20. import {DashboardWidgetSource, WidgetType} from 'sentry/views/dashboards/types';
  21. import {CreateAlertModal} from 'sentry/views/ddm/createAlertModal';
  22. import {OrganizationContext} from 'sentry/views/organizationContext';
  23. type ContextMenuProps = {
  24. displayType: MetricDisplayType;
  25. metricsQuery: MetricsQuery;
  26. };
  27. export function MetricWidgetContextMenu({metricsQuery, displayType}: ContextMenuProps) {
  28. const organization = useOrganization();
  29. const createAlert = useCreateAlert(organization, metricsQuery);
  30. const createDashboardWidget = useCreateDashboardWidget(
  31. organization,
  32. metricsQuery,
  33. displayType
  34. );
  35. if (!hasDDMFeature(organization)) {
  36. return null;
  37. }
  38. return (
  39. <StyledDropdownMenuControl
  40. items={[
  41. {
  42. leadingItems: [<IconSiren key="icon" />],
  43. key: 'add-alert',
  44. label: t('Create Alert'),
  45. disabled: !createAlert,
  46. onAction: createAlert,
  47. },
  48. {
  49. leadingItems: [<IconDashboard key="icon" />],
  50. key: 'add-dashoard',
  51. label: t('Add to Dashboard'),
  52. disabled: !createDashboardWidget,
  53. onAction: createDashboardWidget,
  54. },
  55. ]}
  56. triggerProps={{
  57. 'aria-label': t('Widget actions'),
  58. size: 'xs',
  59. borderless: true,
  60. showChevron: false,
  61. icon: <IconEllipsis direction="down" size="sm" />,
  62. }}
  63. position="bottom-end"
  64. />
  65. );
  66. }
  67. const StyledDropdownMenuControl = styled(DropdownMenu)`
  68. margin: ${space(1)};
  69. `;
  70. export function useCreateAlert(organization: Organization, metricsQuery: MetricsQuery) {
  71. return useMemo(() => {
  72. if (
  73. !metricsQuery.mri ||
  74. !metricsQuery.op ||
  75. isCustomMeasurement(metricsQuery) ||
  76. !organization.access.includes('alerts:write')
  77. ) {
  78. return undefined;
  79. }
  80. return function () {
  81. return openModal(deps => (
  82. <OrganizationContext.Provider value={organization}>
  83. <CreateAlertModal metricsQuery={metricsQuery} {...deps} />
  84. </OrganizationContext.Provider>
  85. ));
  86. };
  87. }, [metricsQuery, organization]);
  88. }
  89. export function useCreateDashboardWidget(
  90. organization: Organization,
  91. metricsQuery: MetricsQuery,
  92. displayType?: MetricDisplayType
  93. ) {
  94. const router = useRouter();
  95. const {projects, environments, datetime} = metricsQuery;
  96. const isCustomMetricQuery = isCustomMetric(metricsQuery);
  97. return useMemo(() => {
  98. if (!metricsQuery.mri || !metricsQuery.op || isCustomMeasurement(metricsQuery)) {
  99. return undefined;
  100. }
  101. const widgetQuery = getWidgetQuery(metricsQuery);
  102. const urlWidgetQuery = encodeWidgetQuery(widgetQuery);
  103. const widgetAsQueryParams = getWidgetAsQueryParams(
  104. metricsQuery,
  105. urlWidgetQuery,
  106. displayType
  107. );
  108. return () =>
  109. openAddToDashboardModal({
  110. organization,
  111. selection: {
  112. projects,
  113. environments,
  114. datetime,
  115. },
  116. widget: {
  117. title: 'DDM Widget',
  118. displayType,
  119. widgetType: isCustomMetricQuery ? WidgetType.METRICS : WidgetType.DISCOVER,
  120. limit: !metricsQuery.groupBy?.length ? 1 : 10,
  121. queries: [widgetQuery],
  122. },
  123. router,
  124. widgetAsQueryParams,
  125. location: router.location,
  126. });
  127. }, [
  128. isCustomMetricQuery,
  129. metricsQuery,
  130. datetime,
  131. displayType,
  132. environments,
  133. organization,
  134. projects,
  135. router,
  136. ]);
  137. }
  138. function getWidgetQuery(metricsQuery: MetricsQuery) {
  139. const field = getFieldFromMetricsQuery(metricsQuery);
  140. return {
  141. name: '',
  142. aggregates: [field],
  143. columns: metricsQuery.groupBy ?? [],
  144. fields: [field],
  145. conditions: metricsQuery.query ?? '',
  146. orderby: '',
  147. };
  148. }
  149. function encodeWidgetQuery(query) {
  150. return urlEncode({
  151. ...query,
  152. aggregates: query.aggregates.join(','),
  153. fields: query.fields?.join(','),
  154. columns: query.columns.join(','),
  155. });
  156. }
  157. function getWidgetAsQueryParams(
  158. metricsQuery: MetricsQuery,
  159. urlWidgetQuery: string,
  160. displayType?: MetricDisplayType
  161. ) {
  162. const {start, end, period} = metricsQuery.datetime;
  163. const {projects} = metricsQuery;
  164. return {
  165. source: DashboardWidgetSource.DDM,
  166. start,
  167. end,
  168. statsPeriod: period,
  169. defaultWidgetQuery: urlWidgetQuery,
  170. defaultTableColumns: [],
  171. defaultTitle: 'DDM Widget',
  172. environment: metricsQuery.environments,
  173. displayType,
  174. project: projects,
  175. };
  176. }