Commit 218dd512 authored by Jose Ivan Vargas's avatar Jose Ivan Vargas Committed by Fatih Acet

Migrate the monitoring dashboard store to vuex

This changes the monitoring javascript store from
an object based to a vuex one
parent 3cac033d
...@@ -8,16 +8,14 @@ import { ...@@ -8,16 +8,14 @@ import {
GlLink, GlLink,
} from '@gitlab/ui'; } from '@gitlab/ui';
import _ from 'underscore'; import _ from 'underscore';
import { mapActions, mapState } from 'vuex';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import '~/vue_shared/mixins/is_ee'; import '~/vue_shared/mixins/is_ee';
import { getParameterValues } from '~/lib/utils/url_utility'; import { getParameterValues } from '~/lib/utils/url_utility';
import Flash from '../../flash';
import MonitoringService from '../services/monitoring_service';
import MonitorAreaChart from './charts/area.vue'; import MonitorAreaChart from './charts/area.vue';
import GraphGroup from './graph_group.vue'; import GraphGroup from './graph_group.vue';
import EmptyState from './empty_state.vue'; import EmptyState from './empty_state.vue';
import MonitoringStore from '../stores/monitoring_store';
import { timeWindows, timeWindowsKeyNames } from '../constants'; import { timeWindows, timeWindowsKeyNames } from '../constants';
import { getTimeDiff } from '../utils'; import { getTimeDiff } from '../utils';
...@@ -128,9 +126,7 @@ export default { ...@@ -128,9 +126,7 @@ export default {
}, },
data() { data() {
return { return {
store: new MonitoringStore(),
state: 'gettingStarted', state: 'gettingStarted',
showEmptyState: true,
elWidth: 0, elWidth: 0,
selectedTimeWindow: '', selectedTimeWindow: '',
selectedTimeWindowKey: '', selectedTimeWindowKey: '',
...@@ -141,13 +137,21 @@ export default { ...@@ -141,13 +137,21 @@ export default {
canAddMetrics() { canAddMetrics() {
return this.customMetricsAvailable && this.customMetricsPath.length; return this.customMetricsAvailable && this.customMetricsPath.length;
}, },
...mapState('monitoringDashboard', [
'groups',
'emptyState',
'showEmptyState',
'environments',
'deploymentData',
]),
}, },
created() { created() {
this.service = new MonitoringService({ this.setEndpoints({
metricsEndpoint: this.metricsEndpoint, metricsEndpoint: this.metricsEndpoint,
deploymentEndpoint: this.deploymentEndpoint,
environmentsEndpoint: this.environmentsEndpoint, environmentsEndpoint: this.environmentsEndpoint,
deploymentsEndpoint: this.deploymentEndpoint,
}); });
this.timeWindows = timeWindows; this.timeWindows = timeWindows;
this.selectedTimeWindowKey = this.selectedTimeWindowKey =
_.escape(getParameterValues('time_window')[0]) || timeWindowsKeyNames.eightHours; _.escape(getParameterValues('time_window')[0]) || timeWindowsKeyNames.eightHours;
...@@ -165,31 +169,11 @@ export default { ...@@ -165,31 +169,11 @@ export default {
} }
}, },
mounted() { mounted() {
const startEndWindow = getTimeDiff(this.timeWindows[this.selectedTimeWindowKey]);
this.servicePromises = [
this.service
.getGraphsData(startEndWindow)
.then(data => this.store.storeMetrics(data))
.catch(() => Flash(s__('Metrics|There was an error while retrieving metrics'))),
this.service
.getDeploymentData()
.then(data => this.store.storeDeploymentData(data))
.catch(() => Flash(s__('Metrics|There was an error getting deployment information.'))),
];
if (!this.hasMetrics) { if (!this.hasMetrics) {
this.state = 'gettingStarted'; this.setGettingStartedEmptyState();
} else { } else {
if (this.environmentsEndpoint) { this.fetchData(getTimeDiff(this.timeWindows.eightHours));
this.servicePromises.push(
this.service
.getEnvironmentsData()
.then(data => this.store.storeEnvironmentsData(data))
.catch(() =>
Flash(s__('Metrics|There was an error getting environments information.')),
),
);
}
this.getGraphsData();
sidebarMutationObserver = new MutationObserver(this.onSidebarMutation); sidebarMutationObserver = new MutationObserver(this.onSidebarMutation);
sidebarMutationObserver.observe(document.querySelector('.layout-page'), { sidebarMutationObserver.observe(document.querySelector('.layout-page'), {
attributes: true, attributes: true,
...@@ -199,6 +183,11 @@ export default { ...@@ -199,6 +183,11 @@ export default {
} }
}, },
methods: { methods: {
...mapActions('monitoringDashboard', [
'fetchData',
'setGettingStartedEmptyState',
'setEndpoints',
]),
getGraphAlerts(queries) { getGraphAlerts(queries) {
if (!this.allAlerts) return {}; if (!this.allAlerts) return {};
const metricIdsForChart = queries.map(q => q.metricId); const metricIdsForChart = queries.map(q => q.metricId);
...@@ -207,21 +196,6 @@ export default { ...@@ -207,21 +196,6 @@ export default {
getGraphAlertValues(queries) { getGraphAlertValues(queries) {
return Object.values(this.getGraphAlerts(queries)); return Object.values(this.getGraphAlerts(queries));
}, },
getGraphsData() {
this.state = 'loading';
Promise.all(this.servicePromises)
.then(() => {
if (this.store.groups.length < 1) {
this.state = 'noData';
return;
}
this.showEmptyState = false;
})
.catch(() => {
this.state = 'unableToConnect';
});
},
hideAddMetricModal() { hideAddMetricModal() {
this.$refs.addMetricModal.hide(); this.$refs.addMetricModal.hide();
}, },
...@@ -263,10 +237,10 @@ export default { ...@@ -263,10 +237,10 @@ export default {
class="prepend-left-10 js-environments-dropdown" class="prepend-left-10 js-environments-dropdown"
toggle-class="dropdown-menu-toggle" toggle-class="dropdown-menu-toggle"
:text="currentEnvironmentName" :text="currentEnvironmentName"
:disabled="store.environmentsData.length === 0" :disabled="environments.length === 0"
> >
<gl-dropdown-item <gl-dropdown-item
v-for="environment in store.environmentsData" v-for="environment in environments"
:key="environment.id" :key="environment.id"
:active="environment.name === currentEnvironmentName" :active="environment.name === currentEnvironmentName"
active-class="is-active" active-class="is-active"
...@@ -336,7 +310,7 @@ export default { ...@@ -336,7 +310,7 @@ export default {
</div> </div>
</div> </div>
<graph-group <graph-group
v-for="(groupData, index) in store.groups" v-for="(groupData, index) in groups"
:key="index" :key="index"
:name="groupData.group" :name="groupData.group"
:show-panels="showPanels" :show-panels="showPanels"
...@@ -345,7 +319,7 @@ export default { ...@@ -345,7 +319,7 @@ export default {
v-for="(graphData, graphIndex) in groupData.metrics" v-for="(graphData, graphIndex) in groupData.metrics"
:key="graphIndex" :key="graphIndex"
:graph-data="graphData" :graph-data="graphData"
:deployment-data="store.deploymentData" :deployment-data="deploymentData"
:thresholds="getGraphAlertValues(graphData.queries)" :thresholds="getGraphAlertValues(graphData.queries)"
:container-width="elWidth" :container-width="elWidth"
group-id="monitor-area-chart" group-id="monitor-area-chart"
...@@ -362,7 +336,7 @@ export default { ...@@ -362,7 +336,7 @@ export default {
</div> </div>
<empty-state <empty-state
v-else v-else
:selected-state="state" :selected-state="emptyState"
:documentation-path="documentationPath" :documentation-path="documentationPath"
:settings-path="settingsPath" :settings-path="settingsPath"
:clusters-path="clustersPath" :clusters-path="clustersPath"
......
import Vue from 'vue'; import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils'; import { parseBoolean } from '~/lib/utils/common_utils';
import Dashboard from 'ee_else_ce/monitoring/components/dashboard.vue'; import Dashboard from 'ee_else_ce/monitoring/components/dashboard.vue';
import store from './stores';
export default (props = {}) => { export default (props = {}) => {
const el = document.getElementById('prometheus-graphs'); const el = document.getElementById('prometheus-graphs');
...@@ -9,6 +10,7 @@ export default (props = {}) => { ...@@ -9,6 +10,7 @@ export default (props = {}) => {
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Vue({ new Vue({
el, el,
store,
render(createElement) { render(createElement) {
return createElement(Dashboard, { return createElement(Dashboard, {
props: { props: {
......
import axios from '../../lib/utils/axios_utils';
import statusCodes from '../../lib/utils/http_status';
import { backOff } from '../../lib/utils/common_utils';
import { s__, __ } from '../../locale';
const MAX_REQUESTS = 3;
function backOffRequest(makeRequestCallback) {
let requestCounter = 0;
return backOff((next, stop) => {
makeRequestCallback()
.then(resp => {
if (resp.status === statusCodes.NO_CONTENT) {
requestCounter += 1;
if (requestCounter < MAX_REQUESTS) {
next();
} else {
stop(new Error(__('Failed to connect to the prometheus server')));
}
} else {
stop(resp);
}
})
.catch(stop);
});
}
export default class MonitoringService {
constructor({ metricsEndpoint, deploymentEndpoint, environmentsEndpoint }) {
this.metricsEndpoint = metricsEndpoint;
this.deploymentEndpoint = deploymentEndpoint;
this.environmentsEndpoint = environmentsEndpoint;
}
getGraphsData(params = {}) {
return backOffRequest(() => axios.get(this.metricsEndpoint, { params }))
.then(resp => resp.data)
.then(response => {
if (!response || !response.data || !response.success) {
throw new Error(s__('Metrics|Unexpected metrics data response from prometheus endpoint'));
}
return response.data;
});
}
getDeploymentData() {
if (!this.deploymentEndpoint) {
return Promise.resolve([]);
}
return backOffRequest(() => axios.get(this.deploymentEndpoint))
.then(resp => resp.data)
.then(response => {
if (!response || !response.deployments) {
throw new Error(
s__('Metrics|Unexpected deployment data response from prometheus endpoint'),
);
}
return response.deployments;
});
}
getEnvironmentsData() {
return axios
.get(this.environmentsEndpoint)
.then(resp => resp.data)
.then(response => {
if (!response || !response.environments) {
throw new Error(
s__('Metrics|There was an error fetching the environments data, please try again'),
);
}
return response.environments;
});
}
}
import * as types from './mutation_types';
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
import statusCodes from '../../lib/utils/http_status';
import { backOff } from '../../lib/utils/common_utils';
import { s__, __ } from '../../locale';
const MAX_REQUESTS = 3;
function backOffRequest(makeRequestCallback) {
let requestCounter = 0;
return backOff((next, stop) => {
makeRequestCallback()
.then(resp => {
if (resp.status === statusCodes.NO_CONTENT) {
requestCounter += 1;
if (requestCounter < MAX_REQUESTS) {
next();
} else {
stop(new Error(__('Failed to connect to the prometheus server')));
}
} else {
stop(resp);
}
})
.catch(stop);
});
}
export const setGettingStartedEmptyState = ({ commit }) => {
commit(types.SET_GETTING_STARTED_EMPTY_STATE);
};
export const setEndpoints = ({ commit }, endpoints) => {
commit(types.SET_ENDPOINTS, endpoints);
};
export const requestMetricsData = ({ commit }) => commit(types.REQUEST_METRICS_DATA);
export const receiveMetricsDataSuccess = ({ commit }, data) =>
commit(types.RECEIVE_METRICS_DATA_SUCCESS, data);
export const receiveMetricsDataFailure = ({ commit }, error) =>
commit(types.RECEIVE_METRICS_DATA_FAILURE, error);
export const receiveDeploymentsDataSuccess = ({ commit }, data) =>
commit(types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS, data);
export const receiveDeploymentsDataFailure = ({ commit }) =>
commit(types.RECEIVE_DEPLOYMENTS_DATA_FAILURE);
export const receiveEnvironmentsDataSuccess = ({ commit }, data) =>
commit(types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS, data);
export const receiveEnvironmentsDataFailure = ({ commit }) =>
commit(types.RECEIVE_ENVIRONMENTS_DATA_FAILURE);
export const fetchData = ({ dispatch }, params) => {
dispatch('fetchMetricsData', params);
dispatch('fetchDeploymentsData');
dispatch('fetchEnvironmentsData');
};
export const fetchMetricsData = ({ state, dispatch }, params) => {
dispatch('requestMetricsData');
return backOffRequest(() => axios.get(state.metricsEndpoint, { params }))
.then(resp => resp.data)
.then(response => {
if (!response || !response.data || !response.success) {
dispatch('receiveMetricsDataFailure', null);
createFlash(s__('Metrics|Unexpected metrics data response from prometheus endpoint'));
}
dispatch('receiveMetricsDataSuccess', response.data);
})
.catch(error => {
dispatch('receiveMetricsDataFailure', error);
createFlash(s__('Metrics|There was an error while retrieving metrics'));
});
};
export const fetchDeploymentsData = ({ state, dispatch }) => {
if (!state.deploymentEndpoint) {
return Promise.resolve([]);
}
return backOffRequest(() => axios.get(state.deploymentEndpoint))
.then(resp => resp.data)
.then(response => {
if (!response || !response.deployments) {
createFlash(s__('Metrics|Unexpected deployment data response from prometheus endpoint'));
}
dispatch('receiveDeploymentsDataSuccess', response.deployments);
})
.catch(() => {
dispatch('receiveDeploymentsDataFailure');
createFlash(s__('Metrics|There was an error getting deployment information.'));
});
};
export const fetchEnvironmentsData = ({ state, dispatch }) => {
if (!state.environmentsEndpoint) {
return Promise.resolve([]);
}
return axios
.get(state.environmentsEndpoint)
.then(resp => resp.data)
.then(response => {
if (!response || !response.environments) {
createFlash(
s__('Metrics|There was an error fetching the environments data, please try again'),
);
}
dispatch('receiveEnvironmentsDataSuccess', response.environments);
})
.catch(() => {
dispatch('receiveEnvironmentsDataFailure');
createFlash(s__('Metrics|There was an error getting environments information.'));
});
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
import mutations from './mutations';
import state from './state';
Vue.use(Vuex);
export const createStore = () =>
new Vuex.Store({
modules: {
monitoringDashboard: {
namespaced: true,
actions,
mutations,
state,
},
},
});
export default createStore();
export const REQUEST_METRICS_DATA = 'REQUEST_METRICS_DATA';
export const RECEIVE_METRICS_DATA_SUCCESS = 'RECEIVE_METRICS_DATA_SUCCESS';
export const RECEIVE_METRICS_DATA_FAILURE = 'RECEIVE_METRICS_DATA_FAILURE';
export const REQUEST_DEPLOYMENTS_DATA = 'REQUEST_DEPLOYMENTS_DATA';
export const RECEIVE_DEPLOYMENTS_DATA_SUCCESS = 'RECEIVE_DEPLOYMENTS_DATA_SUCCESS';
export const RECEIVE_DEPLOYMENTS_DATA_FAILURE = 'RECEIVE_DEPLOYMENTS_DATA_FAILURE';
export const REQUEST_ENVIRONMENTS_DATA = 'REQUEST_ENVIRONMENTS_DATA';
export const RECEIVE_ENVIRONMENTS_DATA_SUCCESS = 'RECEIVE_ENVIRONMENTS_DATA_SUCCESS';
export const RECEIVE_ENVIRONMENTS_DATA_FAILURE = 'RECEIVE_ENVIRONMENTS_DATA_FAILURE';
export const SET_TIME_WINDOW = 'SET_TIME_WINDOW';
export const SET_METRICS_ENDPOINT = 'SET_METRICS_ENDPOINT';
export const SET_ENVIRONMENTS_ENDPOINT = 'SET_ENVIRONMENTS_ENDPOINT';
export const SET_DEPLOYMENTS_ENDPOINT = 'SET_DEPLOYMENTS_ENDPOINT';
export const SET_ENDPOINTS = 'SET_ENDPOINTS';
export const SET_GETTING_STARTED_EMPTY_STATE = 'SET_GETTING_STARTED_EMPTY_STATE';
import * as types from './mutation_types';
import { normalizeMetrics, sortMetrics } from './utils';
export default {
[types.REQUEST_METRICS_DATA](state) {
state.emptyState = 'loading';
state.showEmptyState = true;
},
[types.RECEIVE_METRICS_DATA_SUCCESS](state, groupData) {
state.groups = groupData.map(group => ({
...group,
metrics: normalizeMetrics(sortMetrics(group.metrics)),
}));
if (!state.groups.length) {
state.emptyState = 'noData';
} else {
state.showEmptyState = false;
}
},
[types.RECEIVE_METRICS_DATA_FAILURE](state, error) {
state.emptyState = error ? 'unableToConnect' : 'noData';
state.showEmptyState = true;
},
[types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS](state, deployments) {
state.deploymentData = deployments;
},
[types.RECEIVE_DEPLOYMENTS_DATA_FAILURE](state) {
state.deploymentData = [];
},
[types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS](state, environments) {
state.environments = environments;
},
[types.RECEIVE_ENVIRONMENTS_DATA_FAILURE](state) {
state.environments = [];
},
[types.SET_ENDPOINTS](state, endpoints) {
state.metricsEndpoint = endpoints.metricsEndpoint;
state.environmentsEndpoint = endpoints.environmentsEndpoint;
state.deploymentsEndpoint = endpoints.deploymentsEndpoint;
},
[types.SET_GETTING_STARTED_EMPTY_STATE](state) {
state.emptyState = 'gettingStarted';
},
};
export default () => ({
hasMetrics: false,
showPanels: true,
metricsEndpoint: null,
environmentsEndpoint: null,
deploymentsEndpoint: null,
emptyState: 'gettingStarted',
showEmptyState: true,
groups: [],
deploymentData: [],
environments: [],
});
import _ from 'underscore'; import _ from 'underscore';
function sortMetrics(metrics) {
return _.chain(metrics)
.sortBy('title')
.sortBy('weight')
.value();
}
function checkQueryEmptyData(query) { function checkQueryEmptyData(query) {
return { return {
...query, ...query,
...@@ -59,7 +52,13 @@ function groupQueriesByChartInfo(metrics) { ...@@ -59,7 +52,13 @@ function groupQueriesByChartInfo(metrics) {
return Object.values(metricsByChart); return Object.values(metricsByChart);
} }
function normalizeMetrics(metrics) { export const sortMetrics = metrics =>
_.chain(metrics)
.sortBy('title')
.sortBy('weight')
.value();
export const normalizeMetrics = metrics => {
const groupedMetrics = groupQueriesByChartInfo(metrics); const groupedMetrics = groupQueriesByChartInfo(metrics);
return groupedMetrics.map(metric => { return groupedMetrics.map(metric => {
...@@ -81,31 +80,4 @@ function normalizeMetrics(metrics) { ...@@ -81,31 +80,4 @@ function normalizeMetrics(metrics) {
queries: removeTimeSeriesNoData(queries), queries: removeTimeSeriesNoData(queries),
}; };
}); });
} };
export default class MonitoringStore {
constructor() {
this.groups = [];
this.deploymentData = [];
this.environmentsData = [];
}
storeMetrics(groups = []) {
this.groups = groups.map(group => ({
...group,
metrics: normalizeMetrics(sortMetrics(group.metrics)),
}));
}
storeDeploymentData(deploymentData = []) {
this.deploymentData = deploymentData;
}
storeEnvironmentsData(environmentsData = []) {
this.environmentsData = environmentsData.filter(environment => !!environment.last_deployment);
}
getMetricsCount() {
return this.groups.reduce((count, group) => count + group.metrics.length, 0);
}
}
---
title: Migrate the monitoring dashboard store to vuex
merge_request: 28555
author:
type: other
...@@ -2,7 +2,8 @@ import { shallowMount } from '@vue/test-utils'; ...@@ -2,7 +2,8 @@ import { shallowMount } from '@vue/test-utils';
import { GlAreaChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts'; import { GlAreaChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
import { shallowWrapperContainsSlotText } from 'spec/helpers/vue_test_utils_helper'; import { shallowWrapperContainsSlotText } from 'spec/helpers/vue_test_utils_helper';
import Area from '~/monitoring/components/charts/area.vue'; import Area from '~/monitoring/components/charts/area.vue';
import MonitoringStore from '~/monitoring/stores/monitoring_store'; import { createStore } from '~/monitoring/stores';
import * as types from '~/monitoring/stores/mutation_types';
import MonitoringMock, { deploymentData } from '../mock_data'; import MonitoringMock, { deploymentData } from '../mock_data';
describe('Area component', () => { describe('Area component', () => {
...@@ -13,17 +14,18 @@ describe('Area component', () => { ...@@ -13,17 +14,18 @@ describe('Area component', () => {
let spriteSpy; let spriteSpy;
beforeEach(() => { beforeEach(() => {
const store = new MonitoringStore(); const store = createStore();
store.storeMetrics(MonitoringMock.data);
store.storeDeploymentData(deploymentData);
[mockGraphData] = store.groups[0].metrics; store.commit(`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, MonitoringMock.data);
store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData);
[mockGraphData] = store.state.monitoringDashboard.groups[0].metrics;
areaChart = shallowMount(Area, { areaChart = shallowMount(Area, {
propsData: { propsData: {
graphData: mockGraphData, graphData: mockGraphData,
containerWidth: 0, containerWidth: 0,
deploymentData: store.deploymentData, deploymentData: store.state.monitoringDashboard.deploymentData,
}, },
slots: { slots: {
default: mockWidgets, default: mockWidgets,
......
...@@ -2,8 +2,15 @@ import Vue from 'vue'; ...@@ -2,8 +2,15 @@ import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import Dashboard from '~/monitoring/components/dashboard.vue'; import Dashboard from '~/monitoring/components/dashboard.vue';
import { timeWindows, timeWindowsKeyNames } from '~/monitoring/constants'; import { timeWindows, timeWindowsKeyNames } from '~/monitoring/constants';
import * as types from '~/monitoring/stores/mutation_types';
import { createStore } from '~/monitoring/stores';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { metricsGroupsAPIResponse, mockApiEndpoint, environmentData } from './mock_data'; import {
metricsGroupsAPIResponse,
mockApiEndpoint,
environmentData,
singleGroupResponse,
} from './mock_data';
const propsData = { const propsData = {
hasMetrics: false, hasMetrics: false,
...@@ -30,6 +37,7 @@ export default propsData; ...@@ -30,6 +37,7 @@ export default propsData;
describe('Dashboard', () => { describe('Dashboard', () => {
let DashboardComponent; let DashboardComponent;
let mock; let mock;
let store;
beforeEach(() => { beforeEach(() => {
setFixtures(` setFixtures(`
...@@ -45,6 +53,7 @@ describe('Dashboard', () => { ...@@ -45,6 +53,7 @@ describe('Dashboard', () => {
}, },
}; };
store = createStore();
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
DashboardComponent = Vue.extend(Dashboard); DashboardComponent = Vue.extend(Dashboard);
}); });
...@@ -58,10 +67,11 @@ describe('Dashboard', () => { ...@@ -58,10 +67,11 @@ describe('Dashboard', () => {
const component = new DashboardComponent({ const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'), el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, showTimeWindowDropdown: false }, propsData: { ...propsData, showTimeWindowDropdown: false },
store,
}); });
expect(component.$el.querySelector('.prometheus-graphs')).toBe(null); expect(component.$el.querySelector('.prometheus-graphs')).toBe(null);
expect(component.state).toEqual('gettingStarted'); expect(component.emptyState).toEqual('gettingStarted');
}); });
}); });
...@@ -74,10 +84,11 @@ describe('Dashboard', () => { ...@@ -74,10 +84,11 @@ describe('Dashboard', () => {
const component = new DashboardComponent({ const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'), el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: false }, propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: false },
store,
}); });
Vue.nextTick(() => { Vue.nextTick(() => {
expect(component.state).toEqual('loading'); expect(component.emptyState).toEqual('loading');
done(); done();
}); });
}); });
...@@ -91,6 +102,7 @@ describe('Dashboard', () => { ...@@ -91,6 +102,7 @@ describe('Dashboard', () => {
showLegend: false, showLegend: false,
showTimeWindowDropdown: false, showTimeWindowDropdown: false,
}, },
store,
}); });
setTimeout(() => { setTimeout(() => {
...@@ -110,6 +122,7 @@ describe('Dashboard', () => { ...@@ -110,6 +122,7 @@ describe('Dashboard', () => {
showPanels: false, showPanels: false,
showTimeWindowDropdown: false, showTimeWindowDropdown: false,
}, },
store,
}); });
setTimeout(() => { setTimeout(() => {
...@@ -129,16 +142,24 @@ describe('Dashboard', () => { ...@@ -129,16 +142,24 @@ describe('Dashboard', () => {
showPanels: false, showPanels: false,
showTimeWindowDropdown: false, showTimeWindowDropdown: false,
}, },
store,
}); });
component.store.storeEnvironmentsData(environmentData); component.$store.commit(
`monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
environmentData,
);
component.$store.commit(
`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
singleGroupResponse,
);
setTimeout(() => { setTimeout(() => {
const dropdownMenuEnvironments = component.$el.querySelectorAll( const dropdownMenuEnvironments = component.$el.querySelectorAll(
'.js-environments-dropdown .dropdown-item', '.js-environments-dropdown .dropdown-item',
); );
expect(dropdownMenuEnvironments.length).toEqual(component.store.environmentsData.length); expect(dropdownMenuEnvironments.length).toEqual(component.environments.length);
done(); done();
}); });
}); });
...@@ -152,18 +173,29 @@ describe('Dashboard', () => { ...@@ -152,18 +173,29 @@ describe('Dashboard', () => {
showPanels: false, showPanels: false,
showTimeWindowDropdown: false, showTimeWindowDropdown: false,
}, },
store,
}); });
component.store.storeEnvironmentsData([]); component.$store.commit(
`monitoringDashboard/${types.SET_ENVIRONMENTS_ENDPOINT}`,
'/environments',
);
component.$store.commit(`monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, []);
component.$store.commit(
`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
singleGroupResponse,
);
setTimeout(() => { Vue.nextTick()
const dropdownMenuEnvironments = component.$el.querySelectorAll( .then(() => {
'.js-environments-dropdown .dropdown-item', const dropdownMenuEnvironments = component.$el.querySelectorAll(
); '.js-environments-dropdown .dropdown-item',
);
expect(dropdownMenuEnvironments.length).toEqual(0); expect(dropdownMenuEnvironments.length).toEqual(0);
done(); done();
}); })
.catch(done.fail);
}); });
it('renders the environments dropdown with a single active element', done => { it('renders the environments dropdown with a single active element', done => {
...@@ -175,19 +207,32 @@ describe('Dashboard', () => { ...@@ -175,19 +207,32 @@ describe('Dashboard', () => {
showPanels: false, showPanels: false,
showTimeWindowDropdown: false, showTimeWindowDropdown: false,
}, },
store,
}); });
component.store.storeEnvironmentsData(environmentData); component.$store.commit(
`monitoringDashboard/${types.SET_ENVIRONMENTS_ENDPOINT}`,
'/environments',
);
component.$store.commit(
`monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
environmentData,
);
component.$store.commit(
`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
singleGroupResponse,
);
setTimeout(() => { Vue.nextTick()
const dropdownItems = component.$el.querySelectorAll( .then(() => {
'.js-environments-dropdown .dropdown-item[active="true"]', const dropdownItems = component.$el.querySelectorAll(
); '.js-environments-dropdown .dropdown-item[active="true"]',
);
expect(dropdownItems.length).toEqual(1); expect(dropdownItems.length).toEqual(1);
expect(dropdownItems[0].textContent.trim()).toEqual(component.currentEnvironmentName); done();
done(); })
}); .catch(done.fail);
}); });
it('hides the dropdown', done => { it('hides the dropdown', done => {
...@@ -200,6 +245,7 @@ describe('Dashboard', () => { ...@@ -200,6 +245,7 @@ describe('Dashboard', () => {
environmentsEndpoint: '', environmentsEndpoint: '',
showTimeWindowDropdown: false, showTimeWindowDropdown: false,
}, },
store,
}); });
Vue.nextTick(() => { Vue.nextTick(() => {
...@@ -219,6 +265,7 @@ describe('Dashboard', () => { ...@@ -219,6 +265,7 @@ describe('Dashboard', () => {
showPanels: false, showPanels: false,
showTimeWindowDropdown: false, showTimeWindowDropdown: false,
}, },
store,
}); });
setTimeout(() => { setTimeout(() => {
...@@ -239,6 +286,7 @@ describe('Dashboard', () => { ...@@ -239,6 +286,7 @@ describe('Dashboard', () => {
showPanels: false, showPanels: false,
showTimeWindowDropdown: true, showTimeWindowDropdown: true,
}, },
store,
}); });
const numberOfTimeWindows = Object.keys(timeWindows).length; const numberOfTimeWindows = Object.keys(timeWindows).length;
...@@ -261,6 +309,7 @@ describe('Dashboard', () => { ...@@ -261,6 +309,7 @@ describe('Dashboard', () => {
const component = new DashboardComponent({ const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'), el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: true }, propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: true },
store,
}); });
setTimeout(() => { setTimeout(() => {
...@@ -281,6 +330,7 @@ describe('Dashboard', () => { ...@@ -281,6 +330,7 @@ describe('Dashboard', () => {
const component = new DashboardComponent({ const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'), el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: true }, propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: true },
store,
}); });
Vue.nextTick(() => { Vue.nextTick(() => {
...@@ -310,6 +360,7 @@ describe('Dashboard', () => { ...@@ -310,6 +360,7 @@ describe('Dashboard', () => {
showPanels: false, showPanels: false,
showTimeWindowDropdown: false, showTimeWindowDropdown: false,
}, },
store,
}); });
expect(component.elWidth).toEqual(0); expect(component.elWidth).toEqual(0);
...@@ -352,6 +403,7 @@ describe('Dashboard', () => { ...@@ -352,6 +403,7 @@ describe('Dashboard', () => {
showTimeWindowDropdown: false, showTimeWindowDropdown: false,
externalDashboardPath: '/mockPath', externalDashboardPath: '/mockPath',
}, },
store,
}); });
}); });
...@@ -377,6 +429,7 @@ describe('Dashboard', () => { ...@@ -377,6 +429,7 @@ describe('Dashboard', () => {
showTimeWindowDropdown: false, showTimeWindowDropdown: false,
externalDashboardPath: '', externalDashboardPath: '',
}, },
store,
}); });
}); });
......
// eslint-disable-next-line import/prefer-default-export
export const resetStore = store => {
store.replaceState({
showEmptyState: true,
emptyState: 'loading',
groups: [],
});
};
This diff is collapsed.
import MonitoringStore from '~/monitoring/stores/monitoring_store';
import MonitoringMock, { deploymentData, environmentData } from './mock_data';
describe('MonitoringStore', () => {
const store = new MonitoringStore();
store.storeMetrics(MonitoringMock.data);
it('contains two groups that contains, one of which has two queries sorted by priority', () => {
expect(store.groups).toBeDefined();
expect(store.groups.length).toEqual(2);
expect(store.groups[0].metrics.length).toEqual(2);
});
it('gets the metrics count for every group', () => {
expect(store.getMetricsCount()).toEqual(3);
});
it('contains deployment data', () => {
store.storeDeploymentData(deploymentData);
expect(store.deploymentData).toBeDefined();
expect(store.deploymentData.length).toEqual(3);
expect(typeof store.deploymentData[0]).toEqual('object');
});
it('only stores environment data that contains deployments', () => {
store.storeEnvironmentsData(environmentData);
expect(store.environmentsData.length).toEqual(2);
});
it('removes the data if all the values from a query are not defined', () => {
expect(store.groups[1].metrics[0].queries[0].result.length).toEqual(0);
});
it('assigns queries a metric id', () => {
expect(store.groups[1].metrics[0].queries[0].metricId).toEqual('100');
});
it('assigns metric id of null if metric has no id', () => {
const noId = MonitoringMock.data.map(group => ({
...group,
...{
metrics: group.metrics.map(metric => {
const { id, ...metricWithoutId } = metric;
return metricWithoutId;
}),
},
}));
store.storeMetrics(noId);
store.groups.forEach(group => {
group.metrics.forEach(metric => {
expect(metric.queries.every(query => query.metricId === null)).toBe(true);
});
});
});
});
import axios from '~/lib/utils/axios_utils';
import MockAdapter from 'axios-mock-adapter';
import store from '~/monitoring/stores';
import * as types from '~/monitoring/stores/mutation_types';
import {
fetchDeploymentsData,
fetchEnvironmentsData,
requestMetricsData,
setEndpoints,
setGettingStartedEmptyState,
} from '~/monitoring/stores/actions';
import storeState from '~/monitoring/stores/state';
import testAction from 'spec/helpers/vuex_action_helper';
import { resetStore } from '../helpers';
import { deploymentData, environmentData } from '../mock_data';
describe('Monitoring store actions', () => {
let mock;
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
resetStore(store);
mock.restore();
});
describe('requestMetricsData', () => {
it('sets emptyState to loading', () => {
const commit = jasmine.createSpy();
const { state } = store;
requestMetricsData({ state, commit });
expect(commit).toHaveBeenCalledWith(types.REQUEST_METRICS_DATA);
});
});
describe('fetchDeploymentsData', () => {
it('commits RECEIVE_DEPLOYMENTS_DATA_SUCCESS on error', done => {
const dispatch = jasmine.createSpy();
const { state } = store;
state.deploymentEndpoint = '/success';
mock.onGet(state.deploymentEndpoint).reply(200, {
deployments: deploymentData,
});
fetchDeploymentsData({ state, dispatch })
.then(() => {
expect(dispatch).toHaveBeenCalledWith('receiveDeploymentsDataSuccess', deploymentData);
done();
})
.catch(done.fail);
});
it('commits RECEIVE_DEPLOYMENTS_DATA_FAILURE on error', done => {
const dispatch = jasmine.createSpy();
const { state } = store;
state.deploymentEndpoint = '/error';
mock.onGet(state.deploymentEndpoint).reply(500);
fetchDeploymentsData({ state, dispatch })
.then(() => {
expect(dispatch).toHaveBeenCalledWith('receiveDeploymentsDataFailure');
done();
})
.catch(done.fail);
});
});
describe('fetchEnvironmentsData', () => {
it('commits RECEIVE_ENVIRONMENTS_DATA_SUCCESS on error', done => {
const dispatch = jasmine.createSpy();
const { state } = store;
state.environmentsEndpoint = '/success';
mock.onGet(state.environmentsEndpoint).reply(200, {
environments: environmentData,
});
fetchEnvironmentsData({ state, dispatch })
.then(() => {
expect(dispatch).toHaveBeenCalledWith('receiveEnvironmentsDataSuccess', environmentData);
done();
})
.catch(done.fail);
});
it('commits RECEIVE_ENVIRONMENTS_DATA_FAILURE on error', done => {
const dispatch = jasmine.createSpy();
const { state } = store;
state.environmentsEndpoint = '/error';
mock.onGet(state.environmentsEndpoint).reply(500);
fetchEnvironmentsData({ state, dispatch })
.then(() => {
expect(dispatch).toHaveBeenCalledWith('receiveEnvironmentsDataFailure');
done();
})
.catch(done.fail);
});
});
describe('Set endpoints', () => {
let mockedState;
beforeEach(() => {
mockedState = storeState();
});
it('should commit SET_ENDPOINTS mutation', done => {
testAction(
setEndpoints,
{
metricsEndpoint: 'additional_metrics.json',
deploymentsEndpoint: 'deployments.json',
environmentsEndpoint: 'deployments.json',
},
mockedState,
[
{
type: types.SET_ENDPOINTS,
payload: {
metricsEndpoint: 'additional_metrics.json',
deploymentsEndpoint: 'deployments.json',
environmentsEndpoint: 'deployments.json',
},
},
],
[],
done,
);
});
});
describe('Set empty states', () => {
let mockedState;
beforeEach(() => {
mockedState = storeState();
});
it('should commit SET_METRICS_ENDPOINT mutation', done => {
testAction(
setGettingStartedEmptyState,
null,
mockedState,
[{ type: types.SET_GETTING_STARTED_EMPTY_STATE }],
[],
done,
);
});
});
});
import mutations from '~/monitoring/stores/mutations';
import * as types from '~/monitoring/stores/mutation_types';
import state from '~/monitoring/stores/state';
import { metricsGroupsAPIResponse, deploymentData } from '../mock_data';
describe('Monitoring mutations', () => {
let stateCopy;
beforeEach(() => {
stateCopy = state();
});
describe(types.RECEIVE_METRICS_DATA_SUCCESS, () => {
beforeEach(() => {
stateCopy.groups = [];
const groups = metricsGroupsAPIResponse.data;
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups);
});
it('normalizes values', () => {
const expectedTimestamp = '2017-05-25T08:22:34.925Z';
const expectedValue = 0.0010794445585559514;
const [timestamp, value] = stateCopy.groups[0].metrics[0].queries[0].result[0].values[0];
expect(timestamp).toEqual(expectedTimestamp);
expect(value).toEqual(expectedValue);
});
it('contains two groups that contains, one of which has two queries sorted by priority', () => {
expect(stateCopy.groups).toBeDefined();
expect(stateCopy.groups.length).toEqual(2);
expect(stateCopy.groups[0].metrics.length).toEqual(2);
});
it('assigns queries a metric id', () => {
expect(stateCopy.groups[1].metrics[0].queries[0].metricId).toEqual('100');
});
it('removes the data if all the values from a query are not defined', () => {
expect(stateCopy.groups[1].metrics[0].queries[0].result.length).toEqual(0);
});
it('assigns metric id of null if metric has no id', () => {
stateCopy.groups = [];
const groups = metricsGroupsAPIResponse.data;
const noId = groups.map(group => ({
...group,
...{
metrics: group.metrics.map(metric => {
const { id, ...metricWithoutId } = metric;
return metricWithoutId;
}),
},
}));
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, noId);
stateCopy.groups.forEach(group => {
group.metrics.forEach(metric => {
expect(metric.queries.every(query => query.metricId === null)).toBe(true);
});
});
});
});
describe(types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS, () => {
it('stores the deployment data', () => {
stateCopy.deploymentData = [];
mutations[types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS](stateCopy, deploymentData);
expect(stateCopy.deploymentData).toBeDefined();
expect(stateCopy.deploymentData.length).toEqual(3);
expect(typeof stateCopy.deploymentData[0]).toEqual('object');
});
});
describe('SET_ENDPOINTS', () => {
it('should set all the endpoints', () => {
mutations[types.SET_ENDPOINTS](stateCopy, {
metricsEndpoint: 'additional_metrics.json',
environmentsEndpoint: 'environments.json',
deploymentsEndpoint: 'deployments.json',
});
expect(stateCopy.metricsEndpoint).toEqual('additional_metrics.json');
expect(stateCopy.environmentsEndpoint).toEqual('environments.json');
expect(stateCopy.deploymentsEndpoint).toEqual('deployments.json');
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment