text: import * as Sentry from '@sentry/browser';
  import createFlash from '~/flash';
  import axios from '~/lib/utils/axios_utils';
  import { convertToFixedRange } from '~/lib/utils/datetime_range';
  import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
  import { s__, sprintf } from '~/locale';
  import { ENVIRONMENT_AVAILABLE_STATE, OVERVIEW_DASHBOARD_PATH, VARIABLE_TYPES } from '../constants';
  import trackDashboardLoad from '../monitoring_tracking_helper';
  import getAnnotations from '../queries/get_annotations.query.graphql';
  import getDashboardValidationWarnings from '../queries/get_dashboard_validation_warnings.query.graphql';
  import getEnvironments from '../queries/get_environments.query.graphql';
  import { getDashboard, getPrometheusQueryData } from '../requests';
  
  import * as types from './mutation_types';
  import {
    gqClient,
    parseEnvironmentsResponse,
    parseAnnotationsResponse,
    removeLeadingSlash,
  } from './utils';
  
  const axiosCancelToken = axios.CancelToken;
  let cancelTokenSource;
  
  function prometheusMetricQueryParams(timeRange) {
    const { start, end } = convertToFixedRange(timeRange);
  
    const timeDiff = (new Date(end) - new Date(start)) / 1000;
    const minStep = 60;
    const queryDataPoints = 600;
  
    return {
      start_time: start,
      end_time: end,
      step: Math.max(minStep, Math.ceil(timeDiff / queryDataPoints)),
    };
  }
  
  /**
   * Extract error messages from API or HTTP request errors.
   *
   * - API errors are in `error.response.data.message`
   * - HTTP (axios) errors are in `error.messsage`
   *
   * @param {Object} error
   * @returns {String} User friendly error message
   */
  function extractErrorMessage(error) {
    const message = error?.response?.data?.message;
    return message ?? error.message;
  }
  
  // Setup
  
  export const setGettingStartedEmptyState = ({ commit }) => {
    commit(types.SET_GETTING_STARTED_EMPTY_STATE);
  };
  
  export const setInitialState = ({ commit }, initialState) => {
    commit(types.SET_INITIAL_STATE, initialState);
  };
  
  export const setTimeRange = ({ commit }, timeRange) => {
    commit(types.SET_TIME_RANGE, timeRange);
  };
  
  export const filterEnvironments = ({ commit, dispatch }, searchTerm) => {
    commit(types.SET_ENVIRONMENTS_FILTER, searchTerm);
    dispatch('fetchEnvironmentsData');
  };
  
  export const setShowErrorBanner = ({ commit }, enabled) => {
    commit(types.SET_SHOW_ERROR_BANNER, enabled);
  };
  
  export const setExpandedPanel = ({ commit }, { group, panel }) => {
    commit(types.SET_EXPANDED_PANEL, { group, panel });
  };
  
  export const clearExpandedPanel = ({ commit }) => {
    commit(types.SET_EXPANDED_PANEL, {
      group: null,
      panel: null,
    });
  };
  
  export const setCurrentDashboard = ({ commit }, { currentDashboard }) => {
    commit(types.SET_CURRENT_DASHBOARD, currentDashboard);
  };
  
  // All Data
  
  /**
   * Fetch all dashboard data.
   *
   * @param {Object} store
   * @returns A promise that resolves when the dashboard
   * skeleton has been loaded.
   */
  export const fetchData = ({ dispatch }) => {
    dispatch('fetchEnvironmentsData');
    dispatch('fetchDashboard');
    dispatch('fetchAnnotations');
  };
  
  // Metrics dashboard
  
  export const fetchDashboard = ({ state, commit, dispatch, getters }) => {
    dispatch('requestMetricsDashboard');
  
    const params = {};
    if (getters.fullDashboardPath) {
      params.dashboard = getters.fullDashboardPath;
    }
  
    return getDashboard(state.dashboardEndpoint, params)
      .then((response) => {
        dispatch('receiveMetricsDashboardSuccess', { response });
        /**
         * After the dashboard is fetched, there can be non-blocking invalid syntax
         * in the dashboard file. This call will fetch such syntax warnings
         * and surface a warning on the UI. If the invalid syntax is blocking,
         * the `fetchDashboard` returns a 404 with error messages that are displayed
         * on the UI.
         */
        dispatch('fetchDashboardValidationWarnings');
      })
      .catch((error) => {
        Sentry.captureException(error);
  
        commit(types.SET_ALL_DASHBOARDS, error.response?.data?.all_dashboards ?? []);
        dispatch('receiveMetricsDashboardFailure', error);
  
        if (state.showErrorBanner) {
          if (error.response.data && error.response.data.message) {
            const { message } = error.response.data;
            createFlash({
              message: sprintf(
                s__('Metrics|There was an error while retrieving metrics. %{message}'),
                { message },
                false,
              ),
            });
          } else {
            createFlash({
              message: s__('Metrics|There was an error while retrieving metrics'),
            });
          }
        }
      });
  };
  
  export const requestMetricsDashboard = ({ commit }) => {
    commit(types.REQUEST_METRICS_DASHBOARD);
  };
  export const receiveMetricsDashboardSuccess = ({ commit, dispatch }, { response }) => {
    const { all_dashboards, dashboard, metrics_data } = response;
  
    commit(types.SET_ALL_DASHBOARDS, all_dashboards);
    commit(types.RECEIVE_METRICS_DASHBOARD_SUCCESS, dashboard);
    commit(types.SET_ENDPOINTS, convertObjectPropsToCamelCase(metrics_data));
  
    return dispatch('fetchDashboardData');
  };
  export const receiveMetricsDashboardFailure = ({ commit }, error) => {
    commit(types.RECEIVE_METRICS_DASHBOARD_FAILURE, error);
  };
  
  // Metrics
  
  /**
   * Loads timeseries data: Prometheus data points and deployment data from the project
   * @param {Object} Vuex store
   */
  export const fetchDashboardData = ({ state, dispatch, getters }) => {
    dispatch('fetchDeploymentsData');
  
    if (!state.timeRange) {
      createFlash({
        message: s__(`Metrics|Invalid time range, please verify.`),
        type: 'warning',
      });
      return Promise.reject();
    }
  
    // Time range params must be pre-calculated once for all metrics and options
    // A subsequent call, may calculate a different time range
    const defaultQueryParams = prometheusMetricQueryParams(state.timeRange);
  
    dispatch('fetchVariableMetricLabelValues', { defaultQueryParams });
  
    const promises = [];
    state.dashboard.panelGroups.forEach((group) => {
      group.panels.forEach((panel) => {
        panel.metrics.forEach((metric) => {
          promises.push(dispatch('fetchPrometheusMetric', { metric, defaultQueryParams }));
        });
      });
    });
  
    return Promise.all(promises)
      .then(() => {
        const dashboardType = getters.fullDashboardPath === '' ? 'default' : 'custom';
        trackDashboardLoad({
          label: `${dashboardType}_metrics_dashboard`,
          value: getters.metricsWithData().length,
        });
      })
      .catch(() => {
        createFlash({
          message: s__(`Metrics|There was an error while retrieving metrics`),
          type: 'warning',
        });
      });
  };
  
  /**
   * Returns list of metrics in data.result
   * {"status":"success", "data":{"resultType":"matrix","result":[]}}
   *
   * @param {metric} metric
   */
  export const fetchPrometheusMetric = (
    { commit, state, getters },
    { metric, defaultQueryParams },
  ) => {
    let queryParams = { ...defaultQueryParams };
    if (metric.step) {
      queryParams.step = metric.step;
    }
  
    if (state.variables.length > 0) {
      queryParams = {
        ...queryParams,
        ...getters.getCustomVariablesParams,
      };
    }
  
    commit(types.REQUEST_METRIC_RESULT, { metricId: metric.metricId });
  
    return getPrometheusQueryData(metric.prometheusEndpointPath, queryParams)
      .then((data) => {
        commit(types.RECEIVE_METRIC_RESULT_SUCCESS, { metricId: metric.metricId, data });
      })
      .catch((error) => {
        Sentry.captureException(error);
  
        commit(types.RECEIVE_METRIC_RESULT_FAILURE, { metricId: metric.metricId, error });
        // Continue to throw error so the dashboard can notify using createFlash
        throw error;
      });
  };
  
  // Deployments
  
  export const fetchDeploymentsData = ({ state, dispatch }) => {
    if (!state.deploymentsEndpoint) {
      return Promise.resolve([]);
    }
    return axios
      .get(state.deploymentsEndpoint)
      .then((resp) => resp.data)
      .then((response) => {
        if (!response || !response.deployments) {
          createFlash({
            message: s__('Metrics|Unexpected deployment data response from prometheus endpoint'),
          });
        }
  
        dispatch('receiveDeploymentsDataSuccess', response.deployments);
      })
      .catch((error) => {
        Sentry.captureException(error);
        dispatch('receiveDeploymentsDataFailure');
        createFlash({
          message: s__('Metrics|There was an error getting deployment information.'),
        });
      });
  };
  export const receiveDeploymentsDataSuccess = ({ commit }, data) => {
    commit(types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS, data);
  };
  export const receiveDeploymentsDataFailure = ({ commit }) => {
    commit(types.RECEIVE_DEPLOYMENTS_DATA_FAILURE);
  };
  
  // Environments
  
  export const fetchEnvironmentsData = ({ state, dispatch }) => {
    dispatch('requestEnvironmentsData');
    return gqClient
      .mutate({
        mutation: getEnvironments,
        variables: {
          projectPath: removeLeadingSlash(state.projectPath),
          search: state.environmentsSearchTerm,
          states: [ENVIRONMENT_AVAILABLE_STATE],
        },
      })
      .then((resp) =>
        parseEnvironmentsResponse(resp.data?.project?.data?.environments, state.projectPath),
      )
      .then((environments) => {
        if (!environments) {
          createFlash({
            message: s__(
              'Metrics|There was an error fetching the environments data, please try again',
            ),
          });
        }
  
        dispatch('receiveEnvironmentsDataSuccess', environments);
      })
      .catch((err) => {
        Sentry.captureException(err);
        dispatch('receiveEnvironmentsDataFailure');
        createFlash({
          message: s__('Metrics|There was an error getting environments information.'),
        });
      });
  };
  export const requestEnvironmentsData = ({ commit }) => {
    commit(types.REQUEST_ENVIRONMENTS_DATA);
  };
  export const receiveEnvironmentsDataSuccess = ({ commit }, data) => {
    commit(types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS, data);
  };
  export const receiveEnvironmentsDataFailure = ({ commit }) => {
    commit(types.RECEIVE_ENVIRONMENTS_DATA_FAILURE);
  };
  
  export const fetchAnnotations = ({ state, dispatch, getters }) => {
    const { start } = convertToFixedRange(state.timeRange);
    const dashboardPath = getters.fullDashboardPath || OVERVIEW_DASHBOARD_PATH;
    return gqClient
      .mutate({
        mutation: getAnnotations,
        variables: {
          projectPath: removeLeadingSlash(state.projectPath),
          environmentName: state.currentEnvironmentName,
          dashboardPath,
          startingFrom: start,
        },
      })
      .then(
        (resp) => resp.data?.project?.environments?.nodes?.[0].metricsDashboard?.annotations.nodes,
      )
      .then(parseAnnotationsResponse)
      .then((annotations) => {
        if (!annotations) {
          createFlash({
            message: s__('Metrics|There was an error fetching annotations. Please try again.'),
          });
        }
  
        dispatch('receiveAnnotationsSuccess', annotations);
      })
      .catch((err) => {
        Sentry.captureException(err);
        dispatch('receiveAnnotationsFailure');
        createFlash({
          message: s__('Metrics|There was an error getting annotations information.'),
        });
      });
  };
  
  export const receiveAnnotationsSuccess = ({ commit }, data) =>
    commit(types.RECEIVE_ANNOTATIONS_SUCCESS, data);
  export const receiveAnnotationsFailure = ({ commit }) => commit(types.RECEIVE_ANNOTATIONS_FAILURE);
  
  export const fetchDashboardValidationWarnings = ({ state, dispatch, getters }) => {
    /**
     * Normally, the overview dashboard won't throw any validation warnings.
     *
     * However, if a bug sneaks into the overview dashboard making it invalid,
     * this might come handy for our clients
     */
    const dashboardPath = getters.fullDashboardPath || OVERVIEW_DASHBOARD_PATH;
    return gqClient
      .mutate({
        mutation: getDashboardValidationWarnings,
        variables: {
          projectPath: removeLeadingSlash(state.projectPath),
          environmentName: state.currentEnvironmentName,
          dashboardPath,
        },
      })
      .then((resp) => resp.data?.project?.environments?.nodes?.[0]?.metricsDashboard || undefined)
      .then(({ schemaValidationWarnings } = {}) => {
        const hasWarnings = schemaValidationWarnings && schemaValidationWarnings.length !== 0;
        /**
         * The payload of the dispatch is a boolean, because at the moment a standard
         * warning message is shown instead of the warnings the BE returns
         */
        dispatch('receiveDashboardValidationWarningsSuccess', hasWarnings || false);
      })
      .catch((err) => {
        Sentry.captureException(err);
        dispatch('receiveDashboardValidationWarningsFailure');
        createFlash({
          message: s__(
            'Metrics|There was an error getting dashboard validation warnings information.',
          ),
        });
      });
  };
  
  export const receiveDashboardValidationWarningsSuccess = ({ commit }, hasWarnings) =>
    commit(types.RECEIVE_DASHBOARD_VALIDATION_WARNINGS_SUCCESS, hasWarnings);
  export const receiveDashboardValidationWarningsFailure = ({ commit }) =>
    commit(types.RECEIVE_DASHBOARD_VALIDATION_WARNINGS_FAILURE);
  
  // Dashboard manipulation
  
  export const toggleStarredValue = ({ commit, state, getters }) => {
    const { selectedDashboard } = getters;
  
    if (state.isUpdatingStarredValue) {
      // Prevent repeating requests for the same change
      return;
    }
    if (!selectedDashboard) {
      return;
    }
  
    const method = selectedDashboard.starred ? 'DELETE' : 'POST';
    const url = selectedDashboard.user_starred_path;
    const newStarredValue = !selectedDashboard.starred;
  
    commit(types.REQUEST_DASHBOARD_STARRING);
  
    axios({
      url,
      method,
    })
      .then(() => {
        commit(types.RECEIVE_DASHBOARD_STARRING_SUCCESS, { selectedDashboard, newStarredValue });
      })
      .catch(() => {
        commit(types.RECEIVE_DASHBOARD_STARRING_FAILURE);
      });
  };
  
  /**
   * Set a new array of metrics to a panel group
   * @param {*} data An object containing
   *   - `key` with a unique panel key
   *   - `metrics` with the metrics array
   */
  export const setPanelGroupMetrics = ({ commit }, data) => {
    commit(types.SET_PANEL_GROUP_METRICS, data);
  };
  
  export const duplicateSystemDashboard = ({ state }, payload) => {
    const params = {
      dashboard: payload.dashboard,
      file_name: payload.fileName,
      branch: payload.branch,
      commit_message: payload.commitMessage,
    };
  
    return axios
      .post(state.dashboardsEndpoint, params)
      .then((response) => response.data)
      .then((data) => data.dashboard)
      .catch((error) => {
        Sentry.captureException(error);
  
        const { response } = error;
  
        if (response && response.data && response.data.error) {
          throw sprintf(s__('Metrics|There was an error creating the dashboard. %{error}'), {
            error: response.data.error,
          });
        } else {
          throw s__('Metrics|There was an error creating the dashboard.');
        }
      });
  };
  
  // Variables manipulation
  
  export const updateVariablesAndFetchData = ({ commit, dispatch }, updatedVariable) => {
    commit(types.UPDATE_VARIABLE_VALUE, updatedVariable);
  
    return dispatch('fetchDashboardData');
  };
  
  export const fetchVariableMetricLabelValues = ({ state, commit }, { defaultQueryParams }) => {
    const { start_time, end_time } = defaultQueryParams;
    const optionsRequests = [];
  
    state.variables.forEach((variable) => {
      if (variable.type === VARIABLE_TYPES.metric_label_values) {
        const { prometheusEndpointPath, label } = variable.options;
  
        const optionsRequest = getPrometheusQueryData(prometheusEndpointPath, {
          start_time,
          end_time,
        })
          .then((data) => {
            commit(types.UPDATE_VARIABLE_METRIC_LABEL_VALUES, { variable, label, data });
          })
          .catch(() => {
            createFlash({
              message: sprintf(
                s__('Metrics|There was an error getting options for variable "%{name}".'),
                {
                  name: variable.name,
                },
              ),
            });
          });
        optionsRequests.push(optionsRequest);
      }
    });
  
    return Promise.all(optionsRequests);
  };
  
  // Panel Builder
  
  export const setPanelPreviewTimeRange = ({ commit }, timeRange) => {
    commit(types.SET_PANEL_PREVIEW_TIME_RANGE, timeRange);
  };
  
  export const fetchPanelPreview = ({ state, commit, dispatch }, panelPreviewYml) => {
    if (!panelPreviewYml) {
      return null;
    }
  
    commit(types.SET_PANEL_PREVIEW_IS_SHOWN, true);
    commit(types.REQUEST_PANEL_PREVIEW, panelPreviewYml);
  
    return axios
      .post(state.panelPreviewEndpoint, { panel_yaml: panelPreviewYml })
      .then(({ data }) => {
        commit(types.RECEIVE_PANEL_PREVIEW_SUCCESS, data);
  
        dispatch('fetchPanelPreviewMetrics');
      })
      .catch((error) => {
        commit(types.RECEIVE_PANEL_PREVIEW_FAILURE, extractErrorMessage(error));
      });
  };
  
  export const fetchPanelPreviewMetrics = ({ state, commit }) => {
    if (cancelTokenSource) {
      cancelTokenSource.cancel();
    }
    cancelTokenSource = axiosCancelToken.source();
  
    const defaultQueryParams = prometheusMetricQueryParams(state.panelPreviewTimeRange);
  
    state.panelPreviewGraphData.metrics.forEach((metric, index) => {
      commit(types.REQUEST_PANEL_PREVIEW_METRIC_RESULT, { index });
  
      const params = { ...defaultQueryParams };
      if (metric.step) {
        params.step = metric.step;
      }
      return getPrometheusQueryData(metric.prometheusEndpointPath, params, {
        cancelToken: cancelTokenSource.token,
      })
        .then((data) => {
          commit(types.RECEIVE_PANEL_PREVIEW_METRIC_RESULT_SUCCESS, { index, data });
        })
        .catch((error) => {
          Sentry.captureException(error);
  
          commit(types.RECEIVE_PANEL_PREVIEW_METRIC_RESULT_FAILURE, { index, error });
          // Continue to throw error so the panel builder can notify using createFlash
          throw error;
        });
    });
  };
  