summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJose Vargas <jvargas@gitlab.com>2019-05-02 17:12:02 -0500
committerJose Vargas <jvargas@gitlab.com>2019-05-02 17:12:02 -0500
commiteb86549f45402c9fabbbf4ba98330ffa9e9da7bb (patch)
treef0eef1807cf18c0c82371fc6db47b4681565e022
parent92cdf61672f25805fff4dd245c7c3eb3749c0d6f (diff)
downloadgitlab-ce-eb86549f45402c9fabbbf4ba98330ffa9e9da7bb.tar.gz
Add fetchDeployments and fetchEnvironments actions
Also moved the monitoring store functions to an utils.js file. Some changes are commented as they're still early WIP
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue44
-rw-r--r--app/assets/javascripts/monitoring/stores/actions.js52
-rw-r--r--app/assets/javascripts/monitoring/stores/getters.js6
-rw-r--r--app/assets/javascripts/monitoring/stores/mutation_types.js8
-rw-r--r--app/assets/javascripts/monitoring/stores/mutations.js23
-rw-r--r--app/assets/javascripts/monitoring/stores/state.js20
-rw-r--r--app/assets/javascripts/monitoring/stores/utils.js84
7 files changed, 197 insertions, 40 deletions
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index c1ca08862d2..4740c37d194 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -103,19 +103,20 @@ export default {
},
},
computed: {
- ...mapState(['groups']),
+ ...mapState(['groups', 'emptyState', 'showEmptyState']),
},
data() {
return {
store: new MonitoringStore(),
state: 'gettingStarted',
- showEmptyState: true,
elWidth: 0,
selectedTimeWindow: '',
};
},
created() {
this.setMetricsEndpoint(this.metricsEndpoint);
+ this.setDeploymentsEndpoint(this.deploymentEndpoint);
+ this.setEnvironmentsEndpoint(this.environmentsEndpoint);
// TODO: Move all of this to the monitoring vuex store/state
this.service = new MonitoringService({
@@ -144,7 +145,8 @@ export default {
.catch(() => Flash(s__('Metrics|There was an error getting deployment information.'))),
];
if (!this.hasMetrics) {
- this.state = 'gettingStarted';
+ // TODO: This should be coming from a mutation/computedGetter
+ // this.state = 'gettingStarted';
} else {
if (this.environmentsEndpoint) {
this.servicePromises.push(
@@ -157,12 +159,8 @@ export default {
);
}
// TODO: Use this instead of the monitoring_service methods
- this.fetchMetricsData(getTimeDiff(this.timeWindows.eightHours))
- .then((resp) => {
- console.log('groups from vuex: ', this.groups);
- }).catch((err) => {
- });
- this.getGraphsData();
+ this.fetchMetricsData(getTimeDiff(this.timeWindows.eightHours));
+ // this.getGraphsData();
sidebarMutationObserver = new MutationObserver(this.onSidebarMutation);
sidebarMutationObserver.observe(document.querySelector('.layout-page'), {
attributes: true,
@@ -172,7 +170,12 @@ export default {
}
},
methods: {
- ...mapActions(['fetchMetricsData', 'setMetricsEndpoint']),
+ ...mapActions([
+ 'fetchMetricsData',
+ 'setMetricsEndpoint',
+ 'setDeploymentsEndpoint',
+ 'setEnvironmentsEndpoint',
+ ]),
getGraphAlerts(queries) {
if (!this.allAlerts) return {};
const metricIdsForChart = queries.map(q => q.metricId);
@@ -182,23 +185,23 @@ export default {
return Object.values(this.getGraphAlerts(queries));
},
getGraphsData() {
- this.state = 'loading';
+ // this.state = 'loading';
Promise.all(this.servicePromises)
.then(() => {
if (this.store.groups.length < 1) {
- this.state = 'noData';
+ // this.state = 'noData';
return;
}
- this.showEmptyState = false;
+ // this.showEmptyState = false; TODO: Delete me
})
.catch(() => {
- this.state = 'unableToConnect';
+ // this.state = 'unableToConnect';
});
},
getGraphsDataWithTime(timeFrame) {
- this.state = 'loading';
- this.showEmptyState = true;
+ // this.state = 'loading';
+ // this.showEmptyState = true; TODO: Delete me!
this.service
.getGraphsData(getTimeDiff(this.timeWindows[timeFrame]))
.then(data => {
@@ -209,7 +212,7 @@ export default {
Flash(s__('Metrics|Not enough data to display'));
})
.finally(() => {
- this.showEmptyState = false;
+ // this.showEmptyState = false; TODO: Delete me!
});
},
onSidebarMutation() {
@@ -226,7 +229,7 @@ export default {
<template>
<div v-if="!showEmptyState" class="prometheus-graphs prepend-top-default">
- <div
+ <!-- <div
v-if="environmentsEndpoint"
class="dropdowns d-flex align-items-center justify-content-between"
>
@@ -308,11 +311,12 @@ export default {
:graph-data="graphData"
/>
</template>
- </graph-group>
+ </graph-group> TODO: Uncomment this once the action that requests all data is in place-->
+ <div><h1>Finished loading...</h1></div>
</div>
<empty-state
v-else
- :selected-state="state"
+ :selected-state="emptyState"
:documentation-path="documentationPath"
:settings-path="settingsPath"
:clusters-path="clustersPath"
diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js
index 75cbe9cb2e8..89d88c97feb 100644
--- a/app/assets/javascripts/monitoring/stores/actions.js
+++ b/app/assets/javascripts/monitoring/stores/actions.js
@@ -29,8 +29,15 @@ function backOffRequest(makeRequestCallback) {
export const setMetricsEndpoint = ({ commit }, metricsEndpoint) => {
commit(types.SET_METRICS_ENDPOINT, metricsEndpoint);
-}
+};
+export const setDeploymentsEndpoint = ({ commit }, deploymentsEndpoint) => {
+ commit(types.SET_DEPLOYMENTS_ENDPOINT, deploymentsEndpoint);
+};
+
+export const setEnvironmentsEndpoint = ({ commit }, environmentsEndpoint ) => {
+ commit(types.SET_ENVIRONMENTS_ENDPOINT, environmentsEndpoint);
+};
export const requestMetricsData = () => ({ commit }) => commit(types.REQUEST_METRICS_DATA);
export const receiveMetricsDataSuccess = ({ commit }, data) =>
@@ -39,17 +46,56 @@ export const receiveMetricsDataFailure = ({ commit }, error) =>
commit(types.RECEIVE_METRICS_DATA_FAILURE, error);
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', {}); // TODO: Do we send an error?
+ dispatch('receiveMetricsDataFailure', null);
createFlash(s__('Metrics|Unexpected metrics data response from prometheus endpoint'));
}
dispatch('receiveMetricsDataSuccess', response.data);
})
.catch(error => {
- dispatch('receiveMetricsDataFailure', error); // TODO: Do we send an error?
+ dispatch('receiveMetricsDataFailure', error);
+ createFlash(s__('Metrics|There was an error while retrieving metrics'));
+ });
+};
+
+export const fetchDeploymentsData = ({ state }) => {
+ 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'),
+ );
+ }
+ return response.deployments;
+ })
+ .catch(() => {
+ createFlash(s__('Metrics|There was an error getting deployment information.'));
+ });
+};
+
+export const fetchEnvironmentsData = ({ state }) => {
+ 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'),
+ );
+ }
+ return response.environments;
+ })
+ .catch(() => {
+ createFlash(s__('Metrics|There was an error getting environments information.'));
});
};
diff --git a/app/assets/javascripts/monitoring/stores/getters.js b/app/assets/javascripts/monitoring/stores/getters.js
index e69de29bb2d..2e43b9175b2 100644
--- a/app/assets/javascripts/monitoring/stores/getters.js
+++ b/app/assets/javascripts/monitoring/stores/getters.js
@@ -0,0 +1,6 @@
+export const getMetricsCount = state => {
+ return state.groups.reduce((count, group) => count + group.metrics.length, 0);
+};
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/app/assets/javascripts/monitoring/stores/mutation_types.js b/app/assets/javascripts/monitoring/stores/mutation_types.js
index a47a5240767..a973f18768e 100644
--- a/app/assets/javascripts/monitoring/stores/mutation_types.js
+++ b/app/assets/javascripts/monitoring/stores/mutation_types.js
@@ -1,5 +1,13 @@
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_SUCESS = 'RECEIVE_ENVIRONMENTS_DATA_SUCESS';
+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';
diff --git a/app/assets/javascripts/monitoring/stores/mutations.js b/app/assets/javascripts/monitoring/stores/mutations.js
index 74cc3bfca16..76edb2e1a22 100644
--- a/app/assets/javascripts/monitoring/stores/mutations.js
+++ b/app/assets/javascripts/monitoring/stores/mutations.js
@@ -1,14 +1,21 @@
import * as types from './mutation_types';
+import { normalizeMetrics, sortMetrics } from './utils';
export default {
- // I understand now
[types.REQUEST_METRICS_DATA](state) {
state.emptyState = 'loading';
- state.showEmptyState = true;
+ // state.showEmptyState = true;
},
- [types.RECEIVE_METRICS_DATA_SUCCESS](state, data) {
- state.groups = data; // TODO: Transform the data from the backend response
- state.showEmptyState = false;
+ [types.RECEIVE_METRICS_DATA_SUCCESS](state, groupData) {
+ state.groups = groupData.map(group => ({
+ ...group,
+ metrics: normalizeMetrics(sortMetrics(group.metrics)),
+ }));
+ if (state.groups.length < 1) {
+ state.emptyState = 'noData';
+ } else {
+ state.showEmptyState = false;
+ }
},
[types.RECEIVE_METRICS_DATA_FAILURE](state, error) {
state.emptyState = error ? 'unableToConnect' : 'noData'; // TODO: use error to deterine the appropiately determine which empty state to use
@@ -17,4 +24,10 @@ export default {
[types.SET_METRICS_ENDPOINT](state, endpoint) {
state.metricsEndpoint = endpoint;
},
+ [types.SET_ENVIRONMENTS_ENDPOINT](state, endpoint) {
+ state.environmentsEndpoint = endpoint;
+ },
+ [types.SET_DEPLOYMENTS_ENDPOINT](state, endpoint) {
+ state.deploymentsEndpoint = endpoint;
+ },
};
diff --git a/app/assets/javascripts/monitoring/stores/state.js b/app/assets/javascripts/monitoring/stores/state.js
index 415c0cb0f30..a8f7ab83d5c 100644
--- a/app/assets/javascripts/monitoring/stores/state.js
+++ b/app/assets/javascripts/monitoring/stores/state.js
@@ -1,21 +1,17 @@
export default () => ({
hasMetrics: false,
showPanels: true,
- documentationPath: null, // From the vuex docs, strings should be null by default
- settingsPath: null, // From dashboard.vue, props
clustersPath: null,
tagsPath: null,
projectPath: null,
+ currentEnvironmentName: null, // Finish props
metricsEndpoint: null,
- deploymentEndpoint: null,
- emptyGettingStartedSvgPath: null,
- emptyLoadingSvgPath: null,
- emptyNoDataSvgPath: null,
- emptyUnableToConnectSvgPath: null,
environmentsEndpoint: null,
- currentEnvironmentName: null, // Finish props
- showEmptyState: true, // From the data
- emptyState: 'loading',
- selectedTimeWindow: null, // finish data section
- groups: [], // NEW
+ deploymentsEndpoint: null,
+ emptyState: 'gettingStarted',
+ showEmptyState: true,
+ selectedTimeWindow: null,
+ groups: [],// From the monitoring store
+ deploymentData: [],
+ environmentsData: [],
});
diff --git a/app/assets/javascripts/monitoring/stores/utils.js b/app/assets/javascripts/monitoring/stores/utils.js
new file mode 100644
index 00000000000..2cce99100ca
--- /dev/null
+++ b/app/assets/javascripts/monitoring/stores/utils.js
@@ -0,0 +1,84 @@
+import _ from 'underscore';
+
+function checkQueryEmptyData(query) {
+ return {
+ ...query,
+ result: query.result.filter(timeSeries => {
+ const newTimeSeries = timeSeries;
+ const hasValue = series =>
+ !Number.isNaN(series[1]) && (series[1] !== null || series[1] !== undefined);
+ const hasNonNullValue = timeSeries.values.find(hasValue);
+
+ newTimeSeries.values = hasNonNullValue ? newTimeSeries.values : [];
+
+ return newTimeSeries.values.length > 0;
+ }),
+ };
+}
+
+function removeTimeSeriesNoData(queries) {
+ return queries.reduce((series, query) => series.concat(checkQueryEmptyData(query)), []);
+}
+
+// Metrics and queries are currently stored 1:1, so `queries` is an array of length one.
+// We want to group queries onto a single chart by title & y-axis label.
+// This function will no longer be required when metrics:queries are 1:many,
+// though there is no consequence if the function stays in use.
+// @param metrics [Array<Object>]
+// Ex) [
+// { id: 1, title: 'title', y_label: 'MB', queries: [{ ...query1Attrs }] },
+// { id: 2, title: 'title', y_label: 'MB', queries: [{ ...query2Attrs }] },
+// { id: 3, title: 'new title', y_label: 'MB', queries: [{ ...query3Attrs }] }
+// ]
+// @return [Array<Object>]
+// Ex) [
+// { title: 'title', y_label: 'MB', queries: [{ metricId: 1, ...query1Attrs },
+// { metricId: 2, ...query2Attrs }] },
+// { title: 'new title', y_label: 'MB', queries: [{ metricId: 3, ...query3Attrs }]}
+// ]
+function groupQueriesByChartInfo(metrics) {
+ const metricsByChart = metrics.reduce((accumulator, metric) => {
+ const { queries, ...chart } = metric;
+ const metricId = chart.id ? chart.id.toString() : null;
+
+ const chartKey = `${chart.title}|${chart.y_label}`;
+ accumulator[chartKey] = accumulator[chartKey] || { ...chart, queries: [] };
+
+ queries.forEach(queryAttrs => accumulator[chartKey].queries.push({ metricId, ...queryAttrs }));
+
+ return accumulator;
+ }, {});
+
+ return Object.values(metricsByChart);
+}
+
+export const sortMetrics = (metrics) => {
+ return _.chain(metrics)
+ .sortBy('title')
+ .sortBy('weight')
+ .value();
+}
+
+export const normalizeMetrics = metrics => {
+ const groupedMetrics = groupQueriesByChartInfo(metrics);
+
+ return groupedMetrics.map(metric => {
+ const queries = metric.queries.map(query => ({
+ ...query,
+ // custom metrics do not require a label, so we should ensure this attribute is defined
+ label: query.label || metric.y_label,
+ result: query.result.map(result => ({
+ ...result,
+ values: result.values.map(([timestamp, value]) => [
+ new Date(timestamp * 1000).toISOString(),
+ Number(value),
+ ]),
+ })),
+ }));
+
+ return {
+ ...metric,
+ queries: removeTimeSeriesNoData(queries),
+ };
+ });
+}