From a7529c64b3969d8e9df966aef2d1b1744dffef63 Mon Sep 17 00:00:00 2001
From: syasonik <syasonik@gitlab.com>
Date: Tue, 23 Jul 2024 16:08:45 -0400
Subject: [PATCH] Clean up unused custom metrics frontend

The Metrics Dashboard (used for monitoring apps deploying using gitlab)
was deprecated in 14.7 and removed from the product in 16.0.
This commit cleans up a portion of the dead code.

https://gitlab.com/groups/gitlab-org/-/epics/10107
---
 .../components/custom_metrics_form.vue        |  97 ------
 .../components/custom_metrics_form_fields.vue | 296 ------------------
 .../components/delete_custom_metric_modal.vue |  54 ----
 .../javascripts/custom_metrics/constants.js   |  12 -
 .../javascripts/custom_metrics/index.js       |  47 ---
 .../projects/prometheus/metrics/edit/index.js |   3 -
 .../projects/prometheus/metrics/new/index.js  |   3 -
 locale/gitlab.pot                             |  81 -----
 .../custom_metrics_form_fields_spec.js        | 245 ---------------
 .../components/custom_metrics_form_spec.js    |  47 ---
 10 files changed, 885 deletions(-)
 delete mode 100644 app/assets/javascripts/custom_metrics/components/custom_metrics_form.vue
 delete mode 100644 app/assets/javascripts/custom_metrics/components/custom_metrics_form_fields.vue
 delete mode 100644 app/assets/javascripts/custom_metrics/components/delete_custom_metric_modal.vue
 delete mode 100644 app/assets/javascripts/custom_metrics/constants.js
 delete mode 100644 app/assets/javascripts/custom_metrics/index.js
 delete mode 100644 app/assets/javascripts/pages/projects/prometheus/metrics/edit/index.js
 delete mode 100644 app/assets/javascripts/pages/projects/prometheus/metrics/new/index.js
 delete mode 100644 spec/frontend/custom_metrics/components/custom_metrics_form_fields_spec.js
 delete mode 100644 spec/frontend/custom_metrics/components/custom_metrics_form_spec.js

diff --git a/app/assets/javascripts/custom_metrics/components/custom_metrics_form.vue b/app/assets/javascripts/custom_metrics/components/custom_metrics_form.vue
deleted file mode 100644
index 960ad5d8288df..0000000000000
--- a/app/assets/javascripts/custom_metrics/components/custom_metrics_form.vue
+++ /dev/null
@@ -1,97 +0,0 @@
-<script>
-import { GlButton } from '@gitlab/ui';
-import csrf from '~/lib/utils/csrf';
-import { __, s__ } from '~/locale';
-import { formDataValidator } from '../constants';
-import CustomMetricsFormFields from './custom_metrics_form_fields.vue';
-import DeleteCustomMetricModal from './delete_custom_metric_modal.vue';
-
-export default {
-  components: {
-    CustomMetricsFormFields,
-    DeleteCustomMetricModal,
-    GlButton,
-  },
-  props: {
-    customMetricsPath: {
-      type: String,
-      required: false,
-      default: '',
-    },
-    metricPersisted: {
-      type: Boolean,
-      required: true,
-    },
-    editIntegrationPath: {
-      type: String,
-      required: true,
-    },
-    validateQueryPath: {
-      type: String,
-      required: true,
-    },
-    formData: {
-      type: Object,
-      required: true,
-      validator: formDataValidator,
-    },
-  },
-  data() {
-    return {
-      formIsValid: null,
-      errorMessage: '',
-    };
-  },
-  computed: {
-    saveButtonText() {
-      return this.metricPersisted ? __('Save Changes') : s__('Metrics|Create metric');
-    },
-    titleText() {
-      return this.metricPersisted ? s__('Metrics|Edit metric') : s__('Metrics|New metric');
-    },
-  },
-  created() {
-    this.csrf = csrf.token != null ? csrf.token : '';
-    this.formOperation = this.metricPersisted ? 'patch' : 'post';
-  },
-  methods: {
-    formValidation(isValid) {
-      this.formIsValid = isValid;
-    },
-    submit() {
-      this.$refs.form.submit();
-    },
-  },
-};
-</script>
-<template>
-  <div class="row my-3">
-    <h4 class="gl-mt-0 col-lg-8 offset-lg-2" data-testid="metrics-header">{{ titleText }}</h4>
-    <form ref="form" class="col-lg-8 offset-lg-2" :action="customMetricsPath" method="post">
-      <custom-metrics-form-fields
-        :form-operation="formOperation"
-        :form-data="formData"
-        :metric-persisted="metricPersisted"
-        :validate-query-path="validateQueryPath"
-        @formValidation="formValidation"
-      />
-      <div class="form-actions">
-        <gl-button
-          data-testid="metrics-save-button"
-          variant="confirm"
-          category="primary"
-          :disabled="!formIsValid"
-          @click="submit"
-        >
-          {{ saveButtonText }}
-        </gl-button>
-        <gl-button class="gl-float-right" :href="editIntegrationPath">{{ __('Cancel') }}</gl-button>
-        <delete-custom-metric-modal
-          v-if="metricPersisted"
-          :delete-metric-url="customMetricsPath"
-          :csrf-token="csrf"
-        />
-      </div>
-    </form>
-  </div>
-</template>
diff --git a/app/assets/javascripts/custom_metrics/components/custom_metrics_form_fields.vue b/app/assets/javascripts/custom_metrics/components/custom_metrics_form_fields.vue
deleted file mode 100644
index a073e822bb9b8..0000000000000
--- a/app/assets/javascripts/custom_metrics/components/custom_metrics_form_fields.vue
+++ /dev/null
@@ -1,296 +0,0 @@
-<script>
-import {
-  GlFormInput,
-  GlLink,
-  GlFormGroup,
-  GlFormRadioGroup,
-  GlLoadingIcon,
-  GlIcon,
-} from '@gitlab/ui';
-import { debounce } from 'lodash';
-import axios from '~/lib/utils/axios_utils';
-import { backOff } from '~/lib/utils/common_utils';
-import csrf from '~/lib/utils/csrf';
-import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
-import { __, s__ } from '~/locale';
-import { queryTypes, formDataValidator } from '../constants';
-
-const VALIDATION_REQUEST_TIMEOUT = 10000;
-const axiosCancelToken = axios.CancelToken;
-let cancelTokenSource;
-
-function backOffRequest(makeRequestCallback) {
-  return backOff((next, stop) => {
-    makeRequestCallback()
-      .then((resp) => {
-        if (resp.status === HTTP_STATUS_OK) {
-          stop(resp);
-        } else {
-          next();
-        }
-      })
-      // If the request is cancelled by axios
-      // then consider it as noop so that its not
-      // caught by subsequent catches
-      .catch((thrown) => (axios.isCancel(thrown) ? undefined : stop(thrown)));
-  }, VALIDATION_REQUEST_TIMEOUT);
-}
-
-export default {
-  components: {
-    GlFormInput,
-    GlLink,
-    GlFormGroup,
-    GlFormRadioGroup,
-    GlLoadingIcon,
-    GlIcon,
-  },
-  props: {
-    formOperation: {
-      type: String,
-      required: true,
-    },
-    formData: {
-      type: Object,
-      required: false,
-      default: () => ({
-        title: '',
-        yLabel: '',
-        query: '',
-        unit: '',
-        group: '',
-        legend: '',
-      }),
-      validator: formDataValidator,
-    },
-    metricPersisted: {
-      type: Boolean,
-      required: false,
-      default: false,
-    },
-    validateQueryPath: {
-      type: String,
-      required: true,
-    },
-  },
-  data() {
-    const group = this.formData.group.length ? this.formData.group : queryTypes.business;
-
-    return {
-      queryIsValid: null,
-      queryValidateInFlight: false,
-      ...this.formData,
-      group,
-      errorMessage: '',
-    };
-  },
-  computed: {
-    formIsValid() {
-      return Boolean(
-        this.queryIsValid &&
-          this.title.length &&
-          this.yLabel.length &&
-          this.unit.length &&
-          this.group.length,
-      );
-    },
-    validQueryMsg() {
-      return this.queryIsValid ? s__('Metrics|PromQL query is valid') : '';
-    },
-    invalidQueryMsg() {
-      return !this.queryIsValid ? this.errorMessage : '';
-    },
-  },
-  watch: {
-    formIsValid(value) {
-      this.$emit('formValidation', value);
-    },
-  },
-  beforeMount() {
-    if (this.metricPersisted) {
-      this.validateQuery(this.query);
-    }
-  },
-  methods: {
-    requestValidation(query, cancelToken) {
-      return backOffRequest(() =>
-        axios.post(
-          this.validateQueryPath,
-          {
-            query,
-          },
-          {
-            cancelToken,
-          },
-        ),
-      );
-    },
-    setFormState(isValid, inFlight, message) {
-      this.queryIsValid = isValid;
-      this.queryValidateInFlight = inFlight;
-      this.errorMessage = message;
-    },
-    validateQuery(query) {
-      if (!query) {
-        this.setFormState(null, false, '');
-        return;
-      }
-      this.setFormState(null, true, '');
-      // cancel previously dispatched backoff request
-      if (cancelTokenSource) {
-        cancelTokenSource.cancel();
-      }
-      // Creating a new token for each request because
-      // if a single token is used it can cancel existing requests
-      // as well.
-      cancelTokenSource = axiosCancelToken.source();
-      this.requestValidation(query, cancelTokenSource.token)
-        .then((res) => {
-          const response = res.data;
-          const { valid, error } = response.query;
-          if (response.success) {
-            this.setFormState(valid, false, valid ? '' : error);
-          } else {
-            throw new Error(__('There was an error trying to validate your query'));
-          }
-        })
-        .catch(() => {
-          this.setFormState(
-            false,
-            false,
-            s__('Metrics|There was an error trying to validate your query'),
-          );
-        });
-    },
-    debouncedValidateQuery: debounce(function checkQuery(query) {
-      this.validateQuery(query);
-    }, 500),
-  },
-  csrfToken: csrf.token || '',
-  formGroupOptions: [
-    { text: __('Business'), value: queryTypes.business },
-    { text: __('Response'), value: queryTypes.response },
-    { text: __('System'), value: queryTypes.system },
-  ],
-};
-</script>
-
-<template>
-  <div>
-    <input ref="method" type="hidden" name="_method" :value="formOperation" />
-    <input :value="$options.csrfToken" type="hidden" name="authenticity_token" />
-    <gl-form-group :label="__('Name')" label-for="prometheus_metric_title" label-class="label-bold">
-      <gl-form-input
-        id="prometheus_metric_title"
-        v-model="title"
-        name="prometheus_metric[title]"
-        class="form-control"
-        :placeholder="s__('Metrics|e.g. Throughput')"
-        required
-      />
-      <span class="form-text text-muted">{{ s__('Metrics|Used as a title for the chart') }}</span>
-    </gl-form-group>
-    <gl-form-group :label="__('Type')" label-for="prometheus_metric_group" label-class="label-bold">
-      <gl-form-radio-group
-        id="metric-group"
-        v-model="group"
-        :options="$options.formGroupOptions"
-        :checked="group"
-        name="prometheus_metric[group]"
-      />
-      <span class="form-text text-muted">{{ s__('Metrics|For grouping similar metrics') }}</span>
-    </gl-form-group>
-    <gl-form-group
-      :label="__('Query')"
-      label-for="prometheus_metric_query"
-      label-class="label-bold"
-      :state="queryIsValid"
-    >
-      <gl-form-input
-        id="prometheus_metric_query"
-        v-model.trim="query"
-        name="prometheus_metric[query]"
-        class="form-control"
-        :placeholder="s__('Metrics|e.g. rate(http_requests_total[5m])')"
-        required
-        :state="queryIsValid"
-        @input="debouncedValidateQuery($event)"
-      />
-      <span v-if="queryValidateInFlight" class="form-text text-muted">
-        <gl-loading-icon size="sm" :inline="true" class="mr-1 align-middle" />
-        {{ s__('Metrics|Validating query') }}
-      </span>
-      <slot v-if="!queryValidateInFlight" name="valid-feedback">
-        <span class="form-text cgreen">
-          {{ validQueryMsg }}
-        </span>
-      </slot>
-      <slot v-if="!queryValidateInFlight" name="invalid-feedback">
-        <span class="form-text cred">
-          {{ invalidQueryMsg }}
-        </span>
-      </slot>
-      <span v-show="query.length === 0" class="form-text text-muted">
-        {{ s__('Metrics|Must be a valid PromQL query.') }}
-        <gl-link href="https://prometheus.io/docs/prometheus/latest/querying/basics/" tabindex="-1">
-          {{ s__('Metrics|Prometheus Query Documentation') }}
-          <gl-icon name="external-link" :size="12" />
-        </gl-link>
-      </span>
-    </gl-form-group>
-    <gl-form-group
-      :label="s__('Metrics|Y-axis label')"
-      label-for="prometheus_metric_y_label"
-      label-class="label-bold"
-    >
-      <gl-form-input
-        id="prometheus_metric_y_label"
-        v-model="yLabel"
-        name="prometheus_metric[y_label]"
-        class="form-control"
-        :placeholder="s__('Metrics|e.g. Requests/second')"
-        required
-      />
-      <span class="form-text text-muted">
-        {{
-          s__('Metrics|Label of the y-axis (usually the unit). The x-axis always represents time.')
-        }}
-      </span>
-    </gl-form-group>
-    <gl-form-group
-      :label="s__('Metrics|Unit label')"
-      label-for="prometheus_metric_unit"
-      label-class="label-bold"
-    >
-      <gl-form-input
-        id="prometheus_metric_unit"
-        v-model="unit"
-        name="prometheus_metric[unit]"
-        class="form-control"
-        :placeholder="s__('Metrics|e.g. req/sec')"
-        required
-      />
-    </gl-form-group>
-    <gl-form-group
-      :label="s__('Metrics|Legend label (optional)')"
-      label-for="prometheus_metric_legend"
-      label-class="label-bold"
-    >
-      <gl-form-input
-        id="prometheus_metric_legend"
-        v-model="legend"
-        name="prometheus_metric[legend]"
-        class="form-control"
-        :placeholder="s__('Metrics|e.g. HTTP requests')"
-        required
-      />
-      <span class="form-text text-muted">
-        {{
-          s__(
-            'Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response.',
-          )
-        }}
-      </span>
-    </gl-form-group>
-  </div>
-</template>
diff --git a/app/assets/javascripts/custom_metrics/components/delete_custom_metric_modal.vue b/app/assets/javascripts/custom_metrics/components/delete_custom_metric_modal.vue
deleted file mode 100644
index 9d489ce387f1c..0000000000000
--- a/app/assets/javascripts/custom_metrics/components/delete_custom_metric_modal.vue
+++ /dev/null
@@ -1,54 +0,0 @@
-<script>
-import { GlModal, GlModalDirective, GlButton } from '@gitlab/ui';
-import { s__ } from '~/locale';
-
-export default {
-  components: {
-    GlModal,
-    GlButton,
-  },
-  directives: {
-    'gl-modal': GlModalDirective,
-  },
-  props: {
-    deleteMetricUrl: {
-      type: String,
-      required: true,
-    },
-    csrfToken: {
-      type: String,
-      required: true,
-    },
-  },
-  methods: {
-    onSubmit() {
-      this.$refs.form.submit();
-    },
-  },
-  descriptionText: s__(
-    `Metrics|You're about to permanently delete this metric. This cannot be undone.`,
-  ),
-  modalId: 'delete-custom-metric-modal',
-};
-</script>
-<template>
-  <div class="gl-inline-block gl-float-right mr-3">
-    <gl-button v-gl-modal="$options.modalId" variant="danger" category="primary">
-      {{ __('Delete') }}
-    </gl-button>
-    <gl-modal
-      :title="s__('Metrics|Delete metric?')"
-      :ok-title="s__('Metrics|Delete metric')"
-      :modal-id="$options.modalId"
-      ok-variant="danger"
-      @ok="onSubmit"
-    >
-      {{ $options.descriptionText }}
-
-      <form ref="form" :action="deleteMetricUrl" method="post">
-        <input type="hidden" name="_method" value="delete" />
-        <input :value="csrfToken" type="hidden" name="authenticity_token" />
-      </form>
-    </gl-modal>
-  </div>
-</template>
diff --git a/app/assets/javascripts/custom_metrics/constants.js b/app/assets/javascripts/custom_metrics/constants.js
deleted file mode 100644
index cd2ac6d83906f..0000000000000
--- a/app/assets/javascripts/custom_metrics/constants.js
+++ /dev/null
@@ -1,12 +0,0 @@
-export const queryTypes = {
-  business: 'business',
-  response: 'response',
-  system: 'system',
-};
-
-export const formDataValidator = (val) => {
-  const fieldNames = Object.keys(val);
-  const requiredFields = ['title', 'query', 'yLabel', 'unit', 'group', 'legend'];
-
-  return requiredFields.every((name) => fieldNames.includes(name));
-};
diff --git a/app/assets/javascripts/custom_metrics/index.js b/app/assets/javascripts/custom_metrics/index.js
deleted file mode 100644
index bf572217f5e5a..0000000000000
--- a/app/assets/javascripts/custom_metrics/index.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import Vue from 'vue';
-import { parseBoolean } from '~/lib/utils/common_utils';
-import CustomMetricsForm from './components/custom_metrics_form.vue';
-
-export default () => {
-  // eslint-disable-next-line no-new
-  new Vue({
-    el: '#js-custom-metrics',
-    components: {
-      CustomMetricsForm,
-    },
-    render(createElement) {
-      const domEl = document.querySelector(this.$options.el);
-      const {
-        customMetricsPath,
-        editIntegrationPath,
-        validateQueryPath,
-        title,
-        query,
-        yLabel,
-        unit,
-        group,
-        legend,
-      } = domEl.dataset;
-      let { metricPersisted } = domEl.dataset;
-
-      metricPersisted = parseBoolean(metricPersisted);
-
-      return createElement('custom-metrics-form', {
-        props: {
-          customMetricsPath,
-          metricPersisted,
-          editIntegrationPath,
-          validateQueryPath,
-          formData: {
-            title,
-            query,
-            yLabel,
-            unit,
-            group,
-            legend,
-          },
-        },
-      });
-    },
-  });
-};
diff --git a/app/assets/javascripts/pages/projects/prometheus/metrics/edit/index.js b/app/assets/javascripts/pages/projects/prometheus/metrics/edit/index.js
deleted file mode 100644
index 82856c1c8b9b4..0000000000000
--- a/app/assets/javascripts/pages/projects/prometheus/metrics/edit/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import CustomMetrics from '~/custom_metrics';
-
-CustomMetrics();
diff --git a/app/assets/javascripts/pages/projects/prometheus/metrics/new/index.js b/app/assets/javascripts/pages/projects/prometheus/metrics/new/index.js
deleted file mode 100644
index 82856c1c8b9b4..0000000000000
--- a/app/assets/javascripts/pages/projects/prometheus/metrics/new/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import CustomMetrics from '~/custom_metrics';
-
-CustomMetrics();
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 2fe6ecef82397..27a06d38d4127 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -9925,9 +9925,6 @@ msgstr ""
 msgid "Burnup chart could not be generated due to too many events"
 msgstr ""
 
-msgid "Business"
-msgstr ""
-
 msgid "Business metrics (Custom)"
 msgstr ""
 
@@ -33141,75 +33138,6 @@ msgstr ""
 msgid "Metrics:"
 msgstr ""
 
-msgid "Metrics|Create metric"
-msgstr ""
-
-msgid "Metrics|Delete metric"
-msgstr ""
-
-msgid "Metrics|Delete metric?"
-msgstr ""
-
-msgid "Metrics|Edit metric"
-msgstr ""
-
-msgid "Metrics|For grouping similar metrics"
-msgstr ""
-
-msgid "Metrics|Label of the y-axis (usually the unit). The x-axis always represents time."
-msgstr ""
-
-msgid "Metrics|Legend label (optional)"
-msgstr ""
-
-msgid "Metrics|Must be a valid PromQL query."
-msgstr ""
-
-msgid "Metrics|New metric"
-msgstr ""
-
-msgid "Metrics|PromQL query is valid"
-msgstr ""
-
-msgid "Metrics|Prometheus Query Documentation"
-msgstr ""
-
-msgid "Metrics|There was an error trying to validate your query"
-msgstr ""
-
-msgid "Metrics|Unit label"
-msgstr ""
-
-msgid "Metrics|Used as a title for the chart"
-msgstr ""
-
-msgid "Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response."
-msgstr ""
-
-msgid "Metrics|Validating query"
-msgstr ""
-
-msgid "Metrics|Y-axis label"
-msgstr ""
-
-msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
-msgstr ""
-
-msgid "Metrics|e.g. HTTP requests"
-msgstr ""
-
-msgid "Metrics|e.g. Requests/second"
-msgstr ""
-
-msgid "Metrics|e.g. Throughput"
-msgstr ""
-
-msgid "Metrics|e.g. rate(http_requests_total[5m])"
-msgstr ""
-
-msgid "Metrics|e.g. req/sec"
-msgstr ""
-
 msgid "Mi"
 msgstr ""
 
@@ -43539,9 +43467,6 @@ msgstr ""
 msgid "QualitySummary|Project quality"
 msgstr ""
 
-msgid "Query"
-msgstr ""
-
 msgid "Queued"
 msgstr ""
 
@@ -52457,9 +52382,6 @@ msgstr ""
 msgid "SynthaxHighlightingTheme|Solarized Light"
 msgstr ""
 
-msgid "System"
-msgstr ""
-
 msgid "System default (%{default})"
 msgstr ""
 
@@ -54061,9 +53983,6 @@ msgstr ""
 msgid "There was an error syncing project %{name}"
 msgstr ""
 
-msgid "There was an error trying to validate your query"
-msgstr ""
-
 msgid "There was an error updating the Maintenance Mode Settings"
 msgstr ""
 
diff --git a/spec/frontend/custom_metrics/components/custom_metrics_form_fields_spec.js b/spec/frontend/custom_metrics/components/custom_metrics_form_fields_spec.js
deleted file mode 100644
index d3cdd0d16efab..0000000000000
--- a/spec/frontend/custom_metrics/components/custom_metrics_form_fields_spec.js
+++ /dev/null
@@ -1,245 +0,0 @@
-import { nextTick } from 'vue';
-import { mount } from '@vue/test-utils';
-import MockAdapter from 'axios-mock-adapter';
-import { TEST_HOST } from 'helpers/test_constants';
-import CustomMetricsFormFields from '~/custom_metrics/components/custom_metrics_form_fields.vue';
-import axios from '~/lib/utils/axios_utils';
-import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
-
-describe('custom metrics form fields component', () => {
-  let wrapper;
-  let mockAxios;
-
-  const getNamedInput = (name) => wrapper.element.querySelector(`input[name="${name}"]`);
-  const validateQueryPath = `${TEST_HOST}/mock/path`;
-  const validQueryResponse = { success: true, query: { valid: true, error: '' } };
-  const csrfToken = 'mockToken';
-  const formOperation = 'post';
-  const makeFormData = (data = {}) => ({
-    formData: {
-      title: '',
-      yLabel: '',
-      query: '',
-      unit: '',
-      group: '',
-      legend: '',
-      ...data,
-    },
-  });
-  const mountComponent = (props) => {
-    wrapper = mount(CustomMetricsFormFields, {
-      propsData: {
-        formOperation,
-        validateQueryPath,
-        ...props,
-      },
-      csrfToken,
-    });
-  };
-
-  beforeEach(() => {
-    mockAxios = new MockAdapter(axios);
-  });
-
-  afterEach(() => {
-    mockAxios.restore();
-  });
-
-  it('checks form validity', async () => {
-    mockAxios.onPost(validateQueryPath).reply(HTTP_STATUS_OK, validQueryResponse);
-    mountComponent({
-      metricPersisted: true,
-      ...makeFormData({
-        title: 'title-old',
-        yLabel: 'yLabel',
-        unit: 'unit',
-        group: 'group',
-      }),
-    });
-
-    wrapper.find(`input[name="prometheus_metric[query]"]`).setValue('query');
-    await axios.waitForAll();
-
-    expect(wrapper.emitted('formValidation')).toStrictEqual([[true]]);
-  });
-
-  describe('hidden inputs', () => {
-    beforeEach(() => {
-      mountComponent();
-    });
-
-    it('specifies form operation _method', () => {
-      expect(getNamedInput('_method', 'input').value).toBe('post');
-    });
-
-    it('specifies authenticity token', () => {
-      expect(getNamedInput('authenticity_token', 'input').value).toBe(csrfToken);
-    });
-  });
-
-  describe('name input', () => {
-    const name = 'prometheus_metric[title]';
-
-    it('is empty by default', () => {
-      mountComponent();
-
-      expect(getNamedInput(name).value).toBe('');
-    });
-
-    it('receives a persisted value', () => {
-      const title = 'mockTitle';
-      mountComponent(makeFormData({ title }));
-
-      expect(getNamedInput(name).value).toBe(title);
-    });
-  });
-
-  describe('group input', () => {
-    it('has a default value', () => {
-      mountComponent();
-
-      expect(getNamedInput('prometheus_metric[group]', 'glformradiogroup-stub').value).toBe(
-        'business',
-      );
-    });
-  });
-
-  describe('query input', () => {
-    const queryInputName = 'prometheus_metric[query]';
-
-    it('is empty by default', () => {
-      mountComponent();
-
-      expect(getNamedInput(queryInputName).value).toBe('');
-    });
-
-    it('receives and validates a persisted value', () => {
-      const query = 'persistedQuery';
-      jest.spyOn(axios, 'post');
-
-      mountComponent({ metricPersisted: true, ...makeFormData({ query }) });
-
-      expect(axios.post).toHaveBeenCalledWith(
-        validateQueryPath,
-        { query },
-        expect.objectContaining({ cancelToken: expect.anything() }),
-      );
-      expect(getNamedInput(queryInputName).value).toBe(query);
-      jest.runAllTimers();
-    });
-
-    it('checks validity on user input', async () => {
-      const query = 'changedQuery';
-      mountComponent();
-
-      expect(mockAxios.history.post).toHaveLength(0);
-      const queryInput = wrapper.find(`input[name="${queryInputName}"]`);
-      queryInput.setValue(query);
-
-      await axios.waitForAll();
-      expect(mockAxios.history.post).toHaveLength(1);
-    });
-
-    describe('when query validation is in flight', () => {
-      beforeEach(() => {
-        mountComponent({ metricPersisted: true, ...makeFormData({ query: 'validQuery' }) });
-        mockAxios.onPost(validateQueryPath).reply(HTTP_STATUS_OK, validQueryResponse);
-      });
-
-      it('expect loading message to display', async () => {
-        const queryInput = wrapper.find(`input[name="${queryInputName}"]`);
-        queryInput.setValue('query');
-        await nextTick();
-
-        expect(wrapper.text()).toContain('Validating query');
-      });
-
-      it('expect loading message to disappear', async () => {
-        const queryInput = wrapper.find(`input[name="${queryInputName}"]`);
-        queryInput.setValue('query');
-
-        await axios.waitForAll();
-        expect(wrapper.text()).not.toContain('Validating query');
-      });
-    });
-
-    describe('when query is invalid', () => {
-      const errorMessage = 'mockErrorMessage';
-      const invalidQueryResponse = { success: true, query: { valid: false, error: errorMessage } };
-
-      beforeEach(() => {
-        mockAxios.onPost(validateQueryPath).reply(HTTP_STATUS_OK, invalidQueryResponse);
-        mountComponent({ metricPersisted: true, ...makeFormData({ query: 'invalidQuery' }) });
-        return axios.waitForAll();
-      });
-
-      it('shows invalid query message', () => {
-        expect(wrapper.text()).toContain(errorMessage);
-      });
-    });
-
-    describe('when query is valid', () => {
-      beforeEach(() => {
-        mockAxios.onPost(validateQueryPath).reply(HTTP_STATUS_OK, validQueryResponse);
-        mountComponent({ metricPersisted: true, ...makeFormData({ query: 'validQuery' }) });
-      });
-
-      it('shows valid query message', async () => {
-        await axios.waitForAll();
-
-        expect(wrapper.text()).toContain('PromQL query is valid');
-      });
-    });
-  });
-
-  describe('yLabel input', () => {
-    const name = 'prometheus_metric[y_label]';
-
-    it('is empty by default', () => {
-      mountComponent();
-
-      expect(getNamedInput(name).value).toBe('');
-    });
-
-    it('receives a persisted value', () => {
-      const yLabel = 'mockYLabel';
-      mountComponent(makeFormData({ yLabel }));
-
-      expect(getNamedInput(name).value).toBe(yLabel);
-    });
-  });
-
-  describe('unit input', () => {
-    const name = 'prometheus_metric[unit]';
-
-    it('is empty by default', () => {
-      mountComponent();
-
-      expect(getNamedInput(name).value).toBe('');
-    });
-
-    it('receives a persisted value', () => {
-      const unit = 'mockUnit';
-      mountComponent(makeFormData({ unit }));
-
-      expect(getNamedInput(name).value).toBe(unit);
-    });
-  });
-
-  describe('legend input', () => {
-    const name = 'prometheus_metric[legend]';
-
-    it('is empty by default', () => {
-      mountComponent();
-
-      expect(getNamedInput(name).value).toBe('');
-    });
-
-    it('receives a persisted value', () => {
-      const legend = 'mockLegend';
-      mountComponent(makeFormData({ legend }));
-
-      expect(getNamedInput(name).value).toBe(legend);
-    });
-  });
-});
diff --git a/spec/frontend/custom_metrics/components/custom_metrics_form_spec.js b/spec/frontend/custom_metrics/components/custom_metrics_form_spec.js
deleted file mode 100644
index bc2d918052c90..0000000000000
--- a/spec/frontend/custom_metrics/components/custom_metrics_form_spec.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import CustomMetricsForm from '~/custom_metrics/components/custom_metrics_form.vue';
-import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-
-describe('CustomMetricsForm', () => {
-  let wrapper;
-
-  function mountComponent({
-    metricPersisted = false,
-    formData = {
-      title: '',
-      query: '',
-      yLabel: '',
-      unit: '',
-      group: '',
-      legend: '',
-    },
-  }) {
-    wrapper = shallowMountExtended(CustomMetricsForm, {
-      propsData: {
-        customMetricsPath: '',
-        editIntegrationPath: '',
-        metricPersisted,
-        validateQueryPath: '',
-        formData,
-      },
-    });
-  }
-
-  const findHeader = () => wrapper.findByTestId('metrics-header');
-  const findSaveButton = () => wrapper.findByTestId('metrics-save-button');
-
-  describe('Computed', () => {
-    it('Form button and title text indicate the custom metric is being edited', () => {
-      mountComponent({ metricPersisted: true });
-
-      expect(findHeader().text()).toBe('Edit metric');
-      expect(findSaveButton().text()).toBe('Save Changes');
-    });
-
-    it('Form button and title text indicate the custom metric is being created', () => {
-      mountComponent({ metricPersisted: false });
-
-      expect(findSaveButton().text()).toBe('Create metric');
-      expect(findHeader().text()).toBe('New metric');
-    });
-  });
-});
-- 
GitLab