debian-mirror-gitlab/spec/frontend/monitoring/store/actions_spec.js

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1161 lines
33 KiB
JavaScript
Raw Normal View History

2019-09-04 21:01:54 +05:30
import MockAdapter from 'axios-mock-adapter';
2021-03-08 18:12:59 +05:30
import { backoffMockImplementation } from 'helpers/backoff_helper';
2021-03-11 19:13:27 +05:30
import testAction from 'helpers/vuex_action_helper';
2021-09-04 01:27:46 +05:30
import createFlash from '~/flash';
2019-12-26 22:10:19 +05:30
import axios from '~/lib/utils/axios_utils';
2020-04-08 14:13:33 +05:30
import * as commonUtils from '~/lib/utils/common_utils';
2021-03-11 19:13:27 +05:30
import statusCodes from '~/lib/utils/http_status';
2020-04-22 19:07:51 +05:30
import { ENVIRONMENT_AVAILABLE_STATE } from '~/monitoring/constants';
2019-12-26 22:10:19 +05:30
2022-06-21 17:19:12 +05:30
import getAnnotations from '~/monitoring/queries/get_annotations.query.graphql';
import getDashboardValidationWarnings from '~/monitoring/queries/get_dashboard_validation_warnings.query.graphql';
import getEnvironments from '~/monitoring/queries/get_environments.query.graphql';
2020-06-23 00:09:42 +05:30
import { createStore } from '~/monitoring/stores';
2019-09-04 21:01:54 +05:30
import {
2020-07-28 23:09:34 +05:30
setGettingStartedEmptyState,
setInitialState,
setExpandedPanel,
clearExpandedPanel,
filterEnvironments,
2020-05-24 23:13:21 +05:30
fetchData,
2019-09-04 21:01:54 +05:30
fetchDashboard,
receiveMetricsDashboardSuccess,
2020-07-28 23:09:34 +05:30
fetchDashboardData,
fetchPrometheusMetric,
2019-09-04 21:01:54 +05:30
fetchDeploymentsData,
fetchEnvironmentsData,
2020-04-22 19:07:51 +05:30
fetchAnnotations,
2020-07-28 23:09:34 +05:30
fetchDashboardValidationWarnings,
2020-05-24 23:13:21 +05:30
toggleStarredValue,
2020-03-13 15:44:24 +05:30
duplicateSystemDashboard,
2020-06-23 00:09:42 +05:30
updateVariablesAndFetchData,
2020-07-28 23:09:34 +05:30
fetchVariableMetricLabelValues,
2020-10-24 23:57:45 +05:30
fetchPanelPreview,
2019-09-04 21:01:54 +05:30
} from '~/monitoring/stores/actions';
2021-03-11 19:13:27 +05:30
import * as getters from '~/monitoring/stores/getters';
import * as types from '~/monitoring/stores/mutation_types';
import storeState from '~/monitoring/stores/state';
2020-04-22 19:07:51 +05:30
import {
gqClient,
parseEnvironmentsResponse,
parseAnnotationsResponse,
} from '~/monitoring/stores/utils';
2021-03-11 19:13:27 +05:30
import Tracking from '~/tracking';
import { defaultTimeRange } from '~/vue_shared/constants';
import {
metricsDashboardResponse,
metricsDashboardViewModel,
metricsDashboardPanelCount,
} from '../fixture_data';
2019-09-04 21:01:54 +05:30
import {
deploymentData,
environmentData,
2020-04-22 19:07:51 +05:30
annotationsData,
2019-09-30 21:07:59 +05:30
dashboardGitResponse,
2020-04-08 14:13:33 +05:30
mockDashboardsErrorResponse,
2019-09-04 21:01:54 +05:30
} from '../mock_data';
2020-01-01 13:55:28 +05:30
jest.mock('~/flash');
2019-12-26 22:10:19 +05:30
2020-01-01 13:55:28 +05:30
describe('Monitoring store actions', () => {
2020-04-08 14:13:33 +05:30
const { convertObjectPropsToCamelCase } = commonUtils;
2019-09-04 21:01:54 +05:30
let mock;
2020-06-23 00:09:42 +05:30
let store;
let state;
2020-04-08 14:13:33 +05:30
2020-07-28 23:09:34 +05:30
let dispatch;
let commit;
2019-09-04 21:01:54 +05:30
beforeEach(() => {
2020-07-28 23:09:34 +05:30
store = createStore({ getters });
2020-06-23 00:09:42 +05:30
state = store.state.monitoringDashboard;
2019-09-04 21:01:54 +05:30
mock = new MockAdapter(axios);
2020-07-28 23:09:34 +05:30
commit = jest.fn();
dispatch = jest.fn();
2020-10-24 23:57:45 +05:30
jest.spyOn(commonUtils, 'backOff').mockImplementation(backoffMockImplementation);
2019-12-26 22:10:19 +05:30
});
2020-07-28 23:09:34 +05:30
2019-12-26 22:10:19 +05:30
afterEach(() => {
2020-01-01 13:55:28 +05:30
mock.reset();
2020-04-08 14:13:33 +05:30
commonUtils.backOff.mockReset();
2020-01-01 13:55:28 +05:30
createFlash.mockReset();
2019-09-04 21:01:54 +05:30
});
2020-01-01 13:55:28 +05:30
2020-07-28 23:09:34 +05:30
// Setup
describe('setGettingStartedEmptyState', () => {
2022-06-21 17:19:12 +05:30
it('should commit SET_GETTING_STARTED_EMPTY_STATE mutation', () => {
return testAction(
2020-07-28 23:09:34 +05:30
setGettingStartedEmptyState,
2020-05-24 23:13:21 +05:30
null,
state,
[
2020-07-28 23:09:34 +05:30
{
type: types.SET_GETTING_STARTED_EMPTY_STATE,
},
2020-05-24 23:13:21 +05:30
],
2020-07-28 23:09:34 +05:30
[],
2020-05-24 23:13:21 +05:30
);
});
2020-07-28 23:09:34 +05:30
});
2020-05-24 23:13:21 +05:30
2020-07-28 23:09:34 +05:30
describe('setInitialState', () => {
2022-06-21 17:19:12 +05:30
it('should commit SET_INITIAL_STATE mutation', () => {
return testAction(
2020-07-28 23:09:34 +05:30
setInitialState,
{
currentDashboard: '.gitlab/dashboards/dashboard.yml',
deploymentsEndpoint: 'deployments.json',
},
2020-05-24 23:13:21 +05:30
state,
[
2020-07-28 23:09:34 +05:30
{
type: types.SET_INITIAL_STATE,
payload: {
currentDashboard: '.gitlab/dashboards/dashboard.yml',
deploymentsEndpoint: 'deployments.json',
},
},
2020-05-24 23:13:21 +05:30
],
2020-07-28 23:09:34 +05:30
[],
);
2020-05-24 23:13:21 +05:30
});
});
2020-07-28 23:09:34 +05:30
describe('setExpandedPanel', () => {
it('Sets a panel as expanded', () => {
const group = 'group_1';
const panel = { title: 'A Panel' };
2020-04-22 19:07:51 +05:30
return testAction(
2020-07-28 23:09:34 +05:30
setExpandedPanel,
{ group, panel },
2019-12-26 22:10:19 +05:30
state,
2020-07-28 23:09:34 +05:30
[{ type: types.SET_EXPANDED_PANEL, payload: { group, panel } }],
2020-04-22 19:07:51 +05:30
[],
);
2019-09-04 21:01:54 +05:30
});
2020-07-28 23:09:34 +05:30
});
2020-04-22 19:07:51 +05:30
2020-07-28 23:09:34 +05:30
describe('clearExpandedPanel', () => {
it('Clears a panel as expanded', () => {
2020-04-22 19:07:51 +05:30
return testAction(
2020-07-28 23:09:34 +05:30
clearExpandedPanel,
undefined,
2019-12-26 22:10:19 +05:30
state,
2020-07-28 23:09:34 +05:30
[{ type: types.SET_EXPANDED_PANEL, payload: { group: null, panel: null } }],
2020-04-22 19:07:51 +05:30
[],
);
2019-09-04 21:01:54 +05:30
});
});
2020-03-13 15:44:24 +05:30
2020-07-28 23:09:34 +05:30
// All Data
2020-03-13 15:44:24 +05:30
2020-07-28 23:09:34 +05:30
describe('fetchData', () => {
it('dispatches fetchEnvironmentsData and fetchEnvironmentsData', () => {
2020-03-13 15:44:24 +05:30
return testAction(
2020-07-28 23:09:34 +05:30
fetchData,
null,
2020-03-13 15:44:24 +05:30
state,
2020-07-28 23:09:34 +05:30
[],
2020-03-13 15:44:24 +05:30
[
2020-07-28 23:09:34 +05:30
{ type: 'fetchEnvironmentsData' },
{ type: 'fetchDashboard' },
{ type: 'fetchAnnotations' },
2020-03-13 15:44:24 +05:30
],
);
});
2020-07-28 23:09:34 +05:30
it('dispatches when feature metricsDashboardAnnotations is on', () => {
const origGon = window.gon;
window.gon = { features: { metricsDashboardAnnotations: true } };
2020-03-13 15:44:24 +05:30
2020-04-22 19:07:51 +05:30
return testAction(
2020-07-28 23:09:34 +05:30
fetchData,
2020-04-22 19:07:51 +05:30
null,
2020-03-13 15:44:24 +05:30
state,
2020-04-22 19:07:51 +05:30
[],
[
2020-07-28 23:09:34 +05:30
{ type: 'fetchEnvironmentsData' },
{ type: 'fetchDashboard' },
{ type: 'fetchAnnotations' },
2020-04-22 19:07:51 +05:30
],
2020-07-28 23:09:34 +05:30
).then(() => {
window.gon = origGon;
});
2020-03-13 15:44:24 +05:30
});
2020-07-28 23:09:34 +05:30
});
2020-03-13 15:44:24 +05:30
2020-07-28 23:09:34 +05:30
// Metrics dashboard
describe('fetchDashboard', () => {
const response = metricsDashboardResponse;
beforeEach(() => {
state.dashboardEndpoint = '/dashboard';
});
it('on success, dispatches receive and success actions, then fetches dashboard warnings', () => {
document.body.dataset.page = 'projects:environments:metrics';
mock.onGet(state.dashboardEndpoint).reply(200, response);
2020-04-22 19:07:51 +05:30
return testAction(
2020-07-28 23:09:34 +05:30
fetchDashboard,
2020-04-22 19:07:51 +05:30
null,
state,
[],
[
2020-07-28 23:09:34 +05:30
{ type: 'requestMetricsDashboard' },
2020-04-22 19:07:51 +05:30
{
2020-07-28 23:09:34 +05:30
type: 'receiveMetricsDashboardSuccess',
payload: { response },
2020-04-22 19:07:51 +05:30
},
2020-07-28 23:09:34 +05:30
{ type: 'fetchDashboardValidationWarnings' },
2020-04-22 19:07:51 +05:30
],
2020-03-13 15:44:24 +05:30
);
2020-04-22 19:07:51 +05:30
});
2020-07-28 23:09:34 +05:30
describe('on failure', () => {
let result;
beforeEach(() => {
const params = {};
const localGetters = {
fullDashboardPath: store.getters['monitoringDashboard/fullDashboardPath'],
};
result = () => {
mock.onGet(state.dashboardEndpoint).replyOnce(500, mockDashboardsErrorResponse);
return fetchDashboard({ state, commit, dispatch, getters: localGetters }, params);
};
});
2020-03-13 15:44:24 +05:30
2022-06-21 17:19:12 +05:30
it('dispatches a failure', async () => {
await result();
expect(commit).toHaveBeenCalledWith(
types.SET_ALL_DASHBOARDS,
mockDashboardsErrorResponse.all_dashboards,
);
expect(dispatch).toHaveBeenCalledWith(
'receiveMetricsDashboardFailure',
new Error('Request failed with status code 500'),
);
expect(createFlash).toHaveBeenCalled();
2020-07-28 23:09:34 +05:30
});
2022-06-21 17:19:12 +05:30
it('dispatches a failure action when a message is returned', async () => {
await result();
expect(dispatch).toHaveBeenCalledWith(
'receiveMetricsDashboardFailure',
new Error('Request failed with status code 500'),
);
expect(createFlash).toHaveBeenCalledWith({
message: expect.stringContaining(mockDashboardsErrorResponse.message),
});
2020-07-28 23:09:34 +05:30
});
2022-06-21 17:19:12 +05:30
it('does not show a flash error when showErrorBanner is disabled', async () => {
2020-07-28 23:09:34 +05:30
state.showErrorBanner = false;
2022-06-21 17:19:12 +05:30
await result();
expect(dispatch).toHaveBeenCalledWith(
'receiveMetricsDashboardFailure',
new Error('Request failed with status code 500'),
);
expect(createFlash).not.toHaveBeenCalled();
2020-07-28 23:09:34 +05:30
});
2020-04-22 19:07:51 +05:30
});
});
2020-07-28 23:09:34 +05:30
describe('receiveMetricsDashboardSuccess', () => {
it('stores groups', () => {
const response = metricsDashboardResponse;
receiveMetricsDashboardSuccess({ state, commit, dispatch }, { response });
expect(commit).toHaveBeenCalledWith(
types.RECEIVE_METRICS_DASHBOARD_SUCCESS,
2020-04-22 19:07:51 +05:30
2020-07-28 23:09:34 +05:30
metricsDashboardResponse.dashboard,
2020-04-22 19:07:51 +05:30
);
2020-07-28 23:09:34 +05:30
expect(dispatch).toHaveBeenCalledWith('fetchDashboardData');
2019-09-04 21:01:54 +05:30
});
2020-03-13 15:44:24 +05:30
2020-07-28 23:09:34 +05:30
it('sets the dashboards loaded from the repository', () => {
const params = {};
const response = metricsDashboardResponse;
response.all_dashboards = dashboardGitResponse;
receiveMetricsDashboardSuccess(
{
state,
commit,
dispatch,
2020-04-22 19:07:51 +05:30
},
2020-07-28 23:09:34 +05:30
{
response,
params,
2020-04-22 19:07:51 +05:30
},
);
2020-07-28 23:09:34 +05:30
expect(commit).toHaveBeenCalledWith(types.SET_ALL_DASHBOARDS, dashboardGitResponse);
2019-09-04 21:01:54 +05:30
});
});
2020-03-13 15:44:24 +05:30
2020-07-28 23:09:34 +05:30
// Metrics
2020-05-24 23:13:21 +05:30
2020-07-28 23:09:34 +05:30
describe('fetchDashboardData', () => {
2020-05-24 23:13:21 +05:30
beforeEach(() => {
2020-07-28 23:09:34 +05:30
jest.spyOn(Tracking, 'event');
state.timeRange = defaultTimeRange;
2020-05-24 23:13:21 +05:30
});
2022-06-21 17:19:12 +05:30
it('commits empty state when state.groups is empty', async () => {
2020-07-28 23:09:34 +05:30
const localGetters = {
metricsWithData: () => [],
};
2022-06-21 17:19:12 +05:30
await fetchDashboardData({ state, commit, dispatch, getters: localGetters });
expect(Tracking.event).toHaveBeenCalledWith(document.body.dataset.page, 'dashboard_fetch', {
label: 'custom_metrics_dashboard',
property: 'count',
value: 0,
});
expect(dispatch).toHaveBeenCalledTimes(2);
expect(dispatch).toHaveBeenCalledWith('fetchDeploymentsData');
expect(dispatch).toHaveBeenCalledWith('fetchVariableMetricLabelValues', {
defaultQueryParams: {
start_time: expect.any(String),
end_time: expect.any(String),
step: expect.any(Number),
},
});
2020-05-24 23:13:21 +05:30
2022-06-21 17:19:12 +05:30
expect(createFlash).not.toHaveBeenCalled();
2020-07-28 23:09:34 +05:30
});
2020-05-24 23:13:21 +05:30
2022-06-21 17:19:12 +05:30
it('dispatches fetchPrometheusMetric for each panel query', async () => {
2020-07-28 23:09:34 +05:30
state.dashboard.panelGroups = convertObjectPropsToCamelCase(
metricsDashboardResponse.dashboard.panel_groups,
);
2020-05-24 23:13:21 +05:30
2020-07-28 23:09:34 +05:30
const [metric] = state.dashboard.panelGroups[0].panels[0].metrics;
const localGetters = {
metricsWithData: () => [metric.id],
};
2022-06-21 17:19:12 +05:30
await fetchDashboardData({ state, commit, dispatch, getters: localGetters });
expect(dispatch).toHaveBeenCalledWith('fetchPrometheusMetric', {
metric,
defaultQueryParams: {
start_time: expect.any(String),
end_time: expect.any(String),
step: expect.any(Number),
},
});
2020-05-24 23:13:21 +05:30
2022-06-21 17:19:12 +05:30
expect(Tracking.event).toHaveBeenCalledWith(document.body.dataset.page, 'dashboard_fetch', {
label: 'custom_metrics_dashboard',
property: 'count',
value: 1,
});
2020-07-28 23:09:34 +05:30
});
2020-05-24 23:13:21 +05:30
2022-06-21 17:19:12 +05:30
it('dispatches fetchPrometheusMetric for each panel query, handles an error', async () => {
2020-07-28 23:09:34 +05:30
state.dashboard.panelGroups = metricsDashboardViewModel.panelGroups;
const metric = state.dashboard.panelGroups[0].panels[0].metrics[0];
dispatch.mockResolvedValueOnce(); // fetchDeploymentsData
dispatch.mockResolvedValueOnce(); // fetchVariableMetricLabelValues
// Mock having one out of four metrics failing
dispatch.mockRejectedValueOnce(new Error('Error fetching this metric'));
dispatch.mockResolvedValue();
2022-06-21 17:19:12 +05:30
await fetchDashboardData({ state, commit, dispatch });
const defaultQueryParams = {
start_time: expect.any(String),
end_time: expect.any(String),
step: expect.any(Number),
};
expect(dispatch).toHaveBeenCalledTimes(metricsDashboardPanelCount + 2); // plus 1 for deployments
expect(dispatch).toHaveBeenCalledWith('fetchDeploymentsData');
expect(dispatch).toHaveBeenCalledWith('fetchVariableMetricLabelValues', {
defaultQueryParams,
});
expect(dispatch).toHaveBeenCalledWith('fetchPrometheusMetric', {
metric,
defaultQueryParams,
});
expect(createFlash).toHaveBeenCalledTimes(1);
2020-05-24 23:13:21 +05:30
});
});
2020-07-28 23:09:34 +05:30
describe('fetchPrometheusMetric', () => {
const defaultQueryParams = {
start_time: '2019-08-06T12:40:02.184Z',
end_time: '2019-08-06T20:40:02.184Z',
step: 60,
};
let metric;
let data;
let prometheusEndpointPath;
beforeEach(() => {
state = storeState();
[metric] = metricsDashboardViewModel.panelGroups[0].panels[0].metrics;
prometheusEndpointPath = metric.prometheusEndpointPath;
data = {
metricId: metric.metricId,
result: [1582065167.353, 5, 1582065599.353],
};
});
2022-06-21 17:19:12 +05:30
it('commits result', () => {
2020-07-28 23:09:34 +05:30
mock.onGet(prometheusEndpointPath).reply(200, { data }); // One attempt
2022-06-21 17:19:12 +05:30
return testAction(
2020-07-28 23:09:34 +05:30
fetchPrometheusMetric,
{ metric, defaultQueryParams },
2020-06-23 00:09:42 +05:30
state,
2019-09-04 21:01:54 +05:30
[
{
2020-07-28 23:09:34 +05:30
type: types.REQUEST_METRIC_RESULT,
2019-09-04 21:01:54 +05:30
payload: {
2020-07-28 23:09:34 +05:30
metricId: metric.metricId,
2019-09-04 21:01:54 +05:30
},
},
2019-12-26 22:10:19 +05:30
{
2020-07-28 23:09:34 +05:30
type: types.RECEIVE_METRIC_RESULT_SUCCESS,
payload: {
metricId: metric.metricId,
data,
},
2019-12-26 22:10:19 +05:30
},
],
2019-09-04 21:01:54 +05:30
[],
2022-06-21 17:19:12 +05:30
);
2019-09-04 21:01:54 +05:30
});
2020-05-24 23:13:21 +05:30
2020-07-28 23:09:34 +05:30
describe('without metric defined step', () => {
const expectedParams = {
start_time: '2019-08-06T12:40:02.184Z',
end_time: '2019-08-06T20:40:02.184Z',
step: 60,
};
2022-06-21 17:19:12 +05:30
it('uses calculated step', async () => {
2020-07-28 23:09:34 +05:30
mock.onGet(prometheusEndpointPath).reply(200, { data }); // One attempt
2022-06-21 17:19:12 +05:30
await testAction(
2020-07-28 23:09:34 +05:30
fetchPrometheusMetric,
{ metric, defaultQueryParams },
state,
[
{
type: types.REQUEST_METRIC_RESULT,
payload: {
metricId: metric.metricId,
},
},
{
type: types.RECEIVE_METRIC_RESULT_SUCCESS,
payload: {
metricId: metric.metricId,
data,
},
},
],
[],
2022-06-21 17:19:12 +05:30
);
expect(mock.history.get[0].params).toEqual(expectedParams);
2020-07-28 23:09:34 +05:30
});
2020-05-24 23:13:21 +05:30
});
2020-07-28 23:09:34 +05:30
describe('with metric defined step', () => {
beforeEach(() => {
metric.step = 7;
});
const expectedParams = {
start_time: '2019-08-06T12:40:02.184Z',
end_time: '2019-08-06T20:40:02.184Z',
step: 7,
};
2022-06-21 17:19:12 +05:30
it('uses metric step', async () => {
2020-07-28 23:09:34 +05:30
mock.onGet(prometheusEndpointPath).reply(200, { data }); // One attempt
2022-06-21 17:19:12 +05:30
await testAction(
2020-07-28 23:09:34 +05:30
fetchPrometheusMetric,
{ metric, defaultQueryParams },
state,
[
{
type: types.REQUEST_METRIC_RESULT,
payload: {
metricId: metric.metricId,
},
},
{
type: types.RECEIVE_METRIC_RESULT_SUCCESS,
payload: {
metricId: metric.metricId,
data,
},
},
],
[],
2022-06-21 17:19:12 +05:30
);
expect(mock.history.get[0].params).toEqual(expectedParams);
2020-07-28 23:09:34 +05:30
});
2019-09-04 21:01:54 +05:30
});
2020-04-22 19:07:51 +05:30
2022-06-21 17:19:12 +05:30
it('commits failure, when waiting for results and getting a server error', async () => {
2020-10-24 23:57:45 +05:30
mock.onGet(prometheusEndpointPath).reply(500);
2020-01-01 13:55:28 +05:30
2020-07-28 23:09:34 +05:30
const error = new Error('Request failed with status code 500');
2020-01-01 13:55:28 +05:30
2022-06-21 17:19:12 +05:30
await expect(
testAction(
fetchPrometheusMetric,
{ metric, defaultQueryParams },
state,
[
{
type: types.REQUEST_METRIC_RESULT,
payload: {
metricId: metric.metricId,
},
2020-07-28 23:09:34 +05:30
},
2022-06-21 17:19:12 +05:30
{
type: types.RECEIVE_METRIC_RESULT_FAILURE,
payload: {
metricId: metric.metricId,
error,
},
2020-07-28 23:09:34 +05:30
},
2022-06-21 17:19:12 +05:30
],
[],
),
).rejects.toEqual(error);
2020-07-28 23:09:34 +05:30
});
});
2020-01-01 13:55:28 +05:30
2020-07-28 23:09:34 +05:30
// Deployments
2020-01-01 13:55:28 +05:30
2020-07-28 23:09:34 +05:30
describe('fetchDeploymentsData', () => {
it('dispatches receiveDeploymentsDataSuccess on success', () => {
state.deploymentsEndpoint = '/success';
mock.onGet(state.deploymentsEndpoint).reply(200, {
deployments: deploymentData,
2020-01-01 13:55:28 +05:30
});
2020-07-28 23:09:34 +05:30
return testAction(
fetchDeploymentsData,
null,
state,
[],
[{ type: 'receiveDeploymentsDataSuccess', payload: deploymentData }],
);
});
it('dispatches receiveDeploymentsDataFailure on error', () => {
state.deploymentsEndpoint = '/error';
mock.onGet(state.deploymentsEndpoint).reply(500);
return testAction(
fetchDeploymentsData,
null,
state,
[],
[{ type: 'receiveDeploymentsDataFailure' }],
() => {
expect(createFlash).toHaveBeenCalled();
},
);
2019-09-04 21:01:54 +05:30
});
});
2020-04-22 19:07:51 +05:30
2020-07-28 23:09:34 +05:30
// Environments
describe('fetchEnvironmentsData', () => {
2019-09-04 21:01:54 +05:30
beforeEach(() => {
2020-07-28 23:09:34 +05:30
state.projectPath = 'gitlab-org/gitlab-test';
2019-09-04 21:01:54 +05:30
});
2020-04-22 19:07:51 +05:30
2020-07-28 23:09:34 +05:30
it('setting SET_ENVIRONMENTS_FILTER should dispatch fetchEnvironmentsData', () => {
jest.spyOn(gqClient, 'mutate').mockReturnValue({
data: {
project: {
data: {
environments: [],
},
},
},
});
return testAction(
filterEnvironments,
{},
state,
[
{
type: 'SET_ENVIRONMENTS_FILTER',
payload: {},
},
],
[
{
type: 'fetchEnvironmentsData',
},
],
);
});
it('fetch environments data call takes in search param', () => {
const mockMutate = jest.spyOn(gqClient, 'mutate');
const searchTerm = 'Something';
const mutationVariables = {
mutation: getEnvironments,
variables: {
projectPath: state.projectPath,
search: searchTerm,
states: [ENVIRONMENT_AVAILABLE_STATE],
},
};
state.environmentsSearchTerm = searchTerm;
mockMutate.mockResolvedValue({});
2020-04-08 14:13:33 +05:30
2020-07-28 23:09:34 +05:30
return testAction(
fetchEnvironmentsData,
null,
state,
[],
[
{ type: 'requestEnvironmentsData' },
{ type: 'receiveEnvironmentsDataSuccess', payload: [] },
],
() => {
expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
},
2019-09-04 21:01:54 +05:30
);
});
2020-05-24 23:13:21 +05:30
2020-07-28 23:09:34 +05:30
it('dispatches receiveEnvironmentsDataSuccess on success', () => {
jest.spyOn(gqClient, 'mutate').mockResolvedValue({
data: {
project: {
data: {
environments: environmentData,
2020-05-24 23:13:21 +05:30
},
},
},
2020-07-28 23:09:34 +05:30
});
2020-05-24 23:13:21 +05:30
2020-07-28 23:09:34 +05:30
return testAction(
fetchEnvironmentsData,
null,
state,
[],
[
{ type: 'requestEnvironmentsData' },
{
type: 'receiveEnvironmentsDataSuccess',
payload: parseEnvironmentsResponse(environmentData, state.projectPath),
},
],
2020-05-24 23:13:21 +05:30
);
});
2020-07-28 23:09:34 +05:30
it('dispatches receiveEnvironmentsDataFailure on error', () => {
jest.spyOn(gqClient, 'mutate').mockRejectedValue({});
return testAction(
fetchEnvironmentsData,
null,
state,
[],
[{ type: 'requestEnvironmentsData' }, { type: 'receiveEnvironmentsDataFailure' }],
2019-12-26 22:10:19 +05:30
);
2019-09-30 21:07:59 +05:30
});
2019-09-04 21:01:54 +05:30
});
2020-01-01 13:55:28 +05:30
2020-07-28 23:09:34 +05:30
describe('fetchAnnotations', () => {
2019-09-04 21:01:54 +05:30
beforeEach(() => {
2020-07-28 23:09:34 +05:30
state.timeRange = {
start: '2020-04-15T12:54:32.137Z',
end: '2020-08-15T12:54:32.137Z',
2020-01-01 13:55:28 +05:30
};
2020-07-28 23:09:34 +05:30
state.projectPath = 'gitlab-org/gitlab-test';
state.currentEnvironmentName = 'production';
state.currentDashboard = '.gitlab/dashboards/custom_dashboard.yml';
// testAction doesn't have access to getters. The state is passed in as getters
// instead of the actual getters inside the testAction method implementation.
// All methods downstream that needs access to getters will throw and error.
// For that reason, the result of the getter is set as a state variable.
state.fullDashboardPath = store.getters['monitoringDashboard/fullDashboardPath'];
2019-09-04 21:01:54 +05:30
});
2020-04-08 14:13:33 +05:30
2020-07-28 23:09:34 +05:30
it('fetches annotations data and dispatches receiveAnnotationsSuccess', () => {
const mockMutate = jest.spyOn(gqClient, 'mutate');
const mutationVariables = {
mutation: getAnnotations,
variables: {
projectPath: state.projectPath,
environmentName: state.currentEnvironmentName,
dashboardPath: state.currentDashboard,
startingFrom: state.timeRange.start,
},
2020-01-01 13:55:28 +05:30
};
2020-07-28 23:09:34 +05:30
const parsedResponse = parseAnnotationsResponse(annotationsData);
2020-01-01 13:55:28 +05:30
2020-07-28 23:09:34 +05:30
mockMutate.mockResolvedValue({
data: {
project: {
environments: {
nodes: [
{
metricsDashboard: {
annotations: {
nodes: parsedResponse,
},
},
},
],
2020-01-01 13:55:28 +05:30
},
2020-07-28 23:09:34 +05:30
},
},
});
2020-01-01 13:55:28 +05:30
2020-07-28 23:09:34 +05:30
return testAction(
fetchAnnotations,
null,
state,
[],
[{ type: 'receiveAnnotationsSuccess', payload: parsedResponse }],
() => {
expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
},
);
2019-09-04 21:01:54 +05:30
});
2020-01-01 13:55:28 +05:30
2020-07-28 23:09:34 +05:30
it('dispatches receiveAnnotationsFailure if the annotations API call fails', () => {
const mockMutate = jest.spyOn(gqClient, 'mutate');
const mutationVariables = {
mutation: getAnnotations,
variables: {
projectPath: state.projectPath,
environmentName: state.currentEnvironmentName,
dashboardPath: state.currentDashboard,
startingFrom: state.timeRange.start,
},
};
2020-01-01 13:55:28 +05:30
2020-07-28 23:09:34 +05:30
mockMutate.mockRejectedValue({});
2020-01-01 13:55:28 +05:30
2020-07-28 23:09:34 +05:30
return testAction(
fetchAnnotations,
null,
state,
[],
[{ type: 'receiveAnnotationsFailure' }],
() => {
expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
},
);
2020-01-01 13:55:28 +05:30
});
});
2020-04-22 19:07:51 +05:30
2020-07-28 23:09:34 +05:30
describe('fetchDashboardValidationWarnings', () => {
let mockMutate;
let mutationVariables;
2020-04-08 14:13:33 +05:30
2020-07-28 23:09:34 +05:30
beforeEach(() => {
state.projectPath = 'gitlab-org/gitlab-test';
state.currentEnvironmentName = 'production';
state.currentDashboard = '.gitlab/dashboards/dashboard_with_warnings.yml';
// testAction doesn't have access to getters. The state is passed in as getters
// instead of the actual getters inside the testAction method implementation.
// All methods downstream that needs access to getters will throw and error.
// For that reason, the result of the getter is set as a state variable.
state.fullDashboardPath = store.getters['monitoringDashboard/fullDashboardPath'];
mockMutate = jest.spyOn(gqClient, 'mutate');
mutationVariables = {
mutation: getDashboardValidationWarnings,
variables: {
projectPath: state.projectPath,
environmentName: state.currentEnvironmentName,
dashboardPath: state.fullDashboardPath,
},
2020-04-08 14:13:33 +05:30
};
2020-01-01 13:55:28 +05:30
});
2020-07-28 23:09:34 +05:30
it('dispatches receiveDashboardValidationWarningsSuccess with true payload when there are warnings', () => {
mockMutate.mockResolvedValue({
data: {
project: {
id: 'gid://gitlab/Project/29',
environments: {
nodes: [
{
name: 'production',
metricsDashboard: {
path: '.gitlab/dashboards/dashboard_errors_test.yml',
schemaValidationWarnings: ["unit: can't be blank"],
},
},
],
2020-01-01 13:55:28 +05:30
},
},
2020-07-28 23:09:34 +05:30
},
});
return testAction(
fetchDashboardValidationWarnings,
null,
state,
2020-01-01 13:55:28 +05:30
[],
2020-07-28 23:09:34 +05:30
[{ type: 'receiveDashboardValidationWarningsSuccess', payload: true }],
2020-01-01 13:55:28 +05:30
() => {
2020-07-28 23:09:34 +05:30
expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
2020-01-01 13:55:28 +05:30
},
2020-07-28 23:09:34 +05:30
);
2020-01-01 13:55:28 +05:30
});
2020-07-28 23:09:34 +05:30
it('dispatches receiveDashboardValidationWarningsSuccess with false payload when there are no warnings', () => {
mockMutate.mockResolvedValue({
data: {
project: {
id: 'gid://gitlab/Project/29',
environments: {
nodes: [
{
name: 'production',
metricsDashboard: {
path: '.gitlab/dashboards/dashboard_errors_test.yml',
schemaValidationWarnings: [],
},
},
],
2020-04-22 19:07:51 +05:30
},
},
2020-07-28 23:09:34 +05:30
},
2020-04-22 19:07:51 +05:30
});
2020-07-28 23:09:34 +05:30
return testAction(
fetchDashboardValidationWarnings,
null,
state,
[],
[{ type: 'receiveDashboardValidationWarningsSuccess', payload: false }],
() => {
expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
},
);
2020-04-22 19:07:51 +05:30
});
2020-07-28 23:09:34 +05:30
it('dispatches receiveDashboardValidationWarningsSuccess with false payload when the response is empty ', () => {
mockMutate.mockResolvedValue({
data: {
project: null,
},
2020-04-22 19:07:51 +05:30
});
2020-07-28 23:09:34 +05:30
return testAction(
fetchDashboardValidationWarnings,
null,
state,
[],
[{ type: 'receiveDashboardValidationWarningsSuccess', payload: false }],
() => {
expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
},
);
2020-04-22 19:07:51 +05:30
});
2020-07-28 23:09:34 +05:30
it('dispatches receiveDashboardValidationWarningsFailure if the warnings API call fails', () => {
mockMutate.mockRejectedValue({});
2020-01-01 13:55:28 +05:30
2020-07-28 23:09:34 +05:30
return testAction(
fetchDashboardValidationWarnings,
null,
2020-01-01 13:55:28 +05:30
state,
[],
2020-07-28 23:09:34 +05:30
[{ type: 'receiveDashboardValidationWarningsFailure' }],
2020-01-01 13:55:28 +05:30
() => {
2020-07-28 23:09:34 +05:30
expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
2020-01-01 13:55:28 +05:30
},
2020-07-28 23:09:34 +05:30
);
2020-01-01 13:55:28 +05:30
});
2020-07-28 23:09:34 +05:30
});
2020-01-01 13:55:28 +05:30
2020-07-28 23:09:34 +05:30
// Dashboard manipulation
2020-01-01 13:55:28 +05:30
2020-07-28 23:09:34 +05:30
describe('toggleStarredValue', () => {
let unstarredDashboard;
let starredDashboard;
2020-01-01 13:55:28 +05:30
2020-07-28 23:09:34 +05:30
beforeEach(() => {
state.isUpdatingStarredValue = false;
[unstarredDashboard, starredDashboard] = dashboardGitResponse;
});
it('performs no changes if no dashboard is selected', () => {
return testAction(toggleStarredValue, null, state, [], []);
});
it('performs no changes if already changing starred value', () => {
state.selectedDashboard = unstarredDashboard;
state.isUpdatingStarredValue = true;
return testAction(toggleStarredValue, null, state, [], []);
});
it('stars dashboard if it is not starred', () => {
state.selectedDashboard = unstarredDashboard;
mock.onPost(unstarredDashboard.user_starred_path).reply(200);
return testAction(toggleStarredValue, null, state, [
{ type: types.REQUEST_DASHBOARD_STARRING },
{
type: types.RECEIVE_DASHBOARD_STARRING_SUCCESS,
payload: {
newStarredValue: true,
selectedDashboard: unstarredDashboard,
2020-01-01 13:55:28 +05:30
},
2020-07-28 23:09:34 +05:30
},
]);
});
it('unstars dashboard if it is starred', () => {
state.selectedDashboard = starredDashboard;
mock.onPost(starredDashboard.user_starred_path).reply(200);
return testAction(toggleStarredValue, null, state, [
{ type: types.REQUEST_DASHBOARD_STARRING },
{ type: types.RECEIVE_DASHBOARD_STARRING_FAILURE },
]);
2019-09-04 21:01:54 +05:30
});
});
2020-03-13 15:44:24 +05:30
describe('duplicateSystemDashboard', () => {
beforeEach(() => {
state.dashboardsEndpoint = '/dashboards.json';
});
2022-06-21 17:19:12 +05:30
it('Succesful POST request resolves', async () => {
2020-03-13 15:44:24 +05:30
mock.onPost(state.dashboardsEndpoint).reply(statusCodes.CREATED, {
dashboard: dashboardGitResponse[1],
});
2022-06-21 17:19:12 +05:30
await testAction(duplicateSystemDashboard, {}, state, [], []);
expect(mock.history.post).toHaveLength(1);
2020-03-13 15:44:24 +05:30
});
2022-06-21 17:19:12 +05:30
it('Succesful POST request resolves to a dashboard', async () => {
2020-03-13 15:44:24 +05:30
const mockCreatedDashboard = dashboardGitResponse[1];
const params = {
dashboard: 'my-dashboard',
fileName: 'file-name.yml',
branch: 'my-new-branch',
commitMessage: 'A new commit message',
};
const expectedPayload = JSON.stringify({
dashboard: 'my-dashboard',
file_name: 'file-name.yml',
branch: 'my-new-branch',
commit_message: 'A new commit message',
});
mock.onPost(state.dashboardsEndpoint).reply(statusCodes.CREATED, {
dashboard: mockCreatedDashboard,
});
2022-06-21 17:19:12 +05:30
const result = await testAction(duplicateSystemDashboard, params, state, [], []);
expect(mock.history.post).toHaveLength(1);
expect(mock.history.post[0].data).toEqual(expectedPayload);
expect(result).toEqual(mockCreatedDashboard);
2020-03-13 15:44:24 +05:30
});
2022-06-21 17:19:12 +05:30
it('Failed POST request throws an error', async () => {
2020-03-13 15:44:24 +05:30
mock.onPost(state.dashboardsEndpoint).reply(statusCodes.BAD_REQUEST);
2022-06-21 17:19:12 +05:30
await expect(testAction(duplicateSystemDashboard, {}, state, [], [])).rejects.toEqual(
'There was an error creating the dashboard.',
);
expect(mock.history.post).toHaveLength(1);
2020-03-13 15:44:24 +05:30
});
2022-06-21 17:19:12 +05:30
it('Failed POST request throws an error with a description', async () => {
2020-03-13 15:44:24 +05:30
const backendErrorMsg = 'This file already exists!';
mock.onPost(state.dashboardsEndpoint).reply(statusCodes.BAD_REQUEST, {
error: backendErrorMsg,
});
2022-06-21 17:19:12 +05:30
await expect(testAction(duplicateSystemDashboard, {}, state, [], [])).rejects.toEqual(
`There was an error creating the dashboard. ${backendErrorMsg}`,
);
expect(mock.history.post).toHaveLength(1);
2020-03-13 15:44:24 +05:30
});
});
2020-05-24 23:13:21 +05:30
2020-07-28 23:09:34 +05:30
// Variables manipulation
2020-05-24 23:13:21 +05:30
2020-07-28 23:09:34 +05:30
describe('updateVariablesAndFetchData', () => {
2022-06-21 17:19:12 +05:30
it('should commit UPDATE_VARIABLE_VALUE mutation and fetch data', () => {
return testAction(
2020-07-28 23:09:34 +05:30
updateVariablesAndFetchData,
{ pod: 'POD' },
2020-05-24 23:13:21 +05:30
state,
2020-07-28 23:09:34 +05:30
[
{
type: types.UPDATE_VARIABLE_VALUE,
payload: { pod: 'POD' },
},
],
[
{
type: 'fetchDashboardData',
},
],
2020-05-24 23:13:21 +05:30
);
});
});
2020-07-28 23:09:34 +05:30
describe('fetchVariableMetricLabelValues', () => {
const variable = {
type: 'metric_label_values',
name: 'label1',
options: {
prometheusEndpointPath: '/series?match[]=metric_name',
label: 'job',
},
};
const defaultQueryParams = {
start_time: '2019-08-06T12:40:02.184Z',
end_time: '2019-08-06T20:40:02.184Z',
};
beforeEach(() => {
state = {
...state,
timeRange: defaultTimeRange,
variables: [variable],
};
});
it('should commit UPDATE_VARIABLE_METRIC_LABEL_VALUES mutation and fetch data', () => {
const data = [
{
__name__: 'up',
job: 'prometheus',
},
{
__name__: 'up',
job: 'POD',
},
];
mock.onGet('/series?match[]=metric_name').reply(200, {
status: 'success',
data,
});
2020-05-24 23:13:21 +05:30
return testAction(
2020-07-28 23:09:34 +05:30
fetchVariableMetricLabelValues,
{ defaultQueryParams },
2020-05-24 23:13:21 +05:30
state,
2020-07-28 23:09:34 +05:30
[
{
type: types.UPDATE_VARIABLE_METRIC_LABEL_VALUES,
payload: { variable, label: 'job', data },
},
],
2020-05-24 23:13:21 +05:30
[],
);
});
2020-07-28 23:09:34 +05:30
it('should notify the user that dynamic options were not loaded', () => {
mock.onGet('/series?match[]=metric_name').reply(500);
return testAction(fetchVariableMetricLabelValues, { defaultQueryParams }, state, [], []).then(
() => {
expect(createFlash).toHaveBeenCalledTimes(1);
2021-09-04 01:27:46 +05:30
expect(createFlash).toHaveBeenCalledWith({
message: expect.stringContaining('error getting options for variable "label1"'),
});
2020-07-28 23:09:34 +05:30
},
);
});
2020-05-24 23:13:21 +05:30
});
2020-10-24 23:57:45 +05:30
describe('fetchPanelPreview', () => {
const panelPreviewEndpoint = '/builder.json';
const mockYmlContent = 'mock yml content';
beforeEach(() => {
state.panelPreviewEndpoint = panelPreviewEndpoint;
});
it('should not commit or dispatch if payload is empty', () => {
testAction(fetchPanelPreview, '', state, [], []);
});
it('should store the panel and fetch metric results', () => {
const mockPanel = {
title: 'Go heap size',
type: 'area-chart',
};
mock
.onPost(panelPreviewEndpoint, { panel_yaml: mockYmlContent })
.reply(statusCodes.OK, mockPanel);
testAction(
fetchPanelPreview,
mockYmlContent,
state,
[
{ type: types.SET_PANEL_PREVIEW_IS_SHOWN, payload: true },
{ type: types.REQUEST_PANEL_PREVIEW, payload: mockYmlContent },
{ type: types.RECEIVE_PANEL_PREVIEW_SUCCESS, payload: mockPanel },
],
[{ type: 'fetchPanelPreviewMetrics' }],
);
});
it('should display a validation error when the backend cannot process the yml', () => {
const mockErrorMsg = 'Each "metric" must define one of :query or :query_range';
mock
.onPost(panelPreviewEndpoint, { panel_yaml: mockYmlContent })
.reply(statusCodes.UNPROCESSABLE_ENTITY, {
message: mockErrorMsg,
});
testAction(fetchPanelPreview, mockYmlContent, state, [
{ type: types.SET_PANEL_PREVIEW_IS_SHOWN, payload: true },
{ type: types.REQUEST_PANEL_PREVIEW, payload: mockYmlContent },
{ type: types.RECEIVE_PANEL_PREVIEW_FAILURE, payload: mockErrorMsg },
]);
});
it('should display a generic error when the backend fails', () => {
mock.onPost(panelPreviewEndpoint, { panel_yaml: mockYmlContent }).reply(500);
testAction(fetchPanelPreview, mockYmlContent, state, [
{ type: types.SET_PANEL_PREVIEW_IS_SHOWN, payload: true },
{ type: types.REQUEST_PANEL_PREVIEW, payload: mockYmlContent },
{
type: types.RECEIVE_PANEL_PREVIEW_FAILURE,
payload: 'Request failed with status code 500',
},
]);
});
});
2019-09-04 21:01:54 +05:30
});