Browse Source

ref(metrics): Remove virtual metrics provider (#76797)

Remove `<VirtualMetricsProvider />`
Remove virtual metrics handling from `<QueryBuilder />`
ArthurKnaus 6 months ago
parent
commit
7f3bf23bb6

+ 0 - 154
static/app/components/metrics/metricQuerySelect.spec.tsx

@@ -1,154 +0,0 @@
-import type React from 'react';
-
-import {initializeOrg} from 'sentry-test/initializeOrg';
-import {render, screen} from 'sentry-test/reactTestingLibrary';
-
-import {MetricQuerySelect} from 'sentry/components/metrics/metricQuerySelect';
-import type {PageFilters} from 'sentry/types/core';
-import type {MetricsQueryApiResponse} from 'sentry/types/metrics';
-import {
-  useVirtualMetricsContext,
-  VirtualMetricsContextProvider,
-} from 'sentry/utils/metrics/virtualMetricsContext';
-import importedUsePageFilters from 'sentry/utils/usePageFilters';
-
-jest.mock('sentry/utils/usePageFilters');
-
-const usePageFilters = jest.mocked(importedUsePageFilters);
-
-const makeFilterProps = (
-  filters: Partial<PageFilters>
-): ReturnType<typeof importedUsePageFilters> => {
-  return {
-    isReady: true,
-    shouldPersist: true,
-    desyncedFilters: new Set(),
-    pinnedFilters: new Set(),
-    selection: {
-      projects: [1],
-      environments: ['prod'],
-      datetime: {start: new Date(), end: new Date(), period: '14d', utc: true},
-      ...filters,
-    },
-  };
-};
-
-const SELECTED_MRI = 'c:custom/span_attribute_66@none';
-
-function MetricQuerySelectWithMRI(
-  props: Omit<React.ComponentProps<typeof MetricQuerySelect>, 'mri'>
-) {
-  const {getVirtualMRI} = useVirtualMetricsContext();
-  const mri = getVirtualMRI(SELECTED_MRI);
-
-  if (!mri) {
-    return null;
-  }
-
-  return <MetricQuerySelect {...props} mri={mri} />;
-}
-
-function renderMockRequests({
-  orgSlug,
-  projectId,
-  metricsQueryApiResponse,
-}: {
-  orgSlug: string;
-  projectId: string;
-  metricsQueryApiResponse?: Partial<MetricsQueryApiResponse>;
-}) {
-  MockApiClient.addMockResponse({
-    url: `/organizations/${orgSlug}/metrics/query/`,
-    method: 'POST',
-    body: metricsQueryApiResponse ?? {
-      data: [
-        [
-          {
-            by: {
-              mri: SELECTED_MRI,
-            },
-            totals: 2703.0,
-          },
-        ],
-      ],
-      start: '2024-07-16T21:00:00Z',
-      end: '2024-07-17T22:00:00Z',
-    },
-  });
-  MockApiClient.addMockResponse({
-    url: `/organizations/${orgSlug}/metrics/extraction-rules/`,
-    method: 'GET',
-    body: [
-      {
-        spanAttribute: 'span.duration',
-        aggregates: ['count'],
-        unit: 'millisecond',
-        tags: ['browser.name'],
-        conditions: [
-          {
-            id: 66,
-            value: '',
-            mris: ['c:custom/span_attribute_66@none'],
-          },
-        ],
-        projectId,
-        createdById: 3242858,
-        dateAdded: '2024-07-17T07:06:33.253094Z',
-        dateUpdated: '2024-07-17T21:27:54.742586Z',
-      },
-    ],
-  });
-  MockApiClient.addMockResponse({
-    url: `/organizations/${orgSlug}/metrics/meta/`,
-    method: 'GET',
-    body: [],
-  });
-}
-
-describe('Metric Query Select', function () {
-  const {project, organization} = initializeOrg();
-
-  it('shall display cardinality limit warning', async function () {
-    renderMockRequests({orgSlug: organization.slug, projectId: project.id});
-
-    usePageFilters.mockImplementation(() =>
-      makeFilterProps({projects: [Number(project.id)]})
-    );
-
-    render(
-      <VirtualMetricsContextProvider>
-        <MetricQuerySelectWithMRI onChange={jest.fn()} conditionId={66} />
-      </VirtualMetricsContextProvider>
-    );
-
-    expect(
-      await screen.findByLabelText('Exceeding the cardinality limit warning')
-    ).toBeInTheDocument();
-  });
-
-  it('shall NOT display cardinality limit warning', async function () {
-    renderMockRequests({
-      orgSlug: organization.slug,
-      projectId: project.id,
-      metricsQueryApiResponse: {
-        data: [],
-      },
-    });
-
-    usePageFilters.mockImplementation(() =>
-      makeFilterProps({projects: [Number(project.id)]})
-    );
-
-    render(
-      <VirtualMetricsContextProvider>
-        <MetricQuerySelectWithMRI onChange={jest.fn()} conditionId={66} />
-      </VirtualMetricsContextProvider>
-    );
-
-    expect(await screen.findByText(/query/i)).toBeInTheDocument();
-
-    expect(
-      screen.queryByLabelText('Exceeding the cardinality limit warning')
-    ).not.toBeInTheDocument();
-  });
-});

+ 0 - 278
static/app/components/metrics/metricQuerySelect.tsx

@@ -1,278 +0,0 @@
-import {useEffect, useMemo} from 'react';
-import {css} from '@emotion/react';
-import styled from '@emotion/styled';
-
-import {
-  CompactSelect,
-  type SelectOption,
-  type SelectOptionOrSection,
-  type SelectSection,
-} from 'sentry/components/compactSelect';
-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 {IconProject, IconWarning} from 'sentry/icons';
-import {t} from 'sentry/locale';
-import {space} from 'sentry/styles/space';
-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';
-import {useVirtualMetricsContext} from 'sentry/utils/metrics/virtualMetricsContext';
-import useOrganization from 'sentry/utils/useOrganization';
-import usePageFilters from 'sentry/utils/usePageFilters';
-import useProjects from 'sentry/utils/useProjects';
-
-interface Props {
-  mri: MRI;
-  onChange: (conditionId: number) => void;
-  conditionId?: number;
-}
-
-export function MetricQuerySelect({onChange, conditionId, mri}: Props) {
-  const organization = useOrganization();
-  const pageFilters = usePageFilters();
-  const {getConditions, getExtractionRule} = useVirtualMetricsContext();
-  const {data: cardinality} = useCardinalityLimitedMetricVolume(pageFilters.selection);
-
-  const {projects} = useProjects();
-
-  const isCardinalityLimited = (condition?: MetricsExtractionCondition): boolean => {
-    if (!cardinality || !condition) {
-      return false;
-    }
-    return condition.mris.some(conditionMri => cardinality[conditionMri] > 0);
-  };
-
-  const spanConditions = getConditions(mri);
-  const selectedCondition = spanConditions.find(c => c.id === conditionId);
-  const hasBuiltInCondition = spanConditions.some(c => c.id === BUILT_IN_CONDITION_ID);
-
-  const hasMultipleProjects =
-    pageFilters.selection.projects.length > 1 ||
-    pageFilters.selection.projects[0] === -1 ||
-    pageFilters.selection.projects.length === 0;
-
-  // If the selected condition cannot be found, select the first condition
-  useEffect(() => {
-    if (!selectedCondition && spanConditions.length > 0) {
-      onChange(spanConditions[0].id);
-    }
-  }, [onChange, selectedCondition, spanConditions]);
-
-  const options: SelectOptionOrSection<number>[] = useMemo(() => {
-    let builtInOption: SelectOption<number> | null = null;
-    const sectionMap = new Map<number, SelectOption<number>[]>();
-
-    spanConditions
-      .toSorted((a, b) => a.value.localeCompare(b.value))
-      .forEach(condition => {
-        const projectId = getExtractionRule(mri, condition.id)?.projectId!;
-        if (condition.id === BUILT_IN_CONDITION_ID) {
-          builtInOption = {
-            label: t('All Spans'),
-            value: condition.id,
-          };
-          return;
-        }
-        const section = sectionMap.get(projectId) ?? [];
-        section.push({
-          label: condition.value ? (
-            <FormattedCondition condition={condition} />
-          ) : (
-            t('All Spans')
-          ),
-          textValue: condition.value || t('All Spans'),
-          value: condition.id,
-        });
-        sectionMap.set(projectId, section);
-      });
-
-    const sections: SelectSection<number>[] = Array.from(sectionMap.entries())
-      .map(([projectId, sectionOption]) => {
-        const project = projects.find(p => p.id === String(projectId));
-        return {
-          options: sectionOption,
-          sortKey: project?.slug ?? '',
-          label: <ProjectBadge project={project!} avatarSize={12} disableLink />,
-        };
-      })
-      .toSorted((a, b) => a.sortKey.localeCompare(b.sortKey));
-
-    const allSections = builtInOption
-      ? [
-          {
-            label: (
-              <span
-                style={{
-                  display: 'inline-flex',
-                  gap: space(0.5),
-                }}
-              >
-                <IconProject key="generic-project" size="xs" /> {t('Built-in')}
-              </span>
-            ),
-            options: [builtInOption],
-          },
-          ...sections,
-        ]
-      : sections;
-
-    return allSections.length === 1 ? allSections[0].options : allSections;
-  }, [getExtractionRule, mri, projects, spanConditions]);
-
-  const istMetricQueryCardinalityLimited = isCardinalityLimited(
-    spanConditions.find(c => c.id === conditionId)
-  );
-
-  let leadingIcon: React.ReactNode = null;
-  if (conditionId && (hasMultipleProjects || hasBuiltInCondition)) {
-    const project = projects.find(
-      p => p.id === String(getExtractionRule(mri, conditionId)?.projectId)
-    );
-
-    if (conditionId === BUILT_IN_CONDITION_ID) {
-      leadingIcon = <IconProject key="generic-project" size="xs" />;
-    } else if (project) {
-      leadingIcon = (
-        <ProjectBadge
-          project={project}
-          key={project.slug}
-          avatarSize={12}
-          disableLink
-          hideName
-        />
-      );
-    }
-  }
-
-  if (istMetricQueryCardinalityLimited) {
-    leadingIcon = <CardinalityWarningIcon />;
-  }
-
-  if (hasMetricsNewInputs(organization)) {
-    return (
-      <QueryFieldGroup.CompactSelect
-        size="md"
-        triggerLabel={
-          selectedCondition?.value ? (
-            <FormattedCondition condition={selectedCondition} />
-          ) : (
-            t('All Spans')
-          )
-        }
-        triggerProps={{
-          icon: leadingIcon,
-        }}
-        searchable
-        options={options}
-        value={conditionId}
-        onChange={({value}) => {
-          onChange(value);
-        }}
-        css={css`
-          && {
-            width: auto;
-            min-width: auto;
-            & > button {
-              min-height: 100%;
-            }
-          }
-        `}
-      />
-    );
-  }
-
-  return (
-    <CompactSelect
-      size="md"
-      triggerProps={{
-        prefix: t('Query'),
-        icon: istMetricQueryCardinalityLimited ? <CardinalityWarningIcon /> : null,
-      }}
-      options={options}
-      value={conditionId}
-      onChange={({value}) => {
-        onChange(value);
-      }}
-    />
-  );
-}
-
-export function CardinalityWarningIcon() {
-  return (
-    <Tooltip
-      isHoverable
-      title={t(
-        "This query is exeeding the cardinality limit. Remove tags or add more filters in the metric's settings to receive accurate data."
-      )}
-      skipWrapper
-    >
-      <IconWarning
-        size="xs"
-        color="yellow300"
-        role="image"
-        aria-label={t('Exceeding the cardinality limit warning')}
-      />
-    </Tooltip>
-  );
-}
-
-function parseConditionValue(condition?: MetricsExtractionCondition) {
-  if (condition?.value) {
-    try {
-      return parseSearch(condition.value);
-    } catch {
-      // Ignore
-    }
-  }
-  return null;
-}
-
-function FormattedCondition({condition}: {condition?: MetricsExtractionCondition}) {
-  const parsed = useMemo(() => parseConditionValue(condition), [condition]);
-
-  if (!parsed) {
-    return condition?.value;
-  }
-
-  return (
-    <Tooltip
-      overlayStyle={{maxWidth: '80vw'}}
-      delay={500}
-      title={
-        <CompleteHighlight>
-          <HighlightQuery parsedQuery={parsed} />
-        </CompleteHighlight>
-      }
-    >
-      <Highlight>
-        <HighlightQuery parsedQuery={parsed} />
-      </Highlight>
-    </Tooltip>
-  );
-}
-
-const Highlight = styled('span')`
-  padding: ${space(0.5)} ${space(0.25)};
-  overflow: hidden;
-  font-size: ${p => p.theme.fontSizeSmall};
-  gap: ${space(1)};
-  color: ${p => p.theme.textColor};
-  display: flex;
-  white-space: nowrap;
-  font-family: ${p => p.theme.text.familyMono};
-  font-weight: 400;
-  max-width: 350px;
-  & span {
-    margin: 0;
-  }
-`;
-
-const CompleteHighlight = styled(Highlight)`
-  display: flex;
-  flex-wrap: wrap;
-  max-width: unset;
-`;

+ 28 - 145
static/app/components/metrics/queryBuilder.tsx

@@ -7,7 +7,6 @@ import uniqBy from 'lodash/uniqBy';
 import GuideAnchor from 'sentry/components/assistant/guideAnchor';
 import type {SelectOption} from 'sentry/components/compactSelect';
 import {CompactSelect} from 'sentry/components/compactSelect';
-import {MetricQuerySelect} from 'sentry/components/metrics/metricQuerySelect';
 import {
   MetricSearchBar,
   type MetricSearchBarProps,
@@ -19,19 +18,13 @@ import {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import type {MRI} from 'sentry/types/metrics';
 import {trackAnalytics} from 'sentry/utils/analytics';
-import {
-  getDefaultAggregation,
-  isAllowedAggregation,
-  isVirtualMetric,
-} from 'sentry/utils/metrics';
-import {BUILT_IN_CONDITION_ID} from 'sentry/utils/metrics/extractionRules';
+import {getDefaultAggregation, isAllowedAggregation} from 'sentry/utils/metrics';
 import {hasMetricsNewInputs} from 'sentry/utils/metrics/features';
 import {parseMRI} from 'sentry/utils/metrics/mri';
 import type {MetricsQuery} from 'sentry/utils/metrics/types';
 import {useIncrementQueryMetric} from 'sentry/utils/metrics/useIncrementQueryMetric';
 import {useVirtualizedMetricsMeta} from 'sentry/utils/metrics/useMetricsMeta';
 import {useMetricsTags} from 'sentry/utils/metrics/useMetricsTags';
-import {useVirtualMetricsContext} from 'sentry/utils/metrics/virtualMetricsContext';
 import theme from 'sentry/utils/theme';
 import useOrganization from 'sentry/utils/useOrganization';
 import usePageFilters from 'sentry/utils/usePageFilters';
@@ -59,8 +52,6 @@ export const QueryBuilder = memo(function QueryBuilder({
 }: QueryBuilderProps) {
   const organization = useOrganization();
   const pageFilters = usePageFilters();
-  const {getAggregations, getConditions, resolveVirtualMRI, getTags} =
-    useVirtualMetricsContext();
 
   const {
     data: meta,
@@ -69,20 +60,12 @@ export const QueryBuilder = memo(function QueryBuilder({
     refetch: refetchMeta,
   } = useVirtualizedMetricsMeta(pageFilters.selection);
 
-  const resolvedMRI = useMemo(() => {
-    if (!isVirtualMetric(metricsQuery) || !metricsQuery.condition) {
-      return metricsQuery.mri;
+  const {data: tagsData = [], isPending: tagsIsLoading} = useMetricsTags(
+    metricsQuery.mri,
+    {
+      projects: projectIds,
     }
-    return resolveVirtualMRI(
-      metricsQuery.mri,
-      metricsQuery.condition,
-      metricsQuery.aggregation
-    ).mri;
-  }, [metricsQuery, resolveVirtualMRI]);
-
-  const {data: tagsData = [], isPending: tagsIsLoading} = useMetricsTags(resolvedMRI, {
-    projects: projectIds,
-  });
+  );
 
   const groupByOptions = useMemo(() => {
     const options = uniqBy(tagsData, 'key').map(tag => ({
@@ -94,42 +77,16 @@ export const QueryBuilder = memo(function QueryBuilder({
       isQueryable: true, // allow group by this tag
     }));
 
-    if (
-      isVirtualMetric(metricsQuery) &&
-      metricsQuery.condition &&
-      metricsQuery.condition !== BUILT_IN_CONDITION_ID
-    ) {
-      const tagsFromExtractionRules = getTags(metricsQuery.mri, metricsQuery.condition);
-      for (const tag of tagsFromExtractionRules) {
-        if (!options.find(o => o.key === tag.key)) {
-          // if the tag has not been seen in the selected time range
-          // but exists in the extraction rule, it should be disabled in the group by dropdown
-          options.push({
-            key: tag.key,
-            trailingItems: metricsQuery.query?.includes(`${tag.key}:`) ? (
-              <TagWarningIcon />
-            ) : undefined,
-            isQueryable: false, // do not allow group by this tag
-          });
-        }
-      }
-    }
     return options;
-  }, [tagsData, metricsQuery, getTags]);
+  }, [tagsData, metricsQuery]);
 
   const selectedMeta = useMemo(() => {
     return meta.find(metric => metric.mri === metricsQuery.mri);
   }, [meta, metricsQuery.mri]);
 
   const metricAggregates = useMemo(() => {
-    if (!isVirtualMetric(metricsQuery) || !metricsQuery.condition) {
-      return selectedMeta?.operations.filter(isAllowedAggregation) ?? [];
-    }
-
-    return getAggregations(metricsQuery.mri, metricsQuery.condition).filter(
-      isAllowedAggregation
-    );
-  }, [getAggregations, metricsQuery, selectedMeta?.operations]);
+    return selectedMeta?.operations.filter(isAllowedAggregation) ?? [];
+  }, [selectedMeta?.operations]);
 
   const incrementQueryMetric = useIncrementQueryMetric({
     ...metricsQuery,
@@ -155,27 +112,13 @@ export const QueryBuilder = memo(function QueryBuilder({
       }
 
       // If it is a virtual MRI we need to check for the new conditions and aggregations
-      if (isVirtualMetric({mri: mriValue})) {
-        const spanConditions = getConditions(mriValue);
-        queryChanges.condition = spanConditions[0]?.id;
-        const aggegates = getAggregations(mriValue, queryChanges.condition);
-        queryChanges.aggregation = aggegates[0];
-      } else {
-        queryChanges.condition = undefined;
-      }
+      queryChanges.condition = undefined;
 
       trackAnalytics('ddm.widget.metric', {organization});
       incrementQueryMetric('ddm.widget.metric', queryChanges);
       onChange(queryChanges);
     },
-    [
-      getAggregations,
-      getConditions,
-      incrementQueryMetric,
-      metricsQuery.mri,
-      onChange,
-      organization,
-    ]
+    [incrementQueryMetric, metricsQuery.mri, onChange, organization]
   );
 
   const handleOpChange = useCallback(
@@ -214,26 +157,6 @@ export const QueryBuilder = memo(function QueryBuilder({
     [incrementQueryMetric, onChange, organization]
   );
 
-  const handleConditionChange = useCallback(
-    (conditionId: number) => {
-      trackAnalytics('ddm.widget.condition', {organization});
-      const newAggregates = getAggregations(metricsQuery.mri, conditionId);
-
-      const changes: Partial<MetricsQuery> = {
-        condition: conditionId,
-        // Changing the query may change the available tags
-        groupBy: undefined,
-      };
-
-      if (!newAggregates.includes(metricsQuery.aggregation)) {
-        changes.aggregation = newAggregates[0];
-      }
-
-      onChange(changes);
-    },
-    [getAggregations, metricsQuery.aggregation, metricsQuery.mri, onChange, organization]
-  );
-
   const handleMetricTagClick = useCallback(
     (mri: MRI, tag: string) => {
       onChange({mri, groupBy: [tag]});
@@ -350,49 +273,22 @@ export const QueryBuilder = memo(function QueryBuilder({
             position="bottom"
             disabled={index !== 0}
           >
-            {selectedMeta && isVirtualMetric(selectedMeta) ? (
-              <QueryFieldGroup>
-                <QueryFieldGroup.Label css={fixedWidthLabelCss}>
-                  {t('Where')}
-                </QueryFieldGroup.Label>
-                <MetricQuerySelect
-                  mri={metricsQuery.mri}
-                  conditionId={metricsQuery.condition}
-                  onChange={handleConditionChange}
-                />
-                <QueryFieldGroup.Label css={autoWidthLabelCss}>
-                  {t('And')}
-                </QueryFieldGroup.Label>
-                <SearchBar
-                  hasMetricsNewInputs
-                  mri={resolvedMRI}
-                  disabled={!metricsQuery.mri}
-                  onChange={handleQueryChange}
-                  query={metricsQuery.query}
-                  projectIds={projectIdStrings}
-                  blockedTags={
-                    selectedMeta?.blockingStatus?.flatMap(s => s.blockedTags) ?? []
-                  }
-                />
-              </QueryFieldGroup>
-            ) : (
-              <QueryFieldGroup>
-                <QueryFieldGroup.Label css={fixedWidthLabelCss}>
-                  {t('Where')}
-                </QueryFieldGroup.Label>
-                <SearchBar
-                  hasMetricsNewInputs
-                  mri={resolvedMRI}
-                  disabled={!metricsQuery.mri}
-                  onChange={handleQueryChange}
-                  query={metricsQuery.query}
-                  projectIds={projectIdStrings}
-                  blockedTags={
-                    selectedMeta?.blockingStatus?.flatMap(s => s.blockedTags) ?? []
-                  }
-                />
-              </QueryFieldGroup>
-            )}
+            <QueryFieldGroup>
+              <QueryFieldGroup.Label css={fixedWidthLabelCss}>
+                {t('Where')}
+              </QueryFieldGroup.Label>
+              <SearchBar
+                hasMetricsNewInputs
+                mri={metricsQuery.mri}
+                disabled={!metricsQuery.mri}
+                onChange={handleQueryChange}
+                query={metricsQuery.query}
+                projectIds={projectIdStrings}
+                blockedTags={
+                  selectedMeta?.blockingStatus?.flatMap(s => s.blockedTags) ?? []
+                }
+              />
+            </QueryFieldGroup>
           </FullWidthGuideAnchor>
         </FilterBy>
         {alias}
@@ -415,13 +311,6 @@ export const QueryBuilder = memo(function QueryBuilder({
               value={metricsQuery.mri}
             />
           </GuideAnchor>
-          {selectedMeta && isVirtualMetric(selectedMeta) ? (
-            <MetricQuerySelect
-              mri={metricsQuery.mri}
-              conditionId={metricsQuery.condition}
-              onChange={handleConditionChange}
-            />
-          ) : null}
         </FlexBlock>
         <FlexBlock>
           <GuideAnchor
@@ -474,7 +363,7 @@ export const QueryBuilder = memo(function QueryBuilder({
         </FlexBlock>
       </FlexBlock>
       <SearchBar
-        mri={resolvedMRI}
+        mri={metricsQuery.mri}
         disabled={!metricsQuery.mri}
         onChange={handleQueryChange}
         query={metricsQuery.query}
@@ -588,12 +477,6 @@ const FilterBy = styled('div')<{hasSymbols: boolean; isModal?: boolean}>`
     `}
 `;
 
-const autoWidthLabelCss = css`
-  width: auto;
-  min-width: auto;
-  white-space: nowrap;
-`;
-
 const fixedWidthLabelCss = css`
   width: 95px;
   min-width: 95px;

+ 0 - 52
static/app/utils/metrics/virtualMetricsContext.spec.tsx

@@ -1,52 +0,0 @@
-import type {MetricsExtractionRule} from 'sentry/types/metrics';
-import {createMRIToVirtualMap} from 'sentry/utils/metrics/virtualMetricsContext';
-
-describe('createMRIToVirtualMap', () => {
-  it('creates a mapping', () => {
-    const rules = [
-      {
-        spanAttribute: 'span1',
-        projectId: 1,
-        createdById: null,
-        dateAdded: '2021-09-29T20:00:00',
-        dateUpdated: '2021-09-29T20:00:00',
-        aggregates: [],
-        tags: [],
-        unit: 'none',
-        conditions: [
-          {
-            id: 1,
-            value: 'value',
-            mris: ['c:custom/mri1@none' as const, 'c:custom/mri2@none' as const],
-          },
-        ],
-      } satisfies MetricsExtractionRule,
-      {
-        spanAttribute: 'span2',
-        projectId: 2,
-        createdById: null,
-        dateAdded: '2021-09-29T20:00:00',
-        dateUpdated: '2021-09-29T20:00:00',
-        aggregates: [],
-        tags: [],
-        unit: 'millisecond',
-        conditions: [
-          {
-            id: 2,
-            value: 'value',
-            mris: ['c:custom/mri3@none' as const, 'c:custom/mri4@none' as const],
-          },
-        ],
-      } satisfies MetricsExtractionRule,
-    ];
-    const result = createMRIToVirtualMap(rules);
-    expect(result).toEqual(
-      new Map([
-        ['c:custom/mri1@none', 'v:custom/span1@none'],
-        ['c:custom/mri2@none', 'v:custom/span1@none'],
-        ['c:custom/mri3@none', 'v:custom/span2@none'],
-        ['c:custom/mri4@none', 'v:custom/span2@none'],
-      ])
-    );
-  });
-});

+ 2 - 308
static/app/utils/metrics/virtualMetricsContext.tsx

@@ -1,4 +1,4 @@
-import {createContext, useCallback, useContext, useMemo} from 'react';
+import {createContext, useContext} from 'react';
 
 import type {
   MetricAggregation,
@@ -7,16 +7,7 @@ import type {
   MetricsExtractionRule,
   MRI,
 } from 'sentry/types/metrics';
-import {
-  aggregationToMetricType,
-  BUILT_IN_CONDITION_ID,
-} from 'sentry/utils/metrics/extractionRules';
-import {DEFAULT_MRI, formatMRI, parseMRI} from 'sentry/utils/metrics/mri';
 import type {MetricTag} from 'sentry/utils/metrics/types';
-import {useMetricsMeta} from 'sentry/utils/metrics/useMetricsMeta';
-import {useApiQuery} from 'sentry/utils/queryClient';
-import useOrganization from 'sentry/utils/useOrganization';
-import usePageFilters from 'sentry/utils/usePageFilters';
 
 interface ContextType {
   getAggregations: (mri: MRI, conditionId: number) => MetricAggregation[];
@@ -61,304 +52,7 @@ const Context = createContext<ContextType>({
   isLoading: false,
 });
 
+// TODO: Remove this when all dependet code was cleaned up
 export function useVirtualMetricsContext() {
   return useContext(Context);
 }
-
-interface Props {
-  children: React.ReactNode;
-}
-
-export function createVirtualMRI(rule: MetricsExtractionRule): MRI {
-  return `v:custom/${rule.spanAttribute}@none`;
-}
-
-export function createMRIToVirtualMap(rules: MetricsExtractionRule[]): Map<MRI, MRI> {
-  const mriMap = new Map<MRI, MRI>();
-  for (const rule of rules) {
-    for (const condition of rule.conditions) {
-      for (const mri of condition.mris) {
-        mriMap.set(mri, createVirtualMRI(rule));
-      }
-    }
-  }
-  return mriMap;
-}
-
-const getMetricsExtractionRulesApiKey = (orgSlug: string, projects: number[]) =>
-  [
-    `/organizations/${orgSlug}/metrics/extraction-rules/`,
-    {
-      query: {
-        project: projects,
-      },
-    },
-  ] as const;
-
-const EMPTY_ARRAY: never[] = [];
-
-function stripBuiltInCondition(rule: MetricsExtractionRule): MetricsExtractionRule {
-  return {
-    ...rule,
-    conditions: rule.conditions.filter(
-      condition => condition.id !== BUILT_IN_CONDITION_ID
-    ),
-  };
-}
-
-export function VirtualMetricsContextProvider({children}: Props) {
-  const organization = useOrganization();
-  const {selection, isReady} = usePageFilters();
-
-  const extractionRulesQuery = useApiQuery<MetricsExtractionRule[]>(
-    getMetricsExtractionRulesApiKey(organization.slug, selection.projects),
-    {staleTime: 0, enabled: isReady}
-  );
-  const spanMetaQuery = useMetricsMeta(selection, ['spans']);
-
-  const extractionRules = extractionRulesQuery.data ?? EMPTY_ARRAY;
-  const isLoading = extractionRulesQuery.isPending || spanMetaQuery.isLoading;
-
-  const extractionRulesWithBuiltIn = useMemo(() => {
-    return extractionRules.map(rule => {
-      const matchingBuiltInMetric = spanMetaQuery.data.find(
-        meta => formatMRI(meta.mri) === rule.spanAttribute
-      );
-      if (!matchingBuiltInMetric) {
-        return rule;
-      }
-      return {
-        ...rule,
-        conditions: [
-          {
-            id: BUILT_IN_CONDITION_ID,
-            mris: [matchingBuiltInMetric.mri],
-            value: '',
-          },
-          ...rule.conditions,
-        ],
-      };
-    });
-  }, [extractionRules, spanMetaQuery.data]);
-
-  const mriToVirtualMap = useMemo(
-    () => createMRIToVirtualMap(extractionRulesWithBuiltIn),
-    [extractionRulesWithBuiltIn]
-  );
-
-  const virtualMRIToRuleMap = useMemo(() => {
-    const map = new Map<MRI, MetricsExtractionRule[]>();
-    extractionRulesWithBuiltIn.forEach(rule => {
-      const virtualMRI = createVirtualMRI(rule);
-      const existingRules = map.get(virtualMRI) || [];
-      map.set(virtualMRI, [...existingRules, rule]);
-    });
-    return map;
-  }, [extractionRulesWithBuiltIn]);
-
-  const getVirtualMRI = useCallback(
-    (mri: MRI): MRI | null => {
-      const virtualMRI = mriToVirtualMap.get(mri);
-      if (!virtualMRI) {
-        return null;
-      }
-      return virtualMRI;
-    },
-    [mriToVirtualMap]
-  );
-
-  const getVirtualMeta = useCallback(
-    (mri: MRI): MetricMeta => {
-      const rules = virtualMRIToRuleMap.get(mri);
-      if (!rules) {
-        throw new Error('Rules not found');
-      }
-
-      return {
-        type: 'v',
-        unit: 'none',
-        blockingStatus: [],
-        mri: mri,
-        operations: [],
-        projectIds: rules.map(rule => rule.projectId),
-      };
-    },
-    [virtualMRIToRuleMap]
-  );
-
-  const getConditions = useCallback(
-    (mri: MRI): MetricsExtractionCondition[] => {
-      const rules = virtualMRIToRuleMap.get(mri);
-
-      const conditions = rules?.flatMap(rule => rule.conditions) ?? [];
-
-      // Unique by id
-      return Array.from(
-        new Map(conditions.map(condition => [condition.id, condition])).values()
-      );
-    },
-    [virtualMRIToRuleMap]
-  );
-
-  const getCondition = useCallback(
-    (mri: MRI, conditionId: number) => {
-      const conditions = getConditions(mri);
-      return conditions.find(c => c.id === conditionId) || null;
-    },
-    [getConditions]
-  );
-
-  const getExtractionRules = useCallback(
-    (mri: MRI) => {
-      return virtualMRIToRuleMap.get(mri)?.map(stripBuiltInCondition) ?? [];
-    },
-    [virtualMRIToRuleMap]
-  );
-
-  const getExtractionRule = useCallback(
-    (mri: MRI, conditionId: number) => {
-      const rules = getExtractionRules(mri);
-
-      if (!rules) {
-        return null;
-      }
-
-      const rule = rules.find(r => r.conditions.some(c => c.id === conditionId));
-      if (!rule) {
-        return null;
-      }
-
-      return {
-        ...rule,
-        // return the original rule without the built-in condition
-        conditions: rule.conditions.filter(
-          condition => condition.id !== BUILT_IN_CONDITION_ID
-        ),
-      };
-    },
-    [getExtractionRules]
-  );
-
-  const getTags = useCallback(
-    (mri: MRI, conditionId: number): MetricTag[] => {
-      const rule = getExtractionRule(mri, conditionId);
-      return rule?.tags.map(tag => ({key: tag})) || [];
-    },
-    [getExtractionRule]
-  );
-
-  const resolveVirtualMRI = useCallback(
-    (
-      mri: MRI,
-      conditionId: number,
-      aggregation: MetricAggregation
-    ): {
-      aggregation: MetricAggregation;
-      mri: MRI;
-    } => {
-      const condition = getCondition(mri, conditionId);
-      if (!condition) {
-        return {mri: DEFAULT_MRI, aggregation: 'sum'};
-      }
-
-      if (conditionId === BUILT_IN_CONDITION_ID) {
-        // TODO: Do we need to check the aggregate?
-        return {mri: condition.mris[0], aggregation};
-      }
-
-      const metricType = aggregationToMetricType[aggregation];
-      let resolvedMRI = condition.mris.find(m => m.startsWith(metricType));
-      if (!resolvedMRI) {
-        resolvedMRI = mri;
-      }
-
-      return {mri: resolvedMRI, aggregation: metricType === 'c' ? 'sum' : aggregation};
-    },
-    [getCondition]
-  );
-
-  const getVirtualMRIQuery = useCallback(
-    (
-      mri: MRI,
-      aggregation: MetricAggregation
-    ): {
-      aggregation: MetricAggregation;
-      conditionId: number;
-      mri: MRI;
-    } | null => {
-      const virtualMRI = getVirtualMRI(mri);
-      if (!virtualMRI) {
-        return null;
-      }
-
-      const condition = getConditions(virtualMRI).find(c => c.mris.includes(mri));
-      if (!condition) {
-        return null;
-      }
-
-      return {
-        mri: virtualMRI,
-        conditionId: condition.id,
-        aggregation: parseMRI(mri).type === 'c' ? 'count' : aggregation,
-      };
-    },
-    [getVirtualMRI, getConditions]
-  );
-
-  const getAggregations = useCallback(
-    (mri: MRI, conditionId: number) => {
-      if (conditionId === BUILT_IN_CONDITION_ID) {
-        const condition = getCondition(mri, conditionId);
-        const builtInMeta = spanMetaQuery.data.find(
-          meta => meta.mri === condition?.mris[0]
-        );
-        return builtInMeta?.operations ?? [];
-      }
-
-      const rule = getExtractionRule(mri, conditionId);
-      if (!rule) {
-        return [];
-      }
-      return rule.aggregates;
-    },
-    [getCondition, getExtractionRule, spanMetaQuery.data]
-  );
-
-  const virtualMeta = useMemo(
-    () => Array.from(virtualMRIToRuleMap.keys()).map(getVirtualMeta),
-    [getVirtualMeta, virtualMRIToRuleMap]
-  );
-
-  const contextValue = useMemo<ContextType>(
-    () => ({
-      getVirtualMRI,
-      getVirtualMeta,
-      getConditions,
-      getCondition,
-      getAggregations,
-      getExtractionRule,
-      getExtractionRules,
-      getTags,
-      getVirtualMRIQuery,
-      resolveVirtualMRI,
-      virtualMeta,
-      isLoading,
-    }),
-    [
-      getVirtualMRI,
-      getVirtualMeta,
-      getConditions,
-      getCondition,
-      getAggregations,
-      getExtractionRule,
-      getExtractionRules,
-      getTags,
-      getVirtualMRIQuery,
-      resolveVirtualMRI,
-      virtualMeta,
-      isLoading,
-    ]
-  );
-
-  return <Context.Provider value={contextValue}>{children}</Context.Provider>;
-}

+ 2 - 7
static/app/views/metrics/metricScratchpad.spec.tsx

@@ -3,7 +3,6 @@ import {render, screen, waitForElementToBeRemoved} from 'sentry-test/reactTestin
 
 import type {PageFilters} from 'sentry/types/core';
 import type {MetricsQueryApiResponse} from 'sentry/types/metrics';
-import {VirtualMetricsContextProvider} from 'sentry/utils/metrics/virtualMetricsContext';
 import importedUsePageFilters from 'sentry/utils/usePageFilters';
 import {MetricsContextProvider} from 'sentry/views/metrics/context';
 import {MetricScratchpad} from 'sentry/views/metrics/scratchpad';
@@ -136,9 +135,7 @@ describe('metric Scratchpad', function () {
 
     render(
       <MetricsContextProvider>
-        <VirtualMetricsContextProvider>
-          <MetricScratchpad />
-        </VirtualMetricsContextProvider>
+        <MetricScratchpad />
       </MetricsContextProvider>
     );
 
@@ -165,9 +162,7 @@ describe('metric Scratchpad', function () {
 
     render(
       <MetricsContextProvider>
-        <VirtualMetricsContextProvider>
-          <MetricScratchpad />
-        </VirtualMetricsContextProvider>
+        <MetricScratchpad />
       </MetricsContextProvider>
     );
 

+ 0 - 44
static/app/views/settings/projectMetrics/utils/useMetricsExtractionRules.tsx

@@ -1,44 +0,0 @@
-import type {MetricsExtractionRule} from 'sentry/types/metrics';
-import {useApiQuery, type UseApiQueryOptions} from 'sentry/utils/queryClient';
-
-interface MetricRulesAPIQueryParams {
-  query?: string;
-}
-
-export const getMetricsExtractionRulesApiKey = (
-  orgId: string | number,
-  projectId?: string | number,
-  query?: MetricRulesAPIQueryParams
-) => {
-  const endpoint = `/projects/${orgId}/${projectId}/metrics/extraction-rules/`;
-
-  if (!query || Object.keys(query).length === 0) {
-    // when no query is provided, return only endpoint path as a key
-    return [endpoint] as const;
-  }
-  return [endpoint, {query: query}] as const;
-};
-
-export const getMetricsExtractionOrgApiKey = (orgSlug: string) =>
-  [`/organizations/${orgSlug}/metrics/extraction-rules/`] as const;
-
-interface GetParams {
-  orgId: string | number;
-  projectId?: string | number;
-  query?: MetricRulesAPIQueryParams;
-}
-
-export function useMetricsExtractionRules(
-  {orgId, projectId, query}: GetParams,
-  options: Partial<UseApiQueryOptions<MetricsExtractionRule[]>> = {}
-) {
-  return useApiQuery<MetricsExtractionRule[]>(
-    getMetricsExtractionRulesApiKey(orgId, projectId, query),
-    {
-      staleTime: 0,
-      retry: false,
-      ...options,
-      enabled: !!projectId && options.enabled,
-    }
-  );
-}