Commit 23df40f9 authored by Clement Ho's avatar Clement Ho

Merge branch '31368-support-different-time-windows-for-performance-dashboard' into 'master'

Resolve "Support different time windows for performance dashboard"

Closes #31368

See merge request gitlab-org/gitlab-ce!26047
parents 7ffd7941 976f1feb
......@@ -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;
......
......@@ -238,6 +238,9 @@ msgid_plural "%d closed merge requests"
msgstr[0] ""
msgstr[1] ""
msgid "1 day"
msgstr ""
msgid "1 merged merge request"
msgid_plural "%d merged merge requests"
msgstr[0] ""
......@@ -258,6 +261,9 @@ msgid_plural "%d pipelines"
msgstr[0] ""
msgstr[1] ""
msgid "1 week"
msgstr ""
msgid "1st contribution!"
msgstr ""
......@@ -267,6 +273,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 ""
......@@ -282,6 +297,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 ""
......@@ -5109,6 +5127,12 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
msgid "Metrics|Not enough data to display"
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