Browse Source

feat(metrics-extraction): Change extraction condition to object (#73419)

Change the extraction conditions from strings to objects.
ArthurKnaus 3 days ago
parent
commit
8268d1fe1d

+ 14 - 0
static/app/types/metrics.tsx

@@ -116,3 +116,17 @@ export type BlockingStatus = {
 };
 
 export type MetricsMetaCollection = Record<string, MetricMeta>;
+
+export interface MetricsExtractionCondition {
+  id: number;
+  mris: MRI[];
+  query: string;
+}
+
+export interface MetricsExtractionRule {
+  aggregates: MetricsAggregate[];
+  conditions: MetricsExtractionCondition[];
+  spanAttribute: string;
+  tags: string[];
+  unit: string;
+}

+ 5 - 6
static/app/views/settings/projectMetrics/extractMetric.tsx

@@ -6,6 +6,7 @@ import Panel from 'sentry/components/panels/panel';
 import PanelBody from 'sentry/components/panels/panelBody';
 import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
 import {t} from 'sentry/locale';
+import type {MetricsExtractionRule} from 'sentry/types/metrics';
 import type {Project} from 'sentry/types/project';
 import {hasCustomMetricsExtractionRules} from 'sentry/utils/metrics/features';
 import routeTitleGen from 'sentry/utils/routeTitle';
@@ -14,20 +15,18 @@ import useOrganization from 'sentry/utils/useOrganization';
 import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
 import TextBlock from 'sentry/views/settings/components/text/textBlock';
 import {
+  createCondition,
   explodeAggregateGroup,
   type FormData,
   MetricsExtractionRuleForm,
 } from 'sentry/views/settings/projectMetrics/metricsExtractionRuleForm';
-import {
-  type MetricsExtractionRule,
-  useCreateMetricsExtractionRules,
-} from 'sentry/views/settings/projectMetrics/utils/api';
+import {useCreateMetricsExtractionRules} from 'sentry/views/settings/projectMetrics/utils/api';
 
 const INITIAL_DATA: FormData = {
   spanAttribute: null,
   aggregates: ['count'],
   tags: ['release', 'environment'],
-  conditions: [''],
+  conditions: [createCondition()],
 };
 
 const PAGE_TITLE = t('Configure Metric');
@@ -53,7 +52,7 @@ function ExtractMetric({project}: {project: Project}) {
         tags: data.tags,
         aggregates: data.aggregates.flatMap(explodeAggregateGroup),
         unit: 'none',
-        conditions: data.conditions.filter(Boolean),
+        conditions: data.conditions,
       };
 
       createExtractionRuleMutation.mutate(

+ 5 - 6
static/app/views/settings/projectMetrics/metricsExtractionRuleEditModal.tsx

@@ -4,18 +4,17 @@ import {css} from '@emotion/react';
 import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
 import type {ModalRenderProps} from 'sentry/actionCreators/modal';
 import {t} from 'sentry/locale';
+import type {MetricsExtractionRule} from 'sentry/types/metrics';
 import type {Project} from 'sentry/types/project';
 import useOrganization from 'sentry/utils/useOrganization';
 import {
   aggregatesToGroups,
+  createCondition as createExtractionCondition,
   explodeAggregateGroup,
   type FormData,
   MetricsExtractionRuleForm,
 } from 'sentry/views/settings/projectMetrics/metricsExtractionRuleForm';
-import {
-  type MetricsExtractionRule,
-  useUpdateMetricsExtractionRules,
-} from 'sentry/views/settings/projectMetrics/utils/api';
+import {useUpdateMetricsExtractionRules} from 'sentry/views/settings/projectMetrics/utils/api';
 
 interface Props extends ModalRenderProps {
   metricExtractionRule: MetricsExtractionRule;
@@ -43,7 +42,7 @@ export function MetricsExtractionRuleEditModal({
       tags: metricExtractionRule.tags,
       conditions: metricExtractionRule.conditions.length
         ? metricExtractionRule.conditions
-        : [''],
+        : [createExtractionCondition()],
     };
   }, [metricExtractionRule]);
 
@@ -58,7 +57,7 @@ export function MetricsExtractionRuleEditModal({
         tags: data.tags,
         aggregates: data.aggregates.flatMap(explodeAggregateGroup),
         unit: 'none',
-        conditions: data.conditions.filter(Boolean),
+        conditions: data.conditions,
       };
 
       updateExtractionRuleMutation.mutate(

+ 40 - 10
static/app/views/settings/projectMetrics/metricsExtractionRuleForm.tsx

@@ -11,7 +11,7 @@ import ExternalLink from 'sentry/components/links/externalLink';
 import {IconAdd, IconClose} from 'sentry/icons';
 import {t, tct} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
-import type {MetricsAggregate} from 'sentry/types/metrics';
+import type {MetricsAggregate, MetricsExtractionCondition} from 'sentry/types/metrics';
 import type {Project} from 'sentry/types/project';
 import {DiscoverDatasets} from 'sentry/utils/discover/types';
 import useOrganization from 'sentry/utils/useOrganization';
@@ -20,7 +20,7 @@ import {useSpanFieldSupportedTags} from 'sentry/views/performance/utils/useSpanF
 export type AggregateGroup = 'count' | 'count_unique' | 'min_max' | 'percentiles';
 export interface FormData {
   aggregates: AggregateGroup[];
-  conditions: string[];
+  conditions: MetricsExtractionCondition[];
   spanAttribute: string | null;
   tags: string[];
 }
@@ -92,6 +92,22 @@ export function aggregatesToGroups(aggregates: MetricsAggregate[]): AggregateGro
   }
   return groups;
 }
+
+let currentTempId = 0;
+function createTempId(): number {
+  currentTempId -= 1;
+  return currentTempId;
+}
+
+export function createCondition(): MetricsExtractionCondition {
+  return {
+    query: '',
+    // id and mris will be set by the backend after creation
+    id: createTempId(),
+    mris: [],
+  };
+}
+
 const EMPTY_SET = new Set<never>();
 const SPAN_SEARCH_CONFIG = {
   booleanKeys: EMPTY_SET,
@@ -218,20 +234,33 @@ export function MetricsExtractionRuleForm({isEdit, project, onSubmit, ...props}:
             flexibleControlStateSize
           >
             {({onChange, initialData, value}) => {
-              const conditions = (value || initialData) as string[];
+              const conditions = (value ||
+                initialData ||
+                []) as MetricsExtractionCondition[];
+
+              const handleChange = (queryString: string, index: number) => {
+                onChange(
+                  conditions.toSpliced(index, 1, {
+                    ...conditions[index],
+                    query: queryString,
+                  }),
+                  {}
+                );
+              };
+
               return (
                 <Fragment>
                   <ConditionsWrapper hasDelete={value.length > 1}>
-                    {conditions.map((query, index) => (
-                      <Fragment key={index}>
+                    {conditions.map((condition, index) => (
+                      <Fragment key={condition.id}>
                         <SearchWrapper hasPrefix={index !== 0}>
                           {index !== 0 && <ConditionLetter>{t('or')}</ConditionLetter>}
                           <SearchBar
                             {...SPAN_SEARCH_CONFIG}
                             searchSource="metrics-extraction"
-                            query={query}
+                            query={condition.query}
                             onSearch={(queryString: string) =>
-                              onChange(conditions.toSpliced(index, 1, queryString), {})
+                              handleChange(queryString, index)
                             }
                             placeholder={t('Search for span attributes')}
                             organization={organization}
@@ -241,8 +270,10 @@ export function MetricsExtractionRuleForm({isEdit, project, onSubmit, ...props}:
                             projectIds={[parseInt(project.id, 10)]}
                             hasRecentSearches={false}
                             onBlur={(queryString: string) =>
-                              onChange(conditions.toSpliced(index, 1, queryString), {})
+                              handleChange(queryString, index)
                             }
+                            savedSearchType={undefined}
+                            useFormWrapper={false}
                           />
                         </SearchWrapper>
                         {value.length > 1 && (
@@ -257,8 +288,7 @@ export function MetricsExtractionRuleForm({isEdit, project, onSubmit, ...props}:
                   </ConditionsWrapper>
                   <ConditionsButtonBar>
                     <Button
-                      disabled={conditions.some(query => !query)}
-                      onClick={() => onChange([...conditions, ''], {})}
+                      onClick={() => onChange([...conditions, createCondition()], {})}
                       icon={<IconAdd />}
                     >
                       {t('Add Query')}

+ 1 - 1
static/app/views/settings/projectMetrics/metricsExtractionRulesTable.tsx

@@ -13,11 +13,11 @@ import {IconDelete} from 'sentry/icons/iconDelete';
 import {IconEdit} from 'sentry/icons/iconEdit';
 import {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
+import type {MetricsExtractionRule} from 'sentry/types/metrics';
 import type {Project} from 'sentry/types/project';
 import useOrganization from 'sentry/utils/useOrganization';
 import {MetricsExtractionRuleEditModal} from 'sentry/views/settings/projectMetrics/metricsExtractionRuleEditModal';
 import {
-  type MetricsExtractionRule,
   useDeleteMetricsExtractionRules,
   useMetricsExtractionRules,
 } from 'sentry/views/settings/projectMetrics/utils/api';

+ 20 - 12
static/app/views/settings/projectMetrics/utils/api.tsx

@@ -1,5 +1,4 @@
-import type {MetricsAggregate} from 'sentry/types/metrics';
-import type {FormattingSupportedMetricUnit} from 'sentry/utils/metrics/formatters';
+import type {MetricsExtractionRule} from 'sentry/types/metrics';
 import {
   type ApiQueryKey,
   getApiQueryData,
@@ -12,17 +11,22 @@ import {
 import type RequestError from 'sentry/utils/requestError/requestError';
 import useApi from 'sentry/utils/useApi';
 
+/**
+ * Remove temporary ids from conditions before sending to the server
+ */
+function filterTempIds(rules: MetricsExtractionRule[]) {
+  return rules.map(rule => ({
+    ...rule,
+    conditions: rule.conditions.map(condition => ({
+      ...condition,
+      id: condition.id < 0 ? undefined : condition.id,
+    })),
+  }));
+}
+
 const getMetricsExtractionRulesEndpoint = (orgSlug: string, projectSlug: string) =>
   [`/projects/${orgSlug}/${projectSlug}/metrics/extraction-rules/`] as const;
 
-export interface MetricsExtractionRule {
-  aggregates: MetricsAggregate[];
-  conditions: string[];
-  spanAttribute: string;
-  tags: string[];
-  unit: FormattingSupportedMetricUnit;
-}
-
 export function useMetricsExtractionRules(orgSlug: string, projectSlug: string) {
   return useApiQuery<MetricsExtractionRule[]>(
     getMetricsExtractionRulesEndpoint(orgSlug, projectSlug),
@@ -119,7 +123,9 @@ export function useCreateMetricsExtractionRules(orgSlug: string, projectSlug: st
     data => {
       return api.requestPromise(queryKey[0], {
         method: 'POST',
-        data,
+        data: {
+          metricsExtractionRules: filterTempIds(data.metricsExtractionRules),
+        },
       });
     },
     {
@@ -156,7 +162,9 @@ export function useUpdateMetricsExtractionRules(orgSlug: string, projectSlug: st
     data => {
       return api.requestPromise(queryKey[0], {
         method: 'PUT',
-        data,
+        data: {
+          metricsExtractionRules: filterTempIds(data.metricsExtractionRules),
+        },
       });
     },
     {