From 4c344322466f9850b66e66c46d50731c4c891677 Mon Sep 17 00:00:00 2001
From: Daniele Rossetti <drossetti@gitlab.com>
Date: Wed, 31 Jan 2024 11:52:33 +0000
Subject: [PATCH] Configure metrics FilteredSearch with search metadata

---
 .../javascripts/observability/client.js       | 43 +++++----
 .../details/filter_bar/groupby_filter.vue     | 28 +++---
 .../filter_bar/metrics_filtered_search.vue    | 28 +++---
 .../metrics/details/metrics_details.vue       | 35 +++----
 .../details/filter_bar/groupby_filter_spec.js | 65 +++++++------
 .../metrics_filtered_search_spec.js           | 91 +++++++++++--------
 .../metrics/details/metrics_details_spec.js   | 41 ++++-----
 locale/gitlab.pot                             |  2 +-
 spec/frontend/observability/client_spec.js    | 24 ++---
 9 files changed, 182 insertions(+), 175 deletions(-)

diff --git a/app/assets/javascripts/observability/client.js b/app/assets/javascripts/observability/client.js
index 3785f42a769f0..5f146fce4817d 100644
--- a/app/assets/javascripts/observability/client.js
+++ b/app/assets/javascripts/observability/client.js
@@ -337,7 +337,7 @@ async function fetchMetrics(metricsUrl, { filters = {}, limit } = {}) {
 
 const SUPPORTED_METRICS_DIMENSION_FILTER_OPERATORS = ['=', '!=', '=~', '!~'];
 
-function addMetricsDimensionFilterToQueryParams(dimensionFilter, params) {
+function addMetricsAttributeFilterToQueryParams(dimensionFilter, params) {
   if (!dimensionFilter || !params) return;
 
   Object.entries(dimensionFilter).forEach(([filterName, values]) => {
@@ -371,12 +371,12 @@ function addMetricsDateRangeFilterToQueryParams(dateRangeFilter, params) {
 function addGroupByFilterToQueryParams(groupByFilter, params) {
   if (!groupByFilter || !params) return;
 
-  const { func, dimensions } = groupByFilter;
+  const { func, attributes } = groupByFilter;
   if (func) {
     params.append('groupby_fn', func);
   }
-  if (Array.isArray(dimensions) && dimensions.length > 0) {
-    params.append('groupby_attrs', dimensions.join(','));
+  if (Array.isArray(attributes) && attributes.length > 0) {
+    params.append('groupby_attrs', attributes.join(','));
   }
 }
 
@@ -394,10 +394,10 @@ async function fetchMetric(searchUrl, name, type, options = {}) {
       mtype: type,
     });
 
-    const { dimensions, dateRange, groupBy } = options.filters ?? {};
+    const { attributes, dateRange, groupBy } = options.filters ?? {};
 
-    if (dimensions) {
-      addMetricsDimensionFilterToQueryParams(dimensions, params);
+    if (attributes) {
+      addMetricsAttributeFilterToQueryParams(attributes, params);
     }
 
     if (dateRange) {
@@ -442,17 +442,24 @@ async function fetchMetricSearchMetadata(searchMetadataUrl, name, type) {
     // });
     // return data;
 
-    return {
-      name: 'cpu_seconds_total',
-      type: 'sum',
-      description: 'some_description',
-      last_ingested_at: 1705374438711900000,
-      attribute_keys: ['host.name', 'host.dc', 'host.type'],
-      supported_aggregations: ['1m', '1h'],
-      supported_functions: ['min', 'max', 'avg', 'sum', 'count'],
-      default_group_by_attributes: ['host.name'],
-      default_group_by_function: ['avg'],
-    };
+    return new Promise((resolve) => {
+      setTimeout(() =>
+        resolve(
+          {
+            name: 'cpu_seconds_total',
+            type: 'sum',
+            description: 'some_description',
+            last_ingested_at: 1705374438711900000,
+            attribute_keys: ['host.name', 'host.dc', 'host.type'],
+            supported_aggregations: ['1m', '1h'],
+            supported_functions: ['min', 'max', 'avg', 'sum', 'count'],
+            default_group_by_attributes: ['host.name'],
+            default_group_by_function: 'avg',
+          },
+          1000,
+        ),
+      );
+    });
   } catch (e) {
     return reportErrorAndThrow(e);
   }
diff --git a/ee/app/assets/javascripts/metrics/details/filter_bar/groupby_filter.vue b/ee/app/assets/javascripts/metrics/details/filter_bar/groupby_filter.vue
index acfbb5c9b83d6..d6791c1c864e1 100644
--- a/ee/app/assets/javascripts/metrics/details/filter_bar/groupby_filter.vue
+++ b/ee/app/assets/javascripts/metrics/details/filter_bar/groupby_filter.vue
@@ -10,11 +10,11 @@ export default {
     groupByPlaceholderMultipleSelect: s__('ObservabilityMetrics|multiple'),
   },
   props: {
-    searchConfig: {
+    searchMetadata: {
       type: Object,
       required: true,
     },
-    selectedDimensions: {
+    selectedAttributes: {
       type: Array,
       required: true,
     },
@@ -25,24 +25,24 @@ export default {
   },
   data() {
     return {
-      groupByDimensions: this.selectedDimensions,
+      groupByAttributes: this.selectedAttributes,
       groupByFunction: this.selectedFunction,
     };
   },
   computed: {
     availableGroupByFunctions() {
-      return this.searchConfig.groupByFunctions.map((func) => ({ value: func, text: func }));
+      return this.searchMetadata.supported_functions.map((func) => ({ value: func, text: func }));
     },
-    availableGroupByDimensions() {
-      return this.searchConfig.dimensions.map((d) => ({ value: d, text: d }));
+    availableGroupByAttributes() {
+      return this.searchMetadata.attribute_keys.map((d) => ({ value: d, text: d }));
     },
     groupByLabel() {
-      return this.groupByDimensions.length > 1 ? this.groupByDimensions.join(', ') : '';
+      return this.groupByAttributes.length > 1 ? this.groupByAttributes.join(', ') : '';
     },
     groupByToggleText() {
-      if (this.groupByDimensions.length > 0) {
-        if (this.groupByDimensions.length === 1) {
-          return this.groupByDimensions[0];
+      if (this.groupByAttributes.length > 0) {
+        if (this.groupByAttributes.length === 1) {
+          return this.groupByAttributes[0];
         }
         return this.$options.i18n.groupByPlaceholderMultipleSelect;
       }
@@ -52,7 +52,7 @@ export default {
   methods: {
     onSelect() {
       this.$emit('groupBy', {
-        dimensions: this.groupByDimensions,
+        attributes: this.groupByAttributes,
         func: this.groupByFunction,
       });
     },
@@ -70,11 +70,11 @@ export default {
     />
     <span>{{ __('by') }}</span>
     <gl-collapsible-listbox
-      v-model="groupByDimensions"
-      data-testid="group-by-dimensions-dropdown"
+      v-model="groupByAttributes"
+      data-testid="group-by-attributes-dropdown"
       :toggle-text="groupByToggleText"
       multiple
-      :items="availableGroupByDimensions"
+      :items="availableGroupByAttributes"
       @select="onSelect"
     />
     <span data-testid="group-by-label">{{ groupByLabel }}</span>
diff --git a/ee/app/assets/javascripts/metrics/details/filter_bar/metrics_filtered_search.vue b/ee/app/assets/javascripts/metrics/details/filter_bar/metrics_filtered_search.vue
index bec6eb6bffe1e..522e5fe64736d 100644
--- a/ee/app/assets/javascripts/metrics/details/filter_bar/metrics_filtered_search.vue
+++ b/ee/app/assets/javascripts/metrics/details/filter_bar/metrics_filtered_search.vue
@@ -14,14 +14,14 @@ export default {
     GroupByFilter,
   },
   i18n: {
-    searchInputPlaceholder: s__('ObservabilityMetrics|Filter dimensions...'),
+    searchInputPlaceholder: s__('ObservabilityMetrics|Filter attributes...'),
   },
   props: {
-    searchConfig: {
+    searchMetadata: {
       type: Object,
       required: true,
     },
-    dimensionFilters: {
+    attributeFilters: {
       type: Array,
       required: false,
       default: () => [],
@@ -42,16 +42,16 @@ export default {
       shouldShowDateRangePicker: false,
       dateRange: this.dateRangeFilter,
       groupBy: this.groupByFilter ?? {
-        dimensions: this.searchConfig.defaultGroupByDimensions ?? [],
-        func: this.searchConfig.defaultGroupByFunction ?? '',
+        attributes: this.searchMetadata.default_group_by_attributes ?? [],
+        func: this.searchMetadata.default_group_by_function ?? '',
       },
     };
   },
   computed: {
     availableTokens() {
-      return this.searchConfig.dimensions.map((dimension) => ({
-        title: dimension,
-        type: dimension,
+      return this.searchMetadata.attribute_keys.map((attribute) => ({
+        title: attribute,
+        type: attribute,
         token: GlFilteredSearchToken,
         operators: [...OPERATORS_IS_NOT, ...OPERATORS_LIKE_NOT],
       }));
@@ -60,7 +60,7 @@ export default {
   methods: {
     onFilter(filters) {
       this.$emit('filter', {
-        dimensions: filters,
+        attributes: filters,
         dateRange: this.dateRange,
         groupBy: this.groupBy,
       });
@@ -68,8 +68,8 @@ export default {
     onDateRangeSelected({ value, startDate, endDate }) {
       this.dateRange = { value, startDate, endDate };
     },
-    onGroupBy({ dimensions, func }) {
-      this.groupBy = { dimensions, func };
+    onGroupBy({ attributes, func }) {
+      this.groupBy = { attributes, func };
     },
   },
 };
@@ -85,7 +85,7 @@ export default {
       namespace="metrics-details-filtered-search"
       :search-input-placeholder="$options.i18n.searchInputPlaceholder"
       :tokens="availableTokens"
-      :initial-filter-value="dimensionFilters"
+      :initial-filter-value="attributeFilters"
       terms-as-tokens
       @onFilter="onFilter"
     />
@@ -97,8 +97,8 @@ export default {
     <hr class="gl-my-3" />
 
     <group-by-filter
-      :search-config="searchConfig"
-      :selected-dimensions="groupBy.dimensions"
+      :search-metadata="searchMetadata"
+      :selected-attributes="groupBy.attributes"
       :selected-function="groupBy.func"
       @groupBy="onGroupBy"
     />
diff --git a/ee/app/assets/javascripts/metrics/details/metrics_details.vue b/ee/app/assets/javascripts/metrics/details/metrics_details.vue
index 19506899c9e79..2925e8845fa13 100644
--- a/ee/app/assets/javascripts/metrics/details/metrics_details.vue
+++ b/ee/app/assets/javascripts/metrics/details/metrics_details.vue
@@ -53,10 +53,10 @@ export default {
     const defaultRange = periodToDate(DEFAULT_TIME_RANGE);
     return {
       metricData: [],
-      searchConfig: null,
+      searchMetadata: null,
       // TODO get filters from query params https://gitlab.com/gitlab-org/opstrace/opstrace/-/work_items/2605
       filters: {
-        dimensions: [],
+        attributes: [],
         dateRange: {
           value: DEFAULT_TIME_RANGE,
           startDarte: defaultRange.min,
@@ -64,7 +64,6 @@ export default {
         },
       },
       loading: false,
-      searchMetadata: null,
     };
   },
   computed: {
@@ -76,9 +75,9 @@ export default {
         description: this.searchMetadata?.description,
       };
     },
-    dimensionFiltersValue() {
-      // only dimensions are used by the filtered_search component, so only those needs processing
-      return prepareTokens(this.filters.dimensions);
+    attributeFiltersValue() {
+      // only attributes are used by the filtered_search component, so only those needs processing
+      return prepareTokens(this.filters.attributes);
     },
   },
   created() {
@@ -96,10 +95,7 @@ export default {
       try {
         const enabled = await this.observabilityClient.isObservabilityEnabled();
         if (enabled) {
-          await this.fetchMetricSearchMetadata();
-          if (this.searchMetadata) {
-            await this.fetchMetricData();
-          }
+          await Promise.all([this.fetchMetricSearchMetadata(), await this.fetchMetricData()]);
         } else {
           this.goToMetricsIndex();
         }
@@ -131,13 +127,6 @@ export default {
           this.metricType,
           { filters: this.filters },
         );
-        // TODO fetch config from API https://gitlab.com/gitlab-org/opstrace/opstrace/-/issues/2488
-        this.searchConfig = {
-          dimensions: ['dimension_one', 'dimension_two'],
-          groupByFunctions: ['avg', 'sum', 'p50'],
-          defaultGroupByFunction: 'avg',
-          defaultGroupByDimensions: ['dimension_one', 'dimension_two'],
-        };
       } catch (e) {
         createAlert({
           message: this.$options.i18n.error,
@@ -149,10 +138,10 @@ export default {
     goToMetricsIndex() {
       visitUrl(this.metricsIndexUrl);
     },
-    onFilter({ dimensions, dateRange, groupBy }) {
+    onFilter({ attributes, dateRange, groupBy }) {
       this.filters = {
-        // only dimensions are used by the filtered_search component, so only those needs processing
-        dimensions: processFilteredSearchFilters(dimensions),
+        // only attributes are used by the filtered_search component, so only those needs processing
+        attributes: processFilteredSearchFilters(attributes),
         dateRange,
         groupBy,
       };
@@ -182,9 +171,9 @@ export default {
 
     <div class="gl-my-6">
       <filtered-search
-        v-if="searchConfig"
-        :search-config="searchConfig"
-        :dimension-filters="dimensionFiltersValue"
+        v-if="searchMetadata"
+        :search-metadata="searchMetadata"
+        :attribute-filters="attributeFiltersValue"
         :date-range-filter="filters.dateRange"
         :group-by-filter="filters.groupBy"
         @filter="onFilter"
diff --git a/ee/spec/frontend/metrics/details/filter_bar/groupby_filter_spec.js b/ee/spec/frontend/metrics/details/filter_bar/groupby_filter_spec.js
index e3a19da9d45df..1f6d3d5a164a7 100644
--- a/ee/spec/frontend/metrics/details/filter_bar/groupby_filter_spec.js
+++ b/ee/spec/frontend/metrics/details/filter_bar/groupby_filter_spec.js
@@ -5,11 +5,18 @@ describe('GroupByFilter', () => {
   let wrapper;
 
   const props = {
-    searchConfig: {
-      groupByFunctions: ['sum', 'avg'],
-      dimensions: ['dimension_one', 'dimensions_two'],
+    searchMetadata: {
+      name: 'cpu_seconds_total',
+      type: 'sum',
+      description: 'some_description',
+      last_ingested_at: 1705374438711900000,
+      attribute_keys: ['attribute_one', 'attributes_two'],
+      supported_aggregations: ['1m', '1h'],
+      supported_functions: ['sum', 'avg'],
+      default_group_by_attributes: ['attribute_one', 'attributes_two'],
+      default_group_by_function: 'avg',
     },
-    selectedDimensions: ['dimension_one'],
+    selectedAttributes: ['attribute_one'],
     selectedFunction: 'sum',
   };
 
@@ -35,13 +42,13 @@ describe('GroupByFilter', () => {
     );
   });
 
-  it('renders the group by dimensions dropdown', () => {
-    expect(wrapper.findByTestId('group-by-dimensions-dropdown').props('items')).toEqual([
-      { value: 'dimension_one', text: 'dimension_one' },
-      { value: 'dimensions_two', text: 'dimensions_two' },
+  it('renders the group by attributes dropdown', () => {
+    expect(wrapper.findByTestId('group-by-attributes-dropdown').props('items')).toEqual([
+      { value: 'attribute_one', text: 'attribute_one' },
+      { value: 'attributes_two', text: 'attributes_two' },
     ]);
-    expect(wrapper.findByTestId('group-by-dimensions-dropdown').props('selected')).toEqual(
-      props.selectedDimensions,
+    expect(wrapper.findByTestId('group-by-attributes-dropdown').props('selected')).toEqual(
+      props.selectedAttributes,
     );
   });
 
@@ -51,22 +58,22 @@ describe('GroupByFilter', () => {
     expect(wrapper.emitted('groupBy')).toEqual([
       [
         {
-          dimensions: props.selectedDimensions,
+          attributes: props.selectedAttributes,
           func: 'avg',
         },
       ],
     ]);
   });
 
-  it('emits groupBy on dimension change', async () => {
+  it('emits groupBy on attribute change', async () => {
     await wrapper
-      .findByTestId('group-by-dimensions-dropdown')
-      .vm.$emit('select', ['dimension_two']);
+      .findByTestId('group-by-attributes-dropdown')
+      .vm.$emit('select', ['attribute_two']);
 
     expect(wrapper.emitted('groupBy')).toEqual([
       [
         {
-          dimensions: ['dimension_two'],
+          attributes: ['attribute_two'],
           func: props.selectedFunction,
         },
       ],
@@ -74,23 +81,23 @@ describe('GroupByFilter', () => {
   });
 
   it('updates the group-by toggle text depending on value', async () => {
-    expect(wrapper.findByTestId('group-by-dimensions-dropdown').props('toggleText')).toBe(
-      'dimension_one',
+    expect(wrapper.findByTestId('group-by-attributes-dropdown').props('toggleText')).toBe(
+      'attribute_one',
     );
 
     await wrapper
-      .findByTestId('group-by-dimensions-dropdown')
-      .vm.$emit('select', ['dimension_two']);
+      .findByTestId('group-by-attributes-dropdown')
+      .vm.$emit('select', ['attribute_two']);
 
-    expect(wrapper.findByTestId('group-by-dimensions-dropdown').props('toggleText')).toBe(
-      'dimension_two',
+    expect(wrapper.findByTestId('group-by-attributes-dropdown').props('toggleText')).toBe(
+      'attribute_two',
     );
 
     await wrapper
-      .findByTestId('group-by-dimensions-dropdown')
-      .vm.$emit('select', ['dimension_two', 'dimensions_one']);
+      .findByTestId('group-by-attributes-dropdown')
+      .vm.$emit('select', ['attribute_two', 'attributes_one']);
 
-    expect(wrapper.findByTestId('group-by-dimensions-dropdown').props('toggleText')).toBe(
+    expect(wrapper.findByTestId('group-by-attributes-dropdown').props('toggleText')).toBe(
       'multiple',
     );
   });
@@ -99,15 +106,15 @@ describe('GroupByFilter', () => {
     expect(wrapper.findByTestId('group-by-label').text()).toBe('');
 
     await wrapper
-      .findByTestId('group-by-dimensions-dropdown')
-      .vm.$emit('select', ['dimension_two']);
+      .findByTestId('group-by-attributes-dropdown')
+      .vm.$emit('select', ['attribute_two']);
 
     expect(wrapper.findByTestId('group-by-label').text()).toBe('');
 
     await wrapper
-      .findByTestId('group-by-dimensions-dropdown')
-      .vm.$emit('select', ['dimension_two', 'dimensions_one']);
+      .findByTestId('group-by-attributes-dropdown')
+      .vm.$emit('select', ['attribute_two', 'attributes_one']);
 
-    expect(wrapper.findByTestId('group-by-label').text()).toBe('dimension_two, dimensions_one');
+    expect(wrapper.findByTestId('group-by-label').text()).toBe('attribute_two, attributes_one');
   });
 });
diff --git a/ee/spec/frontend/metrics/details/filter_bar/metrics_filtered_search_spec.js b/ee/spec/frontend/metrics/details/filter_bar/metrics_filtered_search_spec.js
index d6514e580c2ab..e5fb1a9127e9e 100644
--- a/ee/spec/frontend/metrics/details/filter_bar/metrics_filtered_search_spec.js
+++ b/ee/spec/frontend/metrics/details/filter_bar/metrics_filtered_search_spec.js
@@ -10,15 +10,22 @@ import { OPERATORS_LIKE_NOT } from '~/observability/constants';
 describe('MetricsFilteredSearch', () => {
   let wrapper;
 
-  const defaultSearchConfig = {
-    dimensions: ['dimension_one', 'dimension_two'],
-    groupByFunctions: ['avg', 'sum', 'p50'],
+  const defaultSearchMetadata = {
+    name: 'cpu_seconds_total',
+    type: 'sum',
+    description: 'some_description',
+    last_ingested_at: 1705374438711900000,
+    attribute_keys: ['attribute_one', 'attribute_two'],
+    supported_aggregations: ['1m', '1h'],
+    supported_functions: ['avg', 'sum', 'p50'],
+    default_group_by_attributes: ['host.name'],
+    default_group_by_function: 'avg',
   };
 
-  const mount = (props = {}, searchConfig = {}) => {
+  const mount = (props = {}, searchMetadata = {}) => {
     wrapper = shallowMountExtended(MetricsFilteredSearch, {
       propsData: {
-        searchConfig: { ...defaultSearchConfig, ...searchConfig },
+        searchMetadata: { ...defaultSearchMetadata, ...searchMetadata },
         ...props,
       },
     });
@@ -32,14 +39,14 @@ describe('MetricsFilteredSearch', () => {
   const findDateRangeFilter = () => wrapper.findComponent(DateRangeFilter);
   const findGroupByFilter = () => wrapper.findComponent(GroupByFilter);
 
-  it('renders the filtered search component with tokens based on dimensions', () => {
+  it('renders the filtered search component with tokens based on attributes', () => {
     const filteredSeach = findFilteredSearch();
     expect(filteredSeach.exists()).toBe(true);
     const tokens = filteredSeach.props('tokens');
-    expect(tokens.length).toBe(defaultSearchConfig.dimensions.length);
+    expect(tokens.length).toBe(defaultSearchMetadata.attribute_keys.length);
     tokens.forEach((token, index) => {
-      expect(token.type).toBe(defaultSearchConfig.dimensions[index]);
-      expect(token.title).toBe(defaultSearchConfig.dimensions[index]);
+      expect(token.type).toBe(defaultSearchMetadata.attribute_keys[index]);
+      expect(token.title).toBe(defaultSearchMetadata.attribute_keys[index]);
       expect(token.token).toBe(GlFilteredSearchToken);
       expect(token.operators).toEqual([...OPERATORS_IS_NOT, ...OPERATORS_LIKE_NOT]);
     });
@@ -47,7 +54,7 @@ describe('MetricsFilteredSearch', () => {
 
   it('renders the filtered search component with with initial tokens', () => {
     const filters = [{ type: 'key.name', value: 'foo' }];
-    mount({ dimensionFilters: filters });
+    mount({ attributeFilters: filters });
 
     expect(findFilteredSearch().props('initialFilterValue')).toEqual(filters);
   });
@@ -65,32 +72,36 @@ describe('MetricsFilteredSearch', () => {
   });
 
   describe('group-by filter', () => {
-    it('renders the group-by filter with search config', () => {
+    it('renders the group-by filter with search metadata', () => {
       const groupBy = findGroupByFilter();
       expect(groupBy.exists()).toBe(true);
-      expect(groupBy.props('searchConfig')).toEqual(defaultSearchConfig);
-      expect(groupBy.props('selectedFunction')).toBe('');
-      expect(groupBy.props('selectedDimensions')).toEqual([]);
+      expect(groupBy.props('searchMetadata')).toEqual(defaultSearchMetadata);
+      expect(groupBy.props('selectedFunction')).toBe(
+        defaultSearchMetadata.default_group_by_function,
+      );
+      expect(groupBy.props('selectedAttributes')).toEqual(
+        defaultSearchMetadata.default_group_by_attributes,
+      );
     });
 
     it('renders the group-by filter with defaults', () => {
       mount(
         {},
         {
-          defaultGroupByFunction: 'avg',
-          defaultGroupByDimensions: ['dimension_one', 'dimension_two'],
+          default_group_by_function: 'avg',
+          default_group_by_attributes: ['attribute_one', 'attribute_two'],
         },
       );
       const groupBy = findGroupByFilter();
 
-      expect(groupBy.props('searchConfig')).toEqual({
-        ...defaultSearchConfig,
-        defaultGroupByFunction: 'avg',
-        defaultGroupByDimensions: ['dimension_one', 'dimension_two'],
+      expect(groupBy.props('searchMetadata')).toEqual({
+        ...defaultSearchMetadata,
+        default_group_by_function: 'avg',
+        default_group_by_attributes: ['attribute_one', 'attribute_two'],
       });
 
       expect(groupBy.props('selectedFunction')).toBe('avg');
-      expect(groupBy.props('selectedDimensions')).toEqual(['dimension_one', 'dimension_two']);
+      expect(groupBy.props('selectedAttributes')).toEqual(['attribute_one', 'attribute_two']);
     });
 
     it('renders the group-by filter with specified prop', () => {
@@ -98,32 +109,32 @@ describe('MetricsFilteredSearch', () => {
         {
           groupByFilter: {
             func: 'sum',
-            dimensions: ['attr_1'],
+            attributes: ['attr_1'],
           },
         },
         {
-          defaultGroupByFunction: 'avg',
-          defaultGroupByDimensions: ['dimension_one', 'dimension_two'],
+          default_group_by_function: 'avg',
+          default_group_by_attributes: ['attribute_one', 'attribute_two'],
         },
       );
       const groupBy = findGroupByFilter();
 
       expect(groupBy.props('selectedFunction')).toBe('sum');
-      expect(groupBy.props('selectedDimensions')).toEqual(['attr_1']);
+      expect(groupBy.props('selectedAttributes')).toEqual(['attr_1']);
     });
   });
 
-  it('emits the filter event when the dimensions filter is changed', async () => {
-    const filters = [{ dimension: 'namespace', operator: 'is not', value: 'test' }];
+  it('emits the filter event when the attributes filter is changed', async () => {
+    const filters = [{ attribute: 'namespace', operator: 'is not', value: 'test' }];
     await findFilteredSearch().vm.$emit('onFilter', filters);
 
     expect(wrapper.emitted('filter')).toEqual([
       [
         {
-          dimensions: [{ dimension: 'namespace', operator: 'is not', value: 'test' }],
+          attributes: [{ attribute: 'namespace', operator: 'is not', value: 'test' }],
           groupBy: {
-            dimensions: [],
-            func: '',
+            attributes: defaultSearchMetadata.default_group_by_attributes,
+            func: defaultSearchMetadata.default_group_by_function,
           },
         },
       ],
@@ -145,11 +156,11 @@ describe('MetricsFilteredSearch', () => {
     expect(wrapper.emitted('filter')).toEqual([
       [
         {
-          dimensions: [],
+          attributes: [],
           dateRange,
           groupBy: {
-            dimensions: [],
-            func: '',
+            attributes: defaultSearchMetadata.default_group_by_attributes,
+            func: defaultSearchMetadata.default_group_by_function,
           },
         },
       ],
@@ -161,8 +172,8 @@ describe('MetricsFilteredSearch', () => {
     mount(
       {},
       {
-        defaultGroupByFunction: 'avg',
-        defaultGroupByDimensions: ['dimension_one', 'dimension_two'],
+        default_group_by_function: 'avg',
+        default_group_by_attributes: ['attribute_one', 'attribute_two'],
       },
     );
 
@@ -171,9 +182,9 @@ describe('MetricsFilteredSearch', () => {
     expect(wrapper.emitted('filter')).toEqual([
       [
         {
-          dimensions: [],
+          attributes: [],
           groupBy: {
-            dimensions: ['dimension_one', 'dimension_two'],
+            attributes: ['attribute_one', 'attribute_two'],
             func: 'avg',
           },
         },
@@ -183,7 +194,7 @@ describe('MetricsFilteredSearch', () => {
 
   it('emits the filter event when the group-by is changed and the filtered-search onFilter is emitted', async () => {
     const groupBy = {
-      dimensions: ['dimension_one'],
+      attributes: ['attribute_one'],
       func: 'sum',
     };
 
@@ -195,12 +206,12 @@ describe('MetricsFilteredSearch', () => {
     expect(wrapper.emitted('filter')).toEqual([
       [
         {
-          dimensions: [],
+          attributes: [],
           groupBy,
         },
       ],
     ]);
     expect(findGroupByFilter().props('selectedFunction')).toBe(groupBy.func);
-    expect(findGroupByFilter().props('selectedDimensions')).toEqual(groupBy.dimensions);
+    expect(findGroupByFilter().props('selectedAttributes')).toEqual(groupBy.attributes);
   });
 });
diff --git a/ee/spec/frontend/metrics/details/metrics_details_spec.js b/ee/spec/frontend/metrics/details/metrics_details_spec.js
index 84e7ec6288274..d686d4e7e7c96 100644
--- a/ee/spec/frontend/metrics/details/metrics_details_spec.js
+++ b/ee/spec/frontend/metrics/details/metrics_details_spec.js
@@ -133,16 +133,15 @@ describe('MetricsDetails', () => {
     describe('filtered search', () => {
       const findFilteredSearch = () => findMetricDetails().findComponent(FilteredSearch);
       it('renders the FilteredSearch component', () => {
-        expect(findFilteredSearch().exists()).toBe(true);
-        // TODO get searchConfig from API https://gitlab.com/gitlab-org/opstrace/opstrace/-/issues/2488
-        expect(Object.keys(findFilteredSearch().props('searchConfig'))).toEqual(
-          expect.arrayContaining([
-            'dimensions',
-            'groupByFunctions',
-            'defaultGroupByFunction',
-            'defaultGroupByDimensions',
-          ]),
-        );
+        const filteredSearch = findFilteredSearch();
+        expect(filteredSearch.exists()).toBe(true);
+        expect(filteredSearch.props('searchMetadata')).toBe(mockSearchMetadata);
+      });
+
+      it('does not render the filtered search component if fetching metadata fails', async () => {
+        observabilityClientMock.fetchMetricSearchMetadata.mockRejectedValueOnce('error');
+        await mountComponent();
+        expect(findFilteredSearch().exists()).toBe(false);
       });
 
       it('sets the default date range', () => {
@@ -156,7 +155,7 @@ describe('MetricsDetails', () => {
       it('fetches metrics with filters', () => {
         expect(observabilityClientMock.fetchMetric).toHaveBeenCalledWith(METRIC_ID, METRIC_TYPE, {
           filters: {
-            dimensions: [],
+            attributes: [],
             dateRange: {
               endDate: new Date('2020-07-06T00:00:00.000Z'),
               startDarte: new Date('2020-07-05T23:00:00.000Z'),
@@ -167,9 +166,9 @@ describe('MetricsDetails', () => {
       });
 
       describe('on search submit', () => {
-        const setFilters = async (dimensions, dateRange, groupBy) => {
+        const setFilters = async (attributes, dateRange, groupBy) => {
           findFilteredSearch().vm.$emit('filter', {
-            dimensions: prepareTokens(dimensions),
+            attributes: prepareTokens(attributes),
             dateRange,
             groupBy,
           });
@@ -188,7 +187,7 @@ describe('MetricsDetails', () => {
             },
             {
               func: 'avg',
-              dimensions: ['attr_1', 'attr_2'],
+              attributes: ['attr_1', 'attr_2'],
             },
           );
         });
@@ -199,7 +198,7 @@ describe('MetricsDetails', () => {
             METRIC_TYPE,
             {
               filters: {
-                dimensions: {
+                attributes: {
                   'key.one': [{ operator: '=', value: '12h' }],
                 },
                 dateRange: {
@@ -209,7 +208,7 @@ describe('MetricsDetails', () => {
                 },
                 groupBy: {
                   func: 'avg',
-                  dimensions: ['attr_1', 'attr_2'],
+                  attributes: ['attr_1', 'attr_2'],
                 },
               },
             },
@@ -222,14 +221,14 @@ describe('MetricsDetails', () => {
             startDarte: new Date('2020-07-05T23:00:00.000Z'),
             value: '30d',
           });
-          expect(findFilteredSearch().props('dimensionFilters')).toEqual(
+          expect(findFilteredSearch().props('attributeFilters')).toEqual(
             prepareTokens({
               'key.one': [{ operator: '=', value: '12h' }],
             }),
           );
           expect(findFilteredSearch().props('groupByFilter')).toEqual({
             func: 'avg',
-            dimensions: ['attr_1', 'attr_2'],
+            attributes: ['attr_1', 'attr_2'],
           });
         });
       });
@@ -318,12 +317,6 @@ describe('MetricsDetails', () => {
       });
     });
 
-    it('does not fetch metric data if fetching search metadata fails', async () => {
-      observabilityClientMock.fetchMetricSearchMetadata.mockRejectedValueOnce('error');
-      await mountComponent();
-      expect(observabilityClientMock.fetchMetric).not.toHaveBeenCalled();
-    });
-
     it('renders an alert if metricId is missing', async () => {
       await mountComponent({ metricId: undefined });
 
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 389a22113288e..f997e489e5c91 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -33588,7 +33588,7 @@ msgstr ""
 msgid "ObservabilityMetrics|Failed to load metrics."
 msgstr ""
 
-msgid "ObservabilityMetrics|Filter dimensions..."
+msgid "ObservabilityMetrics|Filter attributes..."
 msgstr ""
 
 msgid "ObservabilityMetrics|Last ingested"
diff --git a/spec/frontend/observability/client_spec.js b/spec/frontend/observability/client_spec.js
index 3d98e1a2166bc..367278a7b7e70 100644
--- a/spec/frontend/observability/client_spec.js
+++ b/spec/frontend/observability/client_spec.js
@@ -785,11 +785,11 @@ describe('buildClient', () => {
         axiosMock.onGet(metricsSearchUrl).reply(200, { results: [] });
       });
 
-      describe('dimension filter', () => {
+      describe('attribute filter', () => {
         it('converts filter to proper query params', async () => {
           await client.fetchMetric('name', 'type', {
             filters: {
-              dimensions: {
+              attributes: {
                 attr_1: [
                   { operator: '=', value: 'foo' },
                   { operator: '!=', value: 'bar' },
@@ -811,7 +811,7 @@ describe('buildClient', () => {
         it('handles repeated params', async () => {
           await client.fetchMetric('name', 'type', {
             filters: {
-              dimensions: {
+              attributes: {
                 attr_1: [
                   { operator: '=', value: 'v1' },
                   { operator: '=', value: 'v2' },
@@ -824,7 +824,7 @@ describe('buildClient', () => {
 
         it('ignores empty filters', async () => {
           await client.fetchMetric('name', 'type', {
-            filters: { dimensions: [] },
+            filters: { attributes: [] },
           });
 
           expect(getQueryParam()).toBe('mname=name&mtype=type');
@@ -832,7 +832,7 @@ describe('buildClient', () => {
 
         it('ignores undefined dimension filters', async () => {
           await client.fetchMetric('name', 'type', {
-            filters: { dimensions: undefined },
+            filters: { attributes: undefined },
           });
 
           expect(getQueryParam()).toBe('mname=name&mtype=type');
@@ -841,7 +841,7 @@ describe('buildClient', () => {
         it('ignores non-array filters', async () => {
           await client.fetchMetric('name', 'type', {
             filters: {
-              dimensions: {
+              attributes: {
                 attr_1: { operator: '=', value: 'foo' },
               },
             },
@@ -853,7 +853,7 @@ describe('buildClient', () => {
         it('ignores unsupported operators', async () => {
           await client.fetchMetric('name', 'type', {
             filters: {
-              dimensions: {
+              attributes: {
                 attr_1: [
                   { operator: '*', value: 'foo' },
                   { operator: '>', value: 'foo' },
@@ -929,16 +929,16 @@ describe('buildClient', () => {
           expect(getQueryParam()).toContain(`groupby_fn=sum`);
         });
 
-        it('handle group by dimension', async () => {
+        it('handle group by attribute', async () => {
           await client.fetchMetric('name', 'type', {
-            filters: { groupBy: { dimensions: ['attr_1'] } },
+            filters: { groupBy: { attributes: ['attr_1'] } },
           });
           expect(getQueryParam()).toContain(`groupby_attrs=attr_1`);
         });
 
-        it('handle group by multiple dimensions', async () => {
+        it('handle group by multiple attributes', async () => {
           await client.fetchMetric('name', 'type', {
-            filters: { groupBy: { dimensions: ['attr_1', 'attr_2'] } },
+            filters: { groupBy: { attributes: ['attr_1', 'attr_2'] } },
           });
           expect(getQueryParam()).toContain(`groupby_attrs=attr_1,attr_2`);
         });
@@ -951,7 +951,7 @@ describe('buildClient', () => {
 
         it('ignores empty list', async () => {
           await client.fetchMetric('name', 'type', {
-            filters: { groupBy: { dimensions: [] } },
+            filters: { groupBy: { attributes: [] } },
           });
           expect(getQueryParam()).toBe('mname=name&mtype=type');
         });
-- 
GitLab