summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Knox <psimyn@gmail.com>2019-05-03 08:20:47 +1000
committerSimon Knox <psimyn@gmail.com>2019-05-03 08:20:47 +1000
commitfc4defe6d2196b0e5bf23d196a9ad6496658f425 (patch)
treeaf082f9d368fac0215894ea8c3f734c56ff2052c
parent2760685727d31f53b602e0a49c929fc35d6cbbc4 (diff)
parenteb86549f45402c9fabbbf4ba98330ffa9e9da7bb (diff)
downloadgitlab-ce-fc4defe6d2196b0e5bf23d196a9ad6496658f425.tar.gz
Merge branch '60008-support-for-additional-panel-types-in-dashboard-schema-definition' of gitlab.com:gitlab-org/gitlab-ce into monitoring-vuex
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue49
-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.js9
-rw-r--r--app/assets/javascripts/monitoring/stores/mutations.js107
-rw-r--r--app/assets/javascripts/monitoring/stores/state.js20
-rw-r--r--app/assets/javascripts/monitoring/stores/utils.js84
7 files changed, 195 insertions, 132 deletions
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 32a70f74dc8..3c8082e956e 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({
@@ -141,7 +142,8 @@ export default {
];
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(
@@ -154,15 +156,8 @@ export default {
);
}
// TODO: Use this instead of the monitoring_service methods
- this.servicePromises.push(
- this.fetchMetricsData(getTimeDiff(this.timeWindows.eightHours))
- .then((resp) => {
- console.log('groups from vuex: ', this.groups);
- }).catch((err) => {
- Flash(s__('Metrics|There was an error getting metrics information.'));
- }),
- );
- 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 +167,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 +182,23 @@ export default {
return Object.values(this.getGraphAlerts(queries));
},
getGraphsData() {
- this.state = 'loading';
+ // this.state = 'loading';
Promise.all(this.servicePromises)
.then(() => {
- if (this.groups.length < 1) {
- this.state = 'noData';
+ if (this.store.groups.length < 1) {
+ // 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 +209,7 @@ export default {
Flash(s__('Metrics|Not enough data to display'));
})
.finally(() => {
- this.showEmptyState = false;
+ // this.showEmptyState = false; TODO: Delete me!
});
},
onSidebarMutation() {
@@ -226,7 +226,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 +308,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 317ab1e40a8..a973f18768e 100644
--- a/app/assets/javascripts/monitoring/stores/mutation_types.js
+++ b/app/assets/javascripts/monitoring/stores/mutation_types.js
@@ -1,6 +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_GROUPS = 'SET_GROUPS';
+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 4c7537fd41d..080fdb60953 100644
--- a/app/assets/javascripts/monitoring/stores/mutations.js
+++ b/app/assets/javascripts/monitoring/stores/mutations.js
@@ -1,102 +1,22 @@
import _ from 'underscore';
import * as types from './mutation_types';
-
-function sortMetrics(metrics) {
- return _.chain(metrics)
- .sortBy('title')
- .sortBy('weight')
- .value();
-}
-
-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);
-}
-
-function 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),
- };
- });
-}
-
+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, groups) {
- state.groups = groups.map(group => ({
+ [types.RECEIVE_METRICS_DATA_SUCCESS](state, groupData) {
+ state.groups = groupData.map(group => ({
...group,
metrics: normalizeMetrics(sortMetrics(group.metrics)),
}));
- state.showEmptyState = false;
+ 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
@@ -105,7 +25,10 @@ export default {
[types.SET_METRICS_ENDPOINT](state, endpoint) {
state.metricsEndpoint = endpoint;
},
- [types.SET_GROUPS](state, groups) {
- state.groups = groups;
- }
+ [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),
+ };
+ });
+}