Commit 7f2f3f2c authored by Fatih Acet's avatar Fatih Acet

Merge branch 'jivl-migrate-dashboard-store-vuex' into 'master'

Migrate the monitoring dashboard store to vuex

See merge request gitlab-org/gitlab-ce!28555
parents 3cac033d 218dd512
...@@ -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