Commit 515ecb89 authored by Jose Ivan Vargas's avatar Jose Ivan Vargas Committed by Clement Ho

Add support for time windows for the performance dashbooards

The performance dashboards will now display the data
from a set amount of time windows that are defined
on a constants file
parent df1d62c2
......@@ -10,6 +10,8 @@ import MonitorAreaChart from './charts/area.vue';
import GraphGroup from './graph_group.vue';
import EmptyState from './empty_state.vue';
import MonitoringStore from '../stores/monitoring_store';
import { timeWindows } from '../constants';
import { getTimeDiff } from '../utils';
const sidebarAnimationDuration = 150;
let sidebarMutationObserver;
......@@ -88,6 +90,10 @@ export default {
type: String,
required: true,
},
showTimeWindowDropdown: {
type: Boolean,
required: true,
},
},
data() {
return {
......@@ -95,6 +101,7 @@ export default {
state: 'gettingStarted',
showEmptyState: true,
elWidth: 0,
selectedTimeWindow: '',
};
},
created() {
......@@ -103,6 +110,9 @@ export default {
deploymentEndpoint: this.deploymentEndpoint,
environmentsEndpoint: this.environmentsEndpoint,
});
this.timeWindows = timeWindows;
this.selectedTimeWindow = this.timeWindows.eightHours;
},
beforeDestroy() {
if (sidebarMutationObserver) {
......@@ -166,33 +176,73 @@ export default {
this.state = 'unableToConnect';
});
},
getGraphsDataWithTime(timeFrame) {
this.state = 'loading';
this.showEmptyState = true;
this.service
.getGraphsData(getTimeDiff(this.timeWindows[timeFrame]))
.then(data => {
this.store.storeMetrics(data);
this.selectedTimeWindow = this.timeWindows[timeFrame];
})
.catch(() => {
Flash(s__('Metrics|Not enough data to display'));
})
.finally(() => {
this.showEmptyState = false;
});
},
onSidebarMutation() {
setTimeout(() => {
this.elWidth = this.$el.clientWidth;
}, sidebarAnimationDuration);
},
activeTimeWindow(key) {
return this.timeWindows[key] === this.selectedTimeWindow;
},
},
};
</script>
<template>
<div v-if="!showEmptyState" class="prometheus-graphs prepend-top-default">
<div v-if="environmentsEndpoint" class="environments d-flex align-items-center">
<strong>{{ s__('Metrics|Environment') }}</strong>
<gl-dropdown
class="prepend-left-10 js-environments-dropdown"
toggle-class="dropdown-menu-toggle"
:text="currentEnvironmentName"
:disabled="store.environmentsData.length === 0"
>
<gl-dropdown-item
v-for="environment in store.environmentsData"
:key="environment.id"
:active="environment.name === currentEnvironmentName"
active-class="is-active"
>{{ environment.name }}</gl-dropdown-item
<div
v-if="environmentsEndpoint"
class="dropdowns d-flex align-items-center justify-content-between"
>
<div class="d-flex align-items-center">
<strong>{{ s__('Metrics|Environment') }}</strong>
<gl-dropdown
class="prepend-left-10 js-environments-dropdown"
toggle-class="dropdown-menu-toggle"
:text="currentEnvironmentName"
:disabled="store.environmentsData.length === 0"
>
<gl-dropdown-item
v-for="environment in store.environmentsData"
:key="environment.id"
:active="environment.name === currentEnvironmentName"
active-class="is-active"
>{{ environment.name }}</gl-dropdown-item
>
</gl-dropdown>
</div>
<div v-if="showTimeWindowDropdown" class="d-flex align-items-center">
<strong>{{ s__('Metrics|Show last') }}</strong>
<gl-dropdown
class="prepend-left-10 js-time-window-dropdown"
toggle-class="dropdown-menu-toggle"
:text="selectedTimeWindow"
>
</gl-dropdown>
<gl-dropdown-item
v-for="(value, key) in timeWindows"
:key="key"
:active="activeTimeWindow(key)"
@click="getGraphsDataWithTime(key)"
>{{ value }}</gl-dropdown-item
>
</gl-dropdown>
</div>
</div>
<graph-group
v-for="(groupData, index) in store.groups"
......
import { __ } from '~/locale';
export const chartHeight = 300;
export const graphTypes = {
......@@ -7,3 +9,14 @@ export const graphTypes = {
export const lineTypes = {
default: 'solid',
};
export const timeWindows = {
thirtyMinutes: __('30 minutes'),
threeHours: __('3 hours'),
eightHours: __('8 hours'),
oneDay: __('1 day'),
threeDays: __('3 days'),
oneWeek: __('1 week'),
};
export const msPerMinute = 60000;
......@@ -14,6 +14,7 @@ export default () => {
props: {
...el.dataset,
hasMetrics: parseBoolean(el.dataset.hasMetrics),
showTimeWindowDropdown: gon.features.metricsTimeWindow,
},
});
},
......
......@@ -32,11 +32,11 @@ export default class MonitoringService {
this.environmentsEndpoint = environmentsEndpoint;
}
getGraphsData() {
return backOffRequest(() => axios.get(this.metricsEndpoint))
getGraphsData(params = {}) {
return backOffRequest(() => axios.get(this.metricsEndpoint, { params }))
.then(resp => resp.data)
.then(response => {
if (!response || !response.data) {
if (!response || !response.data || !response.success) {
throw new Error(s__('Metrics|Unexpected metrics data response from prometheus endpoint'));
}
return response.data;
......
import { timeWindows, msPerMinute } from './constants';
/**
* method that converts a predetermined time window to minutes
* defaults to 8 hours as the default option
* @param {String} timeWindow - The time window to convert to minutes
* @returns {number} The time window in minutes
*/
const getTimeDifferenceMinutes = timeWindow => {
switch (timeWindow) {
case timeWindows.thirtyMinutes:
return 30;
case timeWindows.threeHours:
return 60 * 3;
case timeWindows.oneDay:
return 60 * 24 * 1;
case timeWindows.threeDays:
return 60 * 24 * 3;
case timeWindows.oneWeek:
return 60 * 24 * 7 * 1;
default:
return 60 * 8;
}
};
export const getTimeDiff = selectedTimeWindow => {
const end = Date.now();
const timeDifferenceMinutes = getTimeDifferenceMinutes(selectedTimeWindow);
const start = new Date(end - timeDifferenceMinutes * msPerMinute).getTime();
return { start, end };
};
export default {};
......@@ -204,7 +204,7 @@
}
.prometheus-graphs {
.environments {
.dropdowns {
.dropdown-menu-toggle {
svg {
position: absolute;
......
......@@ -41,6 +41,7 @@ describe('Dashboard', () => {
hasMetrics: true,
prometheusAlertsAvailable: true,
alertsEndpoint: '/endpoint',
showTimeWindowDropdown: false,
},
});
});
......@@ -62,6 +63,7 @@ describe('Dashboard', () => {
hasMetrics: true,
prometheusAlertsAvailable: false,
alertsEndpoint: '/endpoint',
showTimeWindowDropdown: false,
},
});
});
......
......@@ -309,6 +309,9 @@ msgid_plural "%d closed merge requests"
msgstr[0] ""
msgstr[1] ""
msgid "1 day"
msgstr ""
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
......@@ -344,6 +347,9 @@ msgid_plural "%d users"
msgstr[0] ""
msgstr[1] ""
msgid "1 week"
msgstr ""
msgid "1st contribution!"
msgstr ""
......@@ -353,6 +359,15 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
msgid "3 days"
msgstr ""
msgid "3 hours"
msgstr ""
msgid "30 minutes"
msgstr ""
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
......@@ -368,6 +383,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
msgid "8 hours"
msgstr ""
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
......@@ -6884,12 +6902,18 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
msgid "Metrics|Not enough data to display"
msgstr ""
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
msgstr ""
......
import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import Dashboard from '~/monitoring/components/dashboard.vue';
import { timeWindows } from '~/monitoring/constants';
import axios from '~/lib/utils/axios_utils';
import { metricsGroupsAPIResponse, mockApiEndpoint, environmentData } from './mock_data';
......@@ -50,7 +51,7 @@ describe('Dashboard', () => {
it('shows a getting started empty state when no metrics are present', () => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData,
propsData: { ...propsData, showTimeWindowDropdown: false },
});
expect(component.$el.querySelector('.prometheus-graphs')).toBe(null);
......@@ -66,7 +67,7 @@ describe('Dashboard', () => {
it('shows up a loading state', done => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true },
propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: false },
});
Vue.nextTick(() => {
......@@ -78,7 +79,12 @@ describe('Dashboard', () => {
it('hides the legend when showLegend is false', done => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true, showLegend: false },
propsData: {
...propsData,
hasMetrics: true,
showLegend: false,
showTimeWindowDropdown: false,
},
});
setTimeout(() => {
......@@ -92,7 +98,12 @@ describe('Dashboard', () => {
it('hides the group panels when showPanels is false', done => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true, showPanels: false },
propsData: {
...propsData,
hasMetrics: true,
showPanels: false,
showTimeWindowDropdown: false,
},
});
setTimeout(() => {
......@@ -106,7 +117,12 @@ describe('Dashboard', () => {
it('renders the environments dropdown with a number of environments', done => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true, showPanels: false },
propsData: {
...propsData,
hasMetrics: true,
showPanels: false,
showTimeWindowDropdown: false,
},
});
component.store.storeEnvironmentsData(environmentData);
......@@ -124,7 +140,12 @@ describe('Dashboard', () => {
it('hides the environments dropdown list when there is no environments', done => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true, showPanels: false },
propsData: {
...propsData,
hasMetrics: true,
showPanels: false,
showTimeWindowDropdown: false,
},
});
component.store.storeEnvironmentsData([]);
......@@ -142,7 +163,12 @@ describe('Dashboard', () => {
it('renders the environments dropdown with a single is-active element', done => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true, showPanels: false },
propsData: {
...propsData,
hasMetrics: true,
showPanels: false,
showTimeWindowDropdown: false,
},
});
component.store.storeEnvironmentsData(environmentData);
......@@ -166,6 +192,7 @@ describe('Dashboard', () => {
hasMetrics: true,
showPanels: false,
environmentsEndpoint: '',
showTimeWindowDropdown: false,
},
});
......@@ -176,6 +203,51 @@ describe('Dashboard', () => {
done();
});
});
it('does not show the time window dropdown when the feature flag is not set', done => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
...propsData,
hasMetrics: true,
showPanels: false,
showTimeWindowDropdown: false,
},
});
setTimeout(() => {
const timeWindowDropdown = component.$el.querySelector('.js-time-window-dropdown');
expect(timeWindowDropdown).toBeNull();
done();
});
});
it('renders the time window dropdown with a set of options', done => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
...propsData,
hasMetrics: true,
showPanels: false,
showTimeWindowDropdown: true,
},
});
const numberOfTimeWindows = Object.keys(timeWindows).length;
setTimeout(() => {
const timeWindowDropdown = component.$el.querySelector('.js-time-window-dropdown');
const timeWindowDropdownEls = component.$el.querySelectorAll(
'.js-time-window-dropdown .dropdown-item',
);
expect(timeWindowDropdown).not.toBeNull();
expect(timeWindowDropdownEls.length).toEqual(numberOfTimeWindows);
done();
});
});
});
describe('when the window resizes', () => {
......@@ -191,7 +263,12 @@ describe('Dashboard', () => {
it('sets elWidth to page width when the sidebar is resized', done => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true, showPanels: false },
propsData: {
...propsData,
hasMetrics: true,
showPanels: false,
showTimeWindowDropdown: false,
},
});
expect(component.elWidth).toEqual(0);
......
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