metricFormulaContextMenu.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. import {useMemo} from 'react';
  2. import styled from '@emotion/styled';
  3. import * as Sentry from '@sentry/react';
  4. import Feature from 'sentry/components/acl/feature';
  5. import type {MenuItemProps} from 'sentry/components/dropdownMenu';
  6. import {DropdownMenu} from 'sentry/components/dropdownMenu';
  7. import {IconClose, IconCopy, IconDashboard, IconEllipsis} from 'sentry/icons';
  8. import {t} from 'sentry/locale';
  9. import {trackAnalytics} from 'sentry/utils/analytics';
  10. import {isCustomMeasurement} from 'sentry/utils/metrics';
  11. import {hasCustomMetrics} from 'sentry/utils/metrics/features';
  12. import type {MetricsEquationWidget} from 'sentry/utils/metrics/types';
  13. import useOrganization from 'sentry/utils/useOrganization';
  14. import {useMetricsContext} from 'sentry/views/metrics/context';
  15. import {useCreateDashboard} from 'sentry/views/metrics/useCreateDashboard';
  16. import type {useFormulaDependencies} from 'sentry/views/metrics/utils/useFormulaDependencies';
  17. type ContextMenuProps = {
  18. formulaDependencies: ReturnType<typeof useFormulaDependencies>;
  19. formulaWidget: MetricsEquationWidget;
  20. widgetIndex: number;
  21. };
  22. export function MetricFormulaContextMenu({
  23. widgetIndex,
  24. formulaWidget,
  25. formulaDependencies,
  26. }: ContextMenuProps) {
  27. const organization = useOrganization();
  28. const {removeWidget, duplicateWidget, widgets} = useMetricsContext();
  29. const canDelete = widgets.length > 1;
  30. const createDashboardWidget = useCreateDashboardWidget(
  31. formulaWidget,
  32. formulaDependencies
  33. );
  34. const items = useMemo<MenuItemProps[]>(
  35. () => [
  36. {
  37. leadingItems: [<IconCopy key="icon" />],
  38. key: 'duplicate',
  39. label: t('Duplicate'),
  40. onAction: () => {
  41. trackAnalytics('ddm.widget.duplicate', {
  42. organization,
  43. });
  44. Sentry.metrics.increment('ddm.widget.duplicate');
  45. duplicateWidget(widgetIndex);
  46. },
  47. },
  48. {
  49. leadingItems: [<IconDashboard key="icon" />],
  50. key: 'add-dashboard',
  51. label: (
  52. <Feature
  53. organization={organization}
  54. hookName="feature-disabled:dashboards-edit"
  55. features="dashboards-edit"
  56. >
  57. {({hasFeature}) => (
  58. <AddToDashboardItem disabled={!hasFeature}>
  59. {t('Add to Dashboard')}
  60. </AddToDashboardItem>
  61. )}
  62. </Feature>
  63. ),
  64. disabled: !createDashboardWidget,
  65. onAction: () => {
  66. if (!organization.features.includes('dashboards-edit')) {
  67. return;
  68. }
  69. trackAnalytics('ddm.add-to-dashboard', {
  70. organization,
  71. source: 'widget',
  72. });
  73. Sentry.metrics.increment('ddm.widget.dashboard');
  74. createDashboardWidget?.();
  75. },
  76. },
  77. {
  78. leadingItems: [<IconClose key="icon" />],
  79. key: 'delete',
  80. label: t('Remove Query'),
  81. disabled: !canDelete,
  82. onAction: () => {
  83. Sentry.metrics.increment('ddm.widget.delete');
  84. removeWidget(widgetIndex);
  85. },
  86. },
  87. ],
  88. [
  89. organization,
  90. createDashboardWidget,
  91. canDelete,
  92. duplicateWidget,
  93. widgetIndex,
  94. removeWidget,
  95. ]
  96. );
  97. if (!hasCustomMetrics(organization)) {
  98. return null;
  99. }
  100. return (
  101. <DropdownMenu
  102. items={items}
  103. triggerProps={{
  104. 'aria-label': t('Widget actions'),
  105. size: 'md',
  106. showChevron: false,
  107. icon: <IconEllipsis direction="down" size="sm" />,
  108. }}
  109. position="bottom-end"
  110. />
  111. );
  112. }
  113. export function useCreateDashboardWidget(
  114. formulaWidget: MetricsEquationWidget,
  115. formulaDependencies: ReturnType<typeof useFormulaDependencies>
  116. ) {
  117. const {dependencies, isError} = formulaDependencies[formulaWidget.id];
  118. const widgetArray = useMemo(() => [formulaWidget], [formulaWidget]);
  119. const createDashboard = useCreateDashboard(widgetArray, formulaDependencies, false);
  120. if (!formulaWidget.formula || isError || dependencies.some(isCustomMeasurement)) {
  121. return undefined;
  122. }
  123. return createDashboard;
  124. }
  125. const AddToDashboardItem = styled('div')<{disabled: boolean}>`
  126. color: ${p => (p.disabled ? p.theme.disabled : p.theme.textColor)};
  127. `;