Browse Source

ref(metrics): Remove references to span based metric modals (#76776)

Remove references to create and edit modal for span based metrics.

After this and #76775 there is no way left to create or edit the
extraction rules for span based metrics.
ArthurKnaus 6 months ago
parent
commit
92b7fad163

+ 3 - 104
static/app/components/metrics/metricQuerySelect.tsx

@@ -1,28 +1,22 @@
-import {useCallback, useEffect, useMemo} from 'react';
+import {useEffect, useMemo} from 'react';
 import {css} from '@emotion/react';
 import styled from '@emotion/styled';
 
-import {Button} from 'sentry/components/button';
 import {
   CompactSelect,
   type SelectOption,
   type SelectOptionOrSection,
   type SelectSection,
 } from 'sentry/components/compactSelect';
-import {DropdownMenu} from 'sentry/components/dropdownMenu';
 import ProjectBadge from 'sentry/components/idBadge/projectBadge';
 import {QueryFieldGroup} from 'sentry/components/metrics/queryFieldGroup';
 import {parseSearch} from 'sentry/components/searchSyntax/parser';
 import HighlightQuery from 'sentry/components/searchSyntax/renderer';
 import {Tooltip} from 'sentry/components/tooltip';
-import {IconAdd, IconInfo, IconProject, IconWarning} from 'sentry/icons';
+import {IconProject, IconWarning} from 'sentry/icons';
 import {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
-import type {
-  MetricsExtractionCondition,
-  MetricsExtractionRule,
-  MRI,
-} from 'sentry/types/metrics';
+import type {MetricsExtractionCondition, MRI} from 'sentry/types/metrics';
 import {BUILT_IN_CONDITION_ID} from 'sentry/utils/metrics/extractionRules';
 import {hasMetricsNewInputs} from 'sentry/utils/metrics/features';
 import {useCardinalityLimitedMetricVolume} from 'sentry/utils/metrics/useCardinalityLimitedMetricVolume';
@@ -30,8 +24,6 @@ import {useVirtualMetricsContext} from 'sentry/utils/metrics/virtualMetricsConte
 import useOrganization from 'sentry/utils/useOrganization';
 import usePageFilters from 'sentry/utils/usePageFilters';
 import useProjects from 'sentry/utils/useProjects';
-import {useSelectedProjects} from 'sentry/views/metrics/utils/useSelectedProjects';
-import {openExtractionRuleEditModal} from 'sentry/views/settings/projectMetrics/metricsExtractionRuleEditModal';
 
 interface Props {
   mri: MRI;
@@ -180,9 +172,6 @@ export function MetricQuerySelect({onChange, conditionId, mri}: Props) {
         onChange={({value}) => {
           onChange(value);
         }}
-        menuFooter={({closeOverlay}) => (
-          <QueryFooter mri={mri} closeOverlay={closeOverlay} />
-        )}
         css={css`
           && {
             width: auto;
@@ -208,9 +197,6 @@ export function MetricQuerySelect({onChange, conditionId, mri}: Props) {
       onChange={({value}) => {
         onChange(value);
       }}
-      menuFooter={({closeOverlay}) => (
-        <QueryFooter mri={mri} closeOverlay={closeOverlay} />
-      )}
     />
   );
 }
@@ -234,78 +220,6 @@ export function CardinalityWarningIcon() {
   );
 }
 
-function QueryFooter({mri, closeOverlay}: {closeOverlay: () => void; mri: MRI}) {
-  const organization = useOrganization();
-  const {getExtractionRules} = useVirtualMetricsContext();
-  const selectedProjects = useSelectedProjects();
-  const extractionRules = getExtractionRules(mri);
-
-  const handleEdit = useCallback(
-    (rule: MetricsExtractionRule) => {
-      closeOverlay();
-      openExtractionRuleEditModal({
-        organization,
-        source: 'ddm.condition-select.add-filter',
-        metricExtractionRule: rule,
-      });
-    },
-    [closeOverlay, organization]
-  );
-
-  const options = useMemo(
-    () =>
-      extractionRules
-        .map(rule => {
-          const project = selectedProjects.find(p => Number(p.id) === rule.projectId);
-          if (!project) {
-            return null;
-          }
-          return {
-            key: project.slug,
-            label: <ProjectBadge project={project} avatarSize={16} disableLink />,
-            onAction: () => handleEdit(rule),
-          };
-        })
-        .filter(item => item !== null)
-        .toSorted((a, b) => a.key.localeCompare(b.key)),
-    [extractionRules, handleEdit, selectedProjects]
-  );
-
-  return (
-    <QueryFooterWrapper>
-      {extractionRules.length > 1 ? (
-        <DropdownMenu
-          size="xs"
-          triggerLabel={t('Add Filter')}
-          triggerProps={{
-            icon: <IconAdd isCircled />,
-          }}
-          items={options}
-        />
-      ) : (
-        <Button
-          size="xs"
-          icon={<IconAdd isCircled />}
-          onClick={() => extractionRules[0] && handleEdit(extractionRules[0])}
-        >
-          {t('Add Filter')}
-        </Button>
-      )}
-      <InfoWrapper>
-        <Tooltip
-          title={t(
-            'Ideally, you can visualize span data by any property you want. However, our infrastructure has limits as well, so pretty please define in advance what you want to see.'
-          )}
-          skipWrapper
-        >
-          <IconInfo size="xs" />
-        </Tooltip>
-        {t('What are filters?')}
-      </InfoWrapper>
-    </QueryFooterWrapper>
-  );
-}
-
 function parseConditionValue(condition?: MetricsExtractionCondition) {
   if (condition?.value) {
     try {
@@ -341,21 +255,6 @@ function FormattedCondition({condition}: {condition?: MetricsExtractionCondition
   );
 }
 
-const InfoWrapper = styled('div')`
-  display: flex;
-  align-items: center;
-  gap: ${space(0.5)};
-  font-size: ${p => p.theme.fontSizeExtraSmall};
-  color: ${p => p.theme.subText};
-`;
-
-const QueryFooterWrapper = styled('div')`
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  min-width: 250px;
-`;
-
 const Highlight = styled('span')`
   padding: ${space(0.5)} ${space(0.25)};
   overflow: hidden;

+ 1 - 49
static/app/components/metrics/mriSelect/index.spec.tsx

@@ -1,14 +1,6 @@
-import {initializeOrg} from 'sentry-test/initializeOrg';
-import {
-  render,
-  renderGlobalModal,
-  screen,
-  userEvent,
-} from 'sentry-test/reactTestingLibrary';
-
 import type {MetricMeta, UseCase} from 'sentry/types/metrics';
 
-import {getMetricsWithDuplicateNames, MRISelect} from '.';
+import {getMetricsWithDuplicateNames} from '.';
 
 function createMetricMeta(
   name: string,
@@ -80,44 +72,4 @@ describe('getMetricsWithDuplicateNames', () => {
     const result = getMetricsWithDuplicateNames(metrics);
     expect(result).toEqual(new Set([]));
   });
-
-  it('by clicking on the "create metric" button the metric modal shall be opened', async function () {
-    const {project, organization} = initializeOrg({
-      organization: {features: ['metrics-new-inputs']},
-    });
-
-    render(
-      <MRISelect
-        onChange={jest.fn()}
-        onTagClick={jest.fn()}
-        onOpenMenu={jest.fn()}
-        isLoading={false}
-        metricsMeta={[
-          {
-            blockingStatus: [],
-            mri: 'c:custom/span.duration@none',
-            operations: ['sum'],
-            projectIds: [Number(project.id)],
-            type: 'c',
-            unit: 'none',
-          },
-        ]}
-        projects={[Number(project)]}
-        value="d:spans/duration@millisecond"
-      />,
-      {
-        organization,
-      }
-    );
-
-    renderGlobalModal();
-
-    await userEvent.click(screen.getByLabelText('Metric'));
-    await userEvent.click(screen.getByRole('button', {name: 'Create Metric'}));
-    expect(screen.getByText(/Don’t see your span attribute/)).toBeInTheDocument();
-
-    expect(
-      await screen.findByRole('heading', {name: 'Create Metric'})
-    ).toBeInTheDocument();
-  });
 });

+ 1 - 42
static/app/components/metrics/mriSelect/index.tsx

@@ -2,14 +2,12 @@ import {memo, useCallback, useEffect, useMemo, useState} from 'react';
 import {css, useTheme} from '@emotion/react';
 import styled from '@emotion/styled';
 
-import {Button} from 'sentry/components/button';
 import {ComboBox} from 'sentry/components/comboBox';
 import type {ComboBoxOption} from 'sentry/components/comboBox/types';
 import ProjectBadge from 'sentry/components/idBadge/projectBadge';
 import {QueryFieldGroup} from 'sentry/components/metrics/queryFieldGroup';
-import {IconAdd, IconInfo, IconProject, IconWarning} from 'sentry/icons';
+import {IconProject, IconWarning} from 'sentry/icons';
 import {t} from 'sentry/locale';
-import {space} from 'sentry/styles/space';
 import type {MetricMeta, MRI} from 'sentry/types/metrics';
 import {type Fuse, useFuzzySearch} from 'sentry/utils/fuzzySearch';
 import {
@@ -31,7 +29,6 @@ import {middleEllipsis} from 'sentry/utils/string/middleEllipsis';
 import useKeyPress from 'sentry/utils/useKeyPress';
 import useOrganization from 'sentry/utils/useOrganization';
 import useProjects from 'sentry/utils/useProjects';
-import {openExtractionRuleCreateModal} from 'sentry/views/settings/projectMetrics/metricsExtractionRuleCreateModal';
 
 import {MetricListItemDetails} from './metricListItemDetails';
 
@@ -350,38 +347,6 @@ export const MRISelect = memo(function MRISelect({
               `
             : undefined
         }
-        menuFooter={
-          isLoading
-            ? undefined
-            : ({closeOverlay}) => (
-                <FlexBlock>
-                  <Button
-                    icon={<IconAdd isCircled />}
-                    priority="primary"
-                    onClick={() => {
-                      closeOverlay();
-                      openExtractionRuleCreateModal({
-                        organization,
-                        source: 'ddm.metric-select.create-metric',
-                      });
-                    }}
-                    size="xs"
-                  >
-                    {t('Create Metric')}
-                  </Button>
-                  <FlexBlock
-                    css={css`
-                      gap: ${space(0.75)};
-                      color: ${theme.subText};
-                      font-size: ${theme.fontSizeSmall};
-                    `}
-                  >
-                    <IconInfo size="xs" />
-                    {t('Don’t see your span attribute? Create Metric.')}
-                  </FlexBlock>
-                </FlexBlock>
-              )
-        }
       />
     );
   }
@@ -414,9 +379,3 @@ export const MRISelect = memo(function MRISelect({
 const CustomMetricInfoText = styled('span')`
   color: ${p => p.theme.subText};
 `;
-
-const FlexBlock = styled('div')`
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-`;

+ 8 - 6
static/app/views/metrics/layout.spec.tsx

@@ -52,9 +52,9 @@ describe('Metrics Layout', function () {
 
     render(<MetricsLayout />, {organization});
 
-    // Button: Create Metric
+    // Button: Add Custom Metrics
     expect(
-      await screen.findByRole('button', {name: 'Create Metric'})
+      await screen.findByRole('button', {name: 'Add Custom Metrics'})
     ).toBeInTheDocument();
 
     // Alert: No alert shall be rendered
@@ -85,8 +85,10 @@ describe('Metrics Layout', function () {
     // Button: Set Up Tracing
     expect(screen.getByRole('button', {name: 'Set Up Tracing'})).toBeInTheDocument();
 
-    // Not in the page: Create Metric
-    expect(screen.queryByRole('button', {name: 'Create Metric'})).not.toBeInTheDocument();
+    // Not in the page: Add Custom Metrics
+    expect(
+      screen.queryByRole('button', {name: 'Add Custom Metrics'})
+    ).not.toBeInTheDocument();
   });
 
   it('not using performance and have old custom metrics', async function () {
@@ -104,8 +106,8 @@ describe('Metrics Layout', function () {
       await screen.findByText(/Metrics using with the old API will stop being ingested/i)
     ).toBeInTheDocument();
 
-    // Button: Create Metric
-    expect(screen.getByRole('button', {name: 'Create Metric'})).toBeInTheDocument();
+    // Button: Add Custom Metrics
+    expect(screen.getByRole('button', {name: 'Add Custom Metrics'})).toBeInTheDocument();
 
     // Main View: Does not display the empty state.
     expect(screen.queryByText(/track and solve what matters/i)).not.toBeInTheDocument();

+ 1 - 26
static/app/views/metrics/metricQueryContextMenu.tsx

@@ -32,7 +32,6 @@ import {
   isMetricsQueryWidget,
   type MetricDisplayType,
   type MetricsQuery,
-  type MetricsQueryWidget,
 } from 'sentry/utils/metrics/types';
 import {useVirtualMetricsContext} from 'sentry/utils/metrics/virtualMetricsContext';
 import useOrganization from 'sentry/utils/useOrganization';
@@ -40,7 +39,6 @@ import usePageFilters from 'sentry/utils/usePageFilters';
 import useRouter from 'sentry/utils/useRouter';
 import {useMetricsContext} from 'sentry/views/metrics/context';
 import {openCreateAlertModal} from 'sentry/views/metrics/createAlertModal';
-import {openExtractionRuleEditModal} from 'sentry/views/settings/projectMetrics/metricsExtractionRuleEditModal';
 
 type ContextMenuProps = {
   displayType: MetricDisplayType;
@@ -53,11 +51,10 @@ export function MetricQueryContextMenu({
   displayType,
   widgetIndex,
 }: ContextMenuProps) {
-  const {getExtractionRule} = useVirtualMetricsContext();
   const organization = useOrganization();
   const router = useRouter();
 
-  const {removeWidget, duplicateWidget, widgets, updateWidget} = useMetricsContext();
+  const {removeWidget, duplicateWidget, widgets} = useMetricsContext();
   const createAlert = getCreateAlert(organization, metricsQuery);
 
   const createDashboardWidget = useCreateDashboardWidget(
@@ -154,26 +151,6 @@ export function MetricQueryContextMenu({
               )}`,
               router
             );
-          } else {
-            const extractionRule = getExtractionRule(
-              metricsQuery.mri,
-              metricsQuery.condition!
-            );
-            if (extractionRule) {
-              openExtractionRuleEditModal({
-                organization,
-                source: 'ddm.configure-metric',
-                metricExtractionRule: extractionRule,
-                onSubmitSuccess: data => {
-                  // Keep the unit of the MRI in sync with the unit of the extraction rule
-                  // TODO: Remove this once we have a better way to handle this
-                  const newMRI = metricsQuery.mri.replace(/@.*$/, `@${data.unit}`);
-                  updateWidget(widgetIndex, {
-                    mri: newMRI,
-                  } as Partial<MetricsQueryWidget>);
-                },
-              });
-            }
           }
         },
       },
@@ -198,8 +175,6 @@ export function MetricQueryContextMenu({
       duplicateWidget,
       widgetIndex,
       router,
-      getExtractionRule,
-      updateWidget,
       removeWidget,
     ]
   );

+ 1 - 33
static/app/views/metrics/pageHeaderActions.spec.tsx

@@ -1,11 +1,4 @@
-import {OrganizationFixture} from 'sentry-fixture/organization';
-
-import {
-  render,
-  renderGlobalModal,
-  screen,
-  userEvent,
-} from 'sentry-test/reactTestingLibrary';
+import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
 
 import {PageHeaderActions} from 'sentry/views/metrics/pageHeaderActions';
 
@@ -26,30 +19,5 @@ describe('Metrics Page Header Actions', function () {
 
       expect(addCustomMetric).toHaveBeenCalled();
     });
-
-    it('display "Create Metric" button', async function () {
-      render(
-        <PageHeaderActions showAddMetricButton addCustomMetric={() => jest.fn()} />,
-        {
-          organization: OrganizationFixture({
-            features: [
-              'custom-metrics-extraction-rule',
-              'custom-metrics-extraction-rule-ui',
-            ],
-          }),
-        }
-      );
-      renderGlobalModal();
-
-      const button = screen.getByRole('button', {name: 'Create Metric'});
-
-      expect(button).toBeInTheDocument();
-
-      await userEvent.click(button);
-
-      expect(
-        await screen.findByRole('heading', {name: 'Create Metric'})
-      ).toBeInTheDocument();
-    });
   });
 });

+ 6 - 24
static/app/views/metrics/pageHeaderActions.tsx

@@ -19,10 +19,7 @@ import {
 import {t} from 'sentry/locale';
 import {trackAnalytics} from 'sentry/utils/analytics';
 import {isCustomMeasurement} from 'sentry/utils/metrics';
-import {
-  hasCustomMetricsExtractionRules,
-  hasMetricsNewInputs,
-} from 'sentry/utils/metrics/features';
+import {hasMetricsNewInputs} from 'sentry/utils/metrics/features';
 import {formatMRI} from 'sentry/utils/metrics/mri';
 import {MetricExpressionType, type MetricsQueryWidget} from 'sentry/utils/metrics/types';
 import {middleEllipsis} from 'sentry/utils/string/middleEllipsis';
@@ -32,7 +29,6 @@ import {useMetricsContext} from 'sentry/views/metrics/context';
 import {getCreateAlert} from 'sentry/views/metrics/metricQueryContextMenu';
 import {useCreateDashboard} from 'sentry/views/metrics/useCreateDashboard';
 import {useFormulaDependencies} from 'sentry/views/metrics/utils/useFormulaDependencies';
-import {openExtractionRuleCreateModal} from 'sentry/views/settings/projectMetrics/metricsExtractionRuleCreateModal';
 
 interface Props {
   addCustomMetric: () => void;
@@ -143,25 +139,11 @@ export function PageHeaderActions({showAddMetricButton, addCustomMetric}: Props)
 
   return (
     <ButtonBar gap={1}>
-      {showAddMetricButton &&
-        (hasCustomMetricsExtractionRules(organization) ? (
-          <Button
-            priority="primary"
-            onClick={() =>
-              openExtractionRuleCreateModal({
-                organization,
-                source: 'ddm.header.create-metric',
-              })
-            }
-            size="sm"
-          >
-            {t('Create Metric')}
-          </Button>
-        ) : (
-          <Button priority="primary" onClick={() => addCustomMetric()} size="sm">
-            {t('Add Custom Metrics')}
-          </Button>
-        ))}
+      {showAddMetricButton && (
+        <Button priority="primary" onClick={() => addCustomMetric()} size="sm">
+          {t('Add Custom Metrics')}
+        </Button>
+      )}
       <Button
         size="sm"
         icon={<IconBookmark isSolid={isDefaultQuery} />}

+ 0 - 296
static/app/views/settings/projectMetrics/metricsExtractionRuleCreateModal.tsx

@@ -1,296 +0,0 @@
-import {Fragment, useCallback, useMemo, useState} from 'react';
-import {css} from '@emotion/react';
-import styled from '@emotion/styled';
-
-import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
-import {
-  type ModalOptions,
-  type ModalRenderProps,
-  openModal,
-} from 'sentry/actionCreators/modal';
-import SelectControl from 'sentry/components/forms/controls/selectControl';
-import ProjectBadge from 'sentry/components/idBadge/projectBadge';
-import {t} from 'sentry/locale';
-import {space} from 'sentry/styles/space';
-import type {MetricsExtractionRule} from 'sentry/types/metrics';
-import type {Organization} from 'sentry/types/organization';
-import type {Project} from 'sentry/types/project';
-import {trackAnalytics} from 'sentry/utils/analytics';
-import {useCardinalityLimitedMetricVolume} from 'sentry/utils/metrics/useCardinalityLimitedMetricVolume';
-import useOrganization from 'sentry/utils/useOrganization';
-import usePageFilters from 'sentry/utils/usePageFilters';
-import useProjects from 'sentry/utils/useProjects';
-import {
-  createCondition,
-  explodeAggregateGroup,
-  type FormData,
-  MetricsExtractionRuleForm,
-} from 'sentry/views/settings/projectMetrics/metricsExtractionRuleForm';
-import {useCreateMetricsExtractionRules} from 'sentry/views/settings/projectMetrics/utils/useMetricsExtractionRules';
-
-interface Props {
-  organization: Organization;
-  /**
-   * Source parameter for analytics
-   */
-  source: string;
-  /**
-   * Initial data to populate the form with
-   */
-  initialData?: Partial<FormData>;
-  /**
-   * Callback when the form is submitted successfully
-   */
-  onSubmitSuccess?: (data: FormData) => void;
-  /**
-   * The project to create the metric for
-   * If not provided, the user will be prompted to select a project
-   */
-  projectId?: string | number;
-}
-
-export const INITIAL_DATA: FormData = {
-  spanAttribute: null,
-  unit: 'none',
-  aggregates: ['count'],
-  tags: ['release', 'environment'],
-  conditions: [createCondition()],
-};
-
-export function MetricsExtractionRuleCreateModal({
-  Header,
-  Body,
-  closeModal,
-  CloseButton,
-  initialData: initalDataProp = {},
-  projectId: projectIdProp,
-  onSubmitSuccess,
-}: Props & ModalRenderProps) {
-  const {projects} = useProjects();
-  const {selection} = usePageFilters();
-
-  const initialData = useMemo(() => {
-    return {
-      ...INITIAL_DATA,
-      ...initalDataProp,
-    };
-  }, [initalDataProp]);
-
-  const initialProjectId = useMemo(() => {
-    if (projectIdProp) {
-      return projectIdProp;
-    }
-
-    if (selection.projects.length === 1 && selection.projects[0] !== -1) {
-      return projects.find(p => p.id === String(selection.projects[0]))?.id;
-    }
-
-    return undefined;
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, []);
-
-  const [projectId, setProjectId] = useState<string | number | undefined>(
-    initialProjectId
-  );
-
-  const projectOptions = useMemo(() => {
-    const nonMemberProjects: Project[] = [];
-    const memberProjects: Project[] = [];
-    projects
-      .filter(
-        project =>
-          selection.projects.length === 0 ||
-          selection.projects.includes(parseInt(project.id, 10))
-      )
-      .forEach(project =>
-        project.isMember ? memberProjects.push(project) : nonMemberProjects.push(project)
-      );
-
-    return [
-      {
-        label: t('My Projects'),
-        options: memberProjects.map(p => ({
-          value: p.id,
-          label: p.slug,
-          leadingItems: <ProjectBadge project={p} avatarSize={16} hideName disableLink />,
-        })),
-      },
-      {
-        label: t('All Projects'),
-        options: nonMemberProjects.map(p => ({
-          value: p.id,
-          label: p.slug,
-          leadingItems: <ProjectBadge project={p} avatarSize={16} hideName disableLink />,
-        })),
-      },
-    ];
-  }, [selection.projects, projects]);
-
-  return (
-    <Fragment>
-      <Header>
-        <h4>{t('Create Metric')}</h4>
-      </Header>
-      <CloseButton />
-      <Body>
-        <p>
-          {t(
-            "Set up the metric you'd like to track and we'll collect it for you from future data."
-          )}
-        </p>
-        {initialProjectId === undefined ? (
-          <ProjectSelectionWrapper>
-            <label htmlFor="project-select">{t('Project')}</label>
-            <SelectControl
-              id="project-select"
-              placeholder={t('Select a project')}
-              options={projectOptions}
-              value={projectId}
-              onChange={({value}) => setProjectId(value)}
-              stacked={false}
-            />
-          </ProjectSelectionWrapper>
-        ) : null}
-        {projectId ? (
-          <FormWrapper
-            initialData={initialData}
-            projectId={projectId}
-            closeModal={closeModal}
-            onSubmitSuccess={onSubmitSuccess}
-          />
-        ) : null}
-      </Body>
-    </Fragment>
-  );
-}
-
-function FormWrapper({
-  closeModal,
-  projectId,
-  initialData,
-  onSubmitSuccess: onSubmitSuccessProp,
-}: {
-  closeModal: () => void;
-  initialData: FormData;
-  projectId: string | number;
-  onSubmitSuccess?: (data: FormData) => void;
-}) {
-  const organization = useOrganization();
-  const createExtractionRuleMutation = useCreateMetricsExtractionRules(
-    organization.slug,
-    projectId
-  );
-
-  const {data: cardinality} = useCardinalityLimitedMetricVolume({
-    projects: [projectId],
-  });
-
-  const handleSubmit = useCallback(
-    (
-      data: FormData,
-      onSubmitSuccess: (data: FormData) => void,
-      onSubmitError: (error: any) => void
-    ) => {
-      const extractionRule: MetricsExtractionRule = {
-        spanAttribute: data.spanAttribute!,
-        tags: data.tags,
-        aggregates: data.aggregates.flatMap(explodeAggregateGroup),
-        unit: data.unit,
-        conditions: data.conditions,
-        projectId: Number(projectId),
-        // Will be set by the backend
-        createdById: null,
-        dateAdded: '',
-        dateUpdated: '',
-      };
-
-      createExtractionRuleMutation.mutate(
-        {
-          metricsExtractionRules: [extractionRule],
-        },
-        {
-          onSuccess: () => {
-            onSubmitSuccessProp?.(data);
-            onSubmitSuccess(data);
-            addSuccessMessage(t('Metric extraction rule created'));
-            closeModal();
-          },
-          onError: error => {
-            const message = error?.responseJSON?.detail
-              ? (error.responseJSON.detail as string)
-              : t('Unable to save your changes.');
-            onSubmitError(message);
-            addErrorMessage(message);
-          },
-        }
-      );
-      onSubmitSuccess(data);
-    },
-    [projectId, createExtractionRuleMutation, onSubmitSuccessProp, closeModal]
-  );
-
-  return (
-    <MetricsExtractionRuleForm
-      initialData={initialData}
-      projectId={projectId}
-      submitLabel={t('Add Metric')}
-      cancelLabel={t('Cancel')}
-      onCancel={closeModal}
-      onSubmit={handleSubmit}
-      cardinality={cardinality}
-      submitDisabled={createExtractionRuleMutation.isLoading}
-    />
-  );
-}
-
-const ProjectSelectionWrapper = styled('div')`
-  padding-bottom: ${space(2)};
-
-  & > label {
-    color: ${p => p.theme.gray300};
-  }
-`;
-
-export const modalCss = css`
-  width: 100%;
-  max-width: 900px;
-`;
-
-export function openExtractionRuleCreateModal(props: Props, options?: ModalOptions) {
-  const {organization, source, onSubmitSuccess} = props;
-
-  trackAnalytics('ddm.span-metric.create.open', {
-    organization,
-    source,
-  });
-
-  const handleClose: ModalOptions['onClose'] = reason => {
-    if (reason && ['close-button', 'backdrop-click', 'escape-key'].includes(reason)) {
-      trackAnalytics('ddm.span-metric.create.cancel', {organization});
-    }
-    options?.onClose?.(reason);
-  };
-
-  const handleSubmitSuccess: Props['onSubmitSuccess'] = data => {
-    trackAnalytics('ddm.span-metric.create.success', {
-      organization,
-      hasFilters: data.conditions.some(condition => condition.value),
-    });
-    onSubmitSuccess?.(data);
-  };
-
-  openModal(
-    modalProps => (
-      <MetricsExtractionRuleCreateModal
-        {...props}
-        onSubmitSuccess={handleSubmitSuccess}
-        {...modalProps}
-      />
-    ),
-    {
-      modalCss,
-      ...options,
-      onClose: handleClose,
-    }
-  );
-}

+ 0 - 177
static/app/views/settings/projectMetrics/metricsExtractionRuleEditModal.tsx

@@ -1,177 +0,0 @@
-import {Fragment, useCallback, useMemo} from 'react';
-import {css} from '@emotion/react';
-
-import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
-import {
-  type ModalOptions,
-  type ModalRenderProps,
-  openModal,
-} from 'sentry/actionCreators/modal';
-import {t} from 'sentry/locale';
-import type {MetricsExtractionRule} from 'sentry/types/metrics';
-import type {Organization} from 'sentry/types/organization';
-import {trackAnalytics} from 'sentry/utils/analytics';
-import {useCardinalityLimitedMetricVolume} from 'sentry/utils/metrics/useCardinalityLimitedMetricVolume';
-import {
-  aggregatesToGroups,
-  createCondition as createExtractionCondition,
-  explodeAggregateGroup,
-  type FormData,
-  MetricsExtractionRuleForm,
-} from 'sentry/views/settings/projectMetrics/metricsExtractionRuleForm';
-import {useUpdateMetricsExtractionRules} from 'sentry/views/settings/projectMetrics/utils/useMetricsExtractionRules';
-
-interface Props {
-  /**
-   * The extraction rule to edit
-   */
-  metricExtractionRule: MetricsExtractionRule;
-  organization: Organization;
-  /**
-   * Source parameter for analytics
-   */
-  source: string;
-  /**
-   * Callback when the form is submitted successfully
-   */
-  onSubmitSuccess?: (data: FormData) => void;
-}
-
-export function MetricsExtractionRuleEditModal({
-  Header,
-  Body,
-  closeModal,
-  CloseButton,
-  metricExtractionRule,
-  organization,
-  onSubmitSuccess: onSubmitSuccessProp,
-}: Props & ModalRenderProps) {
-  const updateExtractionRuleMutation = useUpdateMetricsExtractionRules(
-    organization.slug,
-    metricExtractionRule.projectId
-  );
-
-  const {data: cardinality} = useCardinalityLimitedMetricVolume({
-    projects: [metricExtractionRule.projectId],
-  });
-
-  const initialData: FormData = useMemo(() => {
-    return {
-      spanAttribute: metricExtractionRule.spanAttribute,
-      unit: metricExtractionRule.unit,
-      aggregates: aggregatesToGroups(metricExtractionRule.aggregates),
-      tags: metricExtractionRule.tags,
-      conditions: metricExtractionRule.conditions.length
-        ? metricExtractionRule.conditions
-        : [createExtractionCondition()],
-    };
-  }, [metricExtractionRule]);
-
-  const handleSubmit = useCallback(
-    (
-      data: FormData,
-      onSubmitSuccess: (data: FormData) => void,
-      onSubmitError: (error: any) => void
-    ) => {
-      const extractionRule: MetricsExtractionRule = {
-        ...metricExtractionRule,
-        spanAttribute: data.spanAttribute!,
-        tags: data.tags,
-        aggregates: data.aggregates.flatMap(explodeAggregateGroup),
-        unit: data.unit,
-        conditions: data.conditions,
-      };
-
-      updateExtractionRuleMutation.mutate(
-        {
-          metricsExtractionRules: [extractionRule],
-        },
-        {
-          onSuccess: () => {
-            onSubmitSuccess(data);
-            onSubmitSuccessProp?.(data);
-            addSuccessMessage(t('Metric extraction rule updated'));
-            closeModal();
-          },
-          onError: error => {
-            const message = error?.responseJSON?.detail
-              ? (error.responseJSON.detail as string)
-              : t('Unable to save your changes.');
-            onSubmitError(message);
-            addErrorMessage(message);
-          },
-        }
-      );
-      onSubmitSuccess(data);
-    },
-    [closeModal, metricExtractionRule, onSubmitSuccessProp, updateExtractionRuleMutation]
-  );
-
-  return (
-    <Fragment>
-      <Header>
-        <h4>{t('Edit Metric')}</h4>
-      </Header>
-      <CloseButton />
-      <Body>
-        <MetricsExtractionRuleForm
-          initialData={initialData}
-          projectId={metricExtractionRule.projectId}
-          submitLabel={t('Update')}
-          cancelLabel={t('Cancel')}
-          onCancel={closeModal}
-          onSubmit={handleSubmit}
-          cardinality={cardinality}
-          submitDisabled={updateExtractionRuleMutation.isLoading}
-          isEdit
-          requireChanges
-        />
-      </Body>
-    </Fragment>
-  );
-}
-
-export const modalCss = css`
-  width: 100%;
-  max-width: 900px;
-`;
-
-export function openExtractionRuleEditModal(props: Props, options?: ModalOptions) {
-  const {organization, metricExtractionRule, source, onSubmitSuccess} = props;
-
-  trackAnalytics('ddm.span-metric.edit.open', {
-    organization,
-    hasFilters: metricExtractionRule.conditions.some(condition => condition.value),
-    source,
-  });
-
-  const handleClose: ModalOptions['onClose'] = reason => {
-    if (reason && ['close-button', 'backdrop-click', 'escape-key'].includes(reason)) {
-      trackAnalytics('ddm.span-metric.edit.cancel', {organization});
-    }
-    options?.onClose?.(reason);
-  };
-
-  const handleSubmitSuccess: Props['onSubmitSuccess'] = data => {
-    trackAnalytics('ddm.span-metric.edit.success', {
-      organization,
-      hasFilters: data.conditions.some(condition => condition.value),
-    });
-    onSubmitSuccess?.(data);
-  };
-
-  openModal(
-    modalProps => (
-      <MetricsExtractionRuleEditModal
-        {...props}
-        onSubmitSuccess={handleSubmitSuccess}
-        {...modalProps}
-      />
-    ),
-    {
-      modalCss,
-      ...options,
-      onClose: handleClose,
-    }
-  );
-}

+ 0 - 165
static/app/views/settings/projectMetrics/metricsExtractionRuleForm.spec.tsx

@@ -1,165 +0,0 @@
-import {initializeOrg} from 'sentry-test/initializeOrg';
-import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
-import selectEvent from 'sentry-test/selectEvent';
-import {textWithMarkupMatcher} from 'sentry-test/utils';
-
-import {INITIAL_DATA} from 'sentry/views/settings/projectMetrics/metricsExtractionRuleCreateModal';
-import {MetricsExtractionRuleForm} from 'sentry/views/settings/projectMetrics/metricsExtractionRuleForm';
-
-function renderMockRequests({orgSlug, projectId}: {orgSlug: string; projectId: string}) {
-  MockApiClient.addMockResponse({
-    url: `/organizations/${orgSlug}/spans/fields/`,
-    body: [],
-  });
-  MockApiClient.addMockResponse({
-    url: `/projects/${orgSlug}/${projectId}/metrics/extraction-rules/`,
-    method: 'GET',
-    body: [
-      {
-        aggregates: ['count'],
-        conditions: [{id: 102, value: '', mris: ['c:custom/span_attribute_102@none']}],
-        createdById: 3142223,
-        dateAdded: '2024-07-29T12:04:23.196785Z',
-        dateUpdated: '2024-07-29T12:04:23.197008Z',
-        projectId,
-        spanAttribute: 'A',
-        tags: ['release', 'environment'],
-        unit: 'none',
-      },
-    ],
-  });
-  MockApiClient.addMockResponse({
-    url: `/organizations/${orgSlug}/spans/fields/environment/values/`,
-    body: [
-      {
-        key: 'prod',
-        name: 'prod',
-      },
-      {
-        key: 'dev',
-        name: 'dev',
-      },
-    ],
-  });
-  MockApiClient.addMockResponse({
-    url: '/organizations/org-slug/recent-searches/',
-    method: 'POST',
-    body: [],
-  });
-}
-
-describe('Metrics Extraction Rule Form', function () {
-  it('by focusing on the "select span attribute" field, the UI shall display a hint about custom attribute', async function () {
-    const {project, organization} = initializeOrg();
-
-    renderMockRequests({orgSlug: organization.slug, projectId: project.id});
-
-    render(
-      <MetricsExtractionRuleForm initialData={INITIAL_DATA} projectId={project.id} />
-    );
-
-    await userEvent.click(screen.getByText('Select span attribute'));
-
-    expect(screen.getByText(/See how to instrument a custom attribute/)).toHaveAttribute(
-      'href',
-      'https://docs.sentry.io/product/explore/metrics/metrics-set-up/'
-    );
-  });
-
-  it('by focusing on the "group and filter by" field, the UI shall display a hint about custom attribute', async function () {
-    const {project, organization} = initializeOrg();
-
-    renderMockRequests({orgSlug: organization.slug, projectId: project.id});
-
-    render(
-      <MetricsExtractionRuleForm initialData={INITIAL_DATA} projectId={project.id} />
-    );
-
-    await userEvent.click(screen.getByLabelText('Select tags'));
-
-    expect(screen.getByText(/See how to instrument a custom tag/)).toHaveAttribute(
-      'href',
-      'https://docs.sentry.io/product/explore/metrics/metrics-set-up/'
-    );
-  });
-
-  it('When creating a new metric and selecting a custom attribute, an alert should prompt to remember to instrument it', async function () {
-    const {project, organization} = initializeOrg();
-
-    renderMockRequests({orgSlug: organization.slug, projectId: project.id});
-
-    render(
-      <MetricsExtractionRuleForm initialData={INITIAL_DATA} projectId={project.id} />
-    );
-
-    await userEvent.type(screen.getByText('Select span attribute'), 'new-metric');
-
-    await userEvent.click(
-      // the dom renders 2x of this text because of aria
-      screen.getAllByText(textWithMarkupMatcher('Create "new-metric"'))[1]
-    );
-
-    expect(screen.getByText(/You want to track a custom attribute/)).toBeInTheDocument();
-
-    await selectEvent.select(screen.getByText('new-metric'), 'browser.name');
-
-    expect(
-      screen.queryByText(/You want to track a custom attribute/)
-    ).not.toBeInTheDocument();
-  });
-
-  it('When editing a metric and updating the form, an alert should prompt to remember it applies only for future data.', async function () {
-    const {project, organization} = initializeOrg();
-
-    renderMockRequests({orgSlug: organization.slug, projectId: project.id});
-
-    render(
-      <MetricsExtractionRuleForm
-        initialData={INITIAL_DATA}
-        projectId={project.id}
-        isEdit
-      />
-    );
-
-    await selectEvent.select(screen.getByText('none'), 'days');
-
-    expect(screen.getByText(/only be reflected on future data/)).toBeInTheDocument();
-  });
-
-  it('Do not allow duplicated filters', async function () {
-    const {project, organization} = initializeOrg();
-
-    renderMockRequests({orgSlug: organization.slug, projectId: project.id});
-
-    render(
-      <MetricsExtractionRuleForm
-        initialData={INITIAL_DATA}
-        projectId={project.id}
-        onSubmit={jest.fn()}
-      />
-    );
-
-    await selectEvent.select(screen.getByText('Select span attribute'), 'user.id');
-
-    await userEvent.click(screen.getByText('Add Filter'));
-    await userEvent.click(screen.getByText('Save Changes'));
-    expect(screen.getByText(/duplicates are not allowed/)).toBeInTheDocument();
-
-    await userEvent.type(
-      screen.getAllByPlaceholderText('Add span attributes')[0],
-      'environment:prod{enter}'
-    );
-    await userEvent.type(
-      screen.getAllByPlaceholderText('Add span attributes')[1],
-      'environment:prod{enter}'
-    );
-    await userEvent.click(screen.getByText('Save Changes'));
-    expect(screen.getByText(/duplicates are not allowed/)).toBeInTheDocument();
-
-    await userEvent.type(
-      screen.getAllByPlaceholderText('Add span attributes')[1],
-      'environment:dev{enter}'
-    );
-    expect(screen.queryByText(/duplicates are not allowed/)).not.toBeInTheDocument();
-  });
-});

Some files were not shown because too many files changed in this diff