diff options
author | Clement Ho <clemmakesapps@gmail.com> | 2019-04-06 01:18:11 +0300 |
---|---|---|
committer | Clement Ho <clemmakesapps@gmail.com> | 2019-04-06 01:18:11 +0300 |
commit | 23df40f99f24ce15297c8fa9a9a90156883e8d0a (patch) | |
tree | 956ef544d4c0cc00faa21a01d29a3d2efb7cebec | |
parent | 7ffd794140ce22b7366d558bde1c97c502104ea3 (diff) | |
parent | 976f1feb28265bfc427a00e7031275f49aa7f877 (diff) |
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
-rw-r--r-- | app/assets/javascripts/monitoring/components/dashboard.vue | 80 | ||||
-rw-r--r-- | app/assets/javascripts/monitoring/constants.js | 13 | ||||
-rw-r--r-- | app/assets/javascripts/monitoring/monitoring_bundle.js | 1 | ||||
-rw-r--r-- | app/assets/javascripts/monitoring/services/monitoring_service.js | 6 | ||||
-rw-r--r-- | app/assets/javascripts/monitoring/utils.js | 34 | ||||
-rw-r--r-- | app/assets/stylesheets/pages/environments.scss | 2 | ||||
-rw-r--r-- | locale/gitlab.pot | 24 | ||||
-rw-r--r-- | spec/javascripts/monitoring/dashboard_spec.js | 93 |
8 files changed, 226 insertions, 27 deletions
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue index f5019bc627e..f2bd4150b6d 100644 --- a/app/assets/javascripts/monitoring/components/dashboard.vue +++ b/app/assets/javascripts/monitoring/components/dashboard.vue @@ -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" diff --git a/app/assets/javascripts/monitoring/constants.js b/app/assets/javascripts/monitoring/constants.js index 55ecf3b5334..9e5d0d0fd28 100644 --- a/app/assets/javascripts/monitoring/constants.js +++ b/app/assets/javascripts/monitoring/constants.js @@ -1,3 +1,5 @@ +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; diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js index 9d78b5ea110..2b4ddd7afbc 100644 --- a/app/assets/javascripts/monitoring/monitoring_bundle.js +++ b/app/assets/javascripts/monitoring/monitoring_bundle.js @@ -14,6 +14,7 @@ export default () => { props: { ...el.dataset, hasMetrics: parseBoolean(el.dataset.hasMetrics), + showTimeWindowDropdown: gon.features.metricsTimeWindow, }, }); }, diff --git a/app/assets/javascripts/monitoring/services/monitoring_service.js b/app/assets/javascripts/monitoring/services/monitoring_service.js index 24b4acaf6da..5fcc2c8cfac 100644 --- a/app/assets/javascripts/monitoring/services/monitoring_service.js +++ b/app/assets/javascripts/monitoring/services/monitoring_service.js @@ -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; diff --git a/app/assets/javascripts/monitoring/utils.js b/app/assets/javascripts/monitoring/utils.js new file mode 100644 index 00000000000..e379827b769 --- /dev/null +++ b/app/assets/javascripts/monitoring/utils.js @@ -0,0 +1,34 @@ +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 {}; diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 0eb854ecf98..8e1ee51628d 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -204,7 +204,7 @@ } .prometheus-graphs { - .environments { + .dropdowns { .dropdown-menu-toggle { svg { position: absolute; diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 1166134347c..139272d5f7b 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -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 "" diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index 454777fa912..ce2c6c43c0f 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -1,6 +1,7 @@ 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); |