diff options
5 files changed, 86 insertions, 30 deletions
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index ae8b4b4d635..0ceff10a02a 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -221,6 +221,22 @@ export const scrollToElement = element => { }; /** + * Returns a function that can only be invoked once between + * each browser screen repaint. + * @param {Function} fn + */ +export const debounceByAnimationFrame = fn => { + let requestId; + + return function debounced(...args) { + if (requestId) { + window.cancelAnimationFrame(requestId); + } + requestId = window.requestAnimationFrame(() => fn.apply(this, args)); + }; +}; + +/** this will take in the `name` of the param you want to parse in the url if the name does not exist this function will return `null` otherwise it will return the value of the param key provided diff --git a/app/assets/javascripts/monitoring/components/charts/area.vue b/app/assets/javascripts/monitoring/components/charts/area.vue index 553a6ef4d8a..ae8d971d061 100644 --- a/app/assets/javascripts/monitoring/components/charts/area.vue +++ b/app/assets/javascripts/monitoring/components/charts/area.vue @@ -1,6 +1,9 @@ <script> import { GlAreaChart } from '@gitlab/ui/dist/charts'; import dateFormat from 'dateformat'; +import { debounceByAnimationFrame } from '~/lib/utils/common_utils'; + +let debouncedResize; export default { components: { @@ -26,12 +29,22 @@ export default { ); }, }, + containerWidth: { + type: Number, + required: true, + }, alertData: { type: Object, required: false, default: () => ({}), }, }, + data() { + return { + width: 0, + height: 0, + }; + }, computed: { chartData() { return this.graphData.queries.reduce((accumulator, query) => { @@ -76,11 +89,26 @@ export default { return `${this.graphData.y_label} (${query.unit})`; }, }, + watch: { + containerWidth: 'onResize', + }, + beforeDestroy() { + window.removeEventListener('resize', debouncedResize); + }, + created() { + debouncedResize = debounceByAnimationFrame(this.onResize); + window.addEventListener('resize', debouncedResize); + }, methods: { formatTooltipText(params) { const [date, value] = params; return [dateFormat(date, 'dd mmm yyyy, h:MMtt'), value.toFixed(3)]; }, + onResize() { + const { width, height } = this.$refs.areaChart.$el.getBoundingClientRect(); + this.width = width; + this.height = height; + }, }, }; </script> @@ -92,11 +120,14 @@ export default { <div class="prometheus-graph-widgets"><slot></slot></div> </div> <gl-area-chart + ref="areaChart" v-bind="$attrs" :data="chartData" :option="chartOptions" :format-tooltip-text="formatTooltipText" :thresholds="alertData" + :width="width" + :height="height" /> </div> </template> diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue index 0b4bb9cc686..76059cb602d 100644 --- a/app/assets/javascripts/monitoring/components/dashboard.vue +++ b/app/assets/javascripts/monitoring/components/dashboard.vue @@ -1,5 +1,4 @@ <script> -import _ from 'underscore'; import { s__ } from '~/locale'; import Icon from '~/vue_shared/components/icon.vue'; import Flash from '../../flash'; @@ -9,6 +8,9 @@ import GraphGroup from './graph_group.vue'; import EmptyState from './empty_state.vue'; import MonitoringStore from '../stores/monitoring_store'; +const sidebarAnimationDuration = 150; +let sidebarMutationObserver; + export default { components: { MonitorAreaChart, @@ -89,39 +91,29 @@ export default { elWidth: 0, }; }, - computed: { - forceRedraw() { - return this.elWidth; - }, - }, created() { this.service = new MonitoringService({ metricsEndpoint: this.metricsEndpoint, deploymentEndpoint: this.deploymentEndpoint, environmentsEndpoint: this.environmentsEndpoint, }); - this.mutationObserverConfig = { - attributes: true, - childList: false, - subtree: false, - }; }, beforeDestroy() { - window.removeEventListener('resize', this.resizeThrottled, false); - this.sidebarMutationObserver.disconnect(); + if (sidebarMutationObserver) { + sidebarMutationObserver.disconnect(); + } }, mounted() { - this.resizeThrottled = _.debounce(this.resize, 100); if (!this.hasMetrics) { this.state = 'gettingStarted'; } else { this.getGraphsData(); - window.addEventListener('resize', this.resizeThrottled, false); - - const sidebarEl = document.querySelector('.nav-sidebar'); - // The sidebar listener - this.sidebarMutationObserver = new MutationObserver(this.resizeThrottled); - this.sidebarMutationObserver.observe(sidebarEl, this.mutationObserverConfig); + sidebarMutationObserver = new MutationObserver(this.onSidebarMutation); + sidebarMutationObserver.observe(document.querySelector('.layout-page'), { + attributes: true, + childList: false, + subtree: false, + }); } }, methods: { @@ -149,20 +141,21 @@ export default { this.showEmptyState = false; }) - .then(this.resize) .catch(() => { this.state = 'unableToConnect'; }); }, - resize() { - this.elWidth = this.$el.clientWidth; + onSidebarMutation() { + setTimeout(() => { + this.elWidth = this.$el.clientWidth; + }, sidebarAnimationDuration); }, }, }; </script> <template> - <div v-if="!showEmptyState" :key="forceRedraw" class="prometheus-graphs prepend-top-default"> + <div v-if="!showEmptyState" class="prometheus-graphs prepend-top-default"> <div class="environments d-flex align-items-center"> {{ s__('Metrics|Environment') }} <div class="dropdown prepend-left-10"> @@ -198,6 +191,7 @@ export default { :key="graphIndex" :graph-data="graphData" :alert-data="getGraphAlerts(graphData.id)" + :container-width="elWidth" group-id="monitor-area-chart" /> </graph-group> diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js index e3fd9604474..3eff3f655ee 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/javascripts/lib/utils/common_utils_spec.js @@ -232,6 +232,21 @@ describe('common_utils', () => { }); }); + describe('debounceByAnimationFrame', () => { + it('debounces a function to allow a maximum of one call per animation frame', done => { + const spy = jasmine.createSpy('spy'); + const debouncedSpy = commonUtils.debounceByAnimationFrame(spy); + window.requestAnimationFrame(() => { + debouncedSpy(); + debouncedSpy(); + window.requestAnimationFrame(() => { + expect(spy).toHaveBeenCalledTimes(1); + done(); + }); + }); + }); + }); + describe('getParameterByName', () => { beforeEach(() => { window.history.pushState({}, null, '?scope=all&p=2'); diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index 565b87de248..97b9671c809 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -29,7 +29,7 @@ describe('Dashboard', () => { beforeEach(() => { setFixtures(` <div class="prometheus-graphs"></div> - <div class="nav-sidebar"></div> + <div class="layout-page"></div> `); DashboardComponent = Vue.extend(Dashboard); }); @@ -164,16 +164,16 @@ describe('Dashboard', () => { jasmine.clock().uninstall(); }); - it('rerenders the dashboard when the sidebar is resized', done => { + 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 }, }); - expect(component.forceRedraw).toEqual(0); + expect(component.elWidth).toEqual(0); - const navSidebarEl = document.querySelector('.nav-sidebar'); - navSidebarEl.classList.add('nav-sidebar-collapsed'); + const pageLayoutEl = document.querySelector('.layout-page'); + pageLayoutEl.classList.add('page-with-icon-sidebar'); Vue.nextTick() .then(() => { @@ -181,7 +181,7 @@ describe('Dashboard', () => { return Vue.nextTick(); }) .then(() => { - expect(component.forceRedraw).toEqual(component.elWidth); + expect(component.elWidth).toEqual(pageLayoutEl.clientWidth); done(); }) .catch(done.fail); |