Commit a4357b8f authored by Adriel Santiago's avatar Adriel Santiago

Handle window and container resize events

Resizes metrics graph on window and sidebard width changes
parent 84ac9253
...@@ -220,6 +220,22 @@ export const scrollToElement = element => { ...@@ -220,6 +220,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 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` if the name does not exist this function will return `null`
......
<script> <script>
import { GlAreaChart } from '@gitlab/ui'; import { GlAreaChart } from '@gitlab/ui';
import dateFormat from 'dateformat'; import dateFormat from 'dateformat';
import { debounceByAnimationFrame } from '~/lib/utils/common_utils';
let debouncedResize;
export default { export default {
components: { components: {
...@@ -26,12 +29,22 @@ export default { ...@@ -26,12 +29,22 @@ export default {
); );
}, },
}, },
containerWidth: {
type: Number,
required: true,
},
alertData: { alertData: {
type: Object, type: Object,
required: false, required: false,
default: () => ({}), default: () => ({}),
}, },
}, },
data() {
return {
width: 0,
height: 0,
};
},
computed: { computed: {
chartData() { chartData() {
return this.graphData.queries.reduce((accumulator, query) => { return this.graphData.queries.reduce((accumulator, query) => {
...@@ -76,11 +89,26 @@ export default { ...@@ -76,11 +89,26 @@ export default {
return `${this.graphData.y_label} (${query.unit})`; 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: { methods: {
formatTooltipText(params) { formatTooltipText(params) {
const [date, value] = params; const [date, value] = params;
return [dateFormat(date, 'dd mmm yyyy, h:MMtt'), value.toFixed(3)]; 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> </script>
...@@ -92,11 +120,14 @@ export default { ...@@ -92,11 +120,14 @@ export default {
<div class="prometheus-graph-widgets"><slot></slot></div> <div class="prometheus-graph-widgets"><slot></slot></div>
</div> </div>
<gl-area-chart <gl-area-chart
ref="areaChart"
v-bind="$attrs" v-bind="$attrs"
:data="chartData" :data="chartData"
:option="chartOptions" :option="chartOptions"
:format-tooltip-text="formatTooltipText" :format-tooltip-text="formatTooltipText"
:thresholds="alertData" :thresholds="alertData"
:width="width"
:height="height"
/> />
</div> </div>
</template> </template>
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
// ee-only // ee-only
import DashboardMixin from 'ee/monitoring/components/dashboard_mixin'; import DashboardMixin from 'ee/monitoring/components/dashboard_mixin';
import _ from 'underscore';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import Flash from '../../flash'; import Flash from '../../flash';
...@@ -12,6 +11,9 @@ import GraphGroup from './graph_group.vue'; ...@@ -12,6 +11,9 @@ import GraphGroup from './graph_group.vue';
import EmptyState from './empty_state.vue'; import EmptyState from './empty_state.vue';
import MonitoringStore from '../stores/monitoring_store'; import MonitoringStore from '../stores/monitoring_store';
const sidebarAnimationDuration = 150;
let sidebarMutationObserver;
export default { export default {
components: { components: {
MonitorAreaChart, MonitorAreaChart,
...@@ -101,29 +103,19 @@ export default { ...@@ -101,29 +103,19 @@ export default {
elWidth: 0, elWidth: 0,
}; };
}, },
computed: {
forceRedraw() {
return this.elWidth;
},
},
created() { created() {
this.service = new MonitoringService({ this.service = new MonitoringService({
metricsEndpoint: this.metricsEndpoint, metricsEndpoint: this.metricsEndpoint,
deploymentEndpoint: this.deploymentEndpoint, deploymentEndpoint: this.deploymentEndpoint,
environmentsEndpoint: this.environmentsEndpoint, environmentsEndpoint: this.environmentsEndpoint,
}); });
this.mutationObserverConfig = {
attributes: true,
childList: false,
subtree: false,
};
}, },
beforeDestroy() { beforeDestroy() {
window.removeEventListener('resize', this.resizeThrottled, false); if (sidebarMutationObserver) {
this.sidebarMutationObserver.disconnect(); sidebarMutationObserver.disconnect();
}
}, },
mounted() { mounted() {
this.resizeThrottled = _.debounce(this.resize, 100);
this.servicePromises = [ this.servicePromises = [
this.service this.service
.getGraphsData() .getGraphsData()
...@@ -148,12 +140,12 @@ export default { ...@@ -148,12 +140,12 @@ export default {
); );
} }
this.getGraphsData(); this.getGraphsData();
window.addEventListener('resize', this.resizeThrottled, false); sidebarMutationObserver = new MutationObserver(this.onSidebarMutation);
sidebarMutationObserver.observe(document.querySelector('.layout-page'), {
const sidebarEl = document.querySelector('.nav-sidebar'); attributes: true,
// The sidebar listener childList: false,
this.sidebarMutationObserver = new MutationObserver(this.resizeThrottled); subtree: false,
this.sidebarMutationObserver.observe(sidebarEl, this.mutationObserverConfig); });
} }
}, },
methods: { methods: {
...@@ -171,20 +163,21 @@ export default { ...@@ -171,20 +163,21 @@ export default {
this.showEmptyState = false; this.showEmptyState = false;
}) })
.then(this.resize)
.catch(() => { .catch(() => {
this.state = 'unableToConnect'; this.state = 'unableToConnect';
}); });
}, },
resize() { onSidebarMutation() {
this.elWidth = this.$el.clientWidth; setTimeout(() => {
this.elWidth = this.$el.clientWidth;
}, sidebarAnimationDuration);
}, },
}, },
}; };
</script> </script>
<template> <template>
<div v-if="!showEmptyState" :key="forceRedraw" class="prometheus-graphs prepend-top-default"> <div v-if="!showEmptyState" class="prometheus-graphs prepend-top-default">
<div v-if="showEnvironmentDropdown" class="environments d-flex align-items-center"> <div v-if="showEnvironmentDropdown" class="environments d-flex align-items-center">
{{ s__('Metrics|Environment') }} {{ s__('Metrics|Environment') }}
<div class="dropdown prepend-left-10"> <div class="dropdown prepend-left-10">
...@@ -220,6 +213,7 @@ export default { ...@@ -220,6 +213,7 @@ export default {
:key="graphIndex" :key="graphIndex"
:graph-data="graphData" :graph-data="graphData"
:alert-data="getGraphAlerts(graphData.id)" :alert-data="getGraphAlerts(graphData.id)"
:container-width="elWidth"
group-id="monitor-area-chart" group-id="monitor-area-chart"
> >
<!-- EE content --> <!-- EE content -->
......
...@@ -232,6 +232,21 @@ describe('common_utils', () => { ...@@ -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', () => { describe('getParameterByName', () => {
beforeEach(() => { beforeEach(() => {
window.history.pushState({}, null, '?scope=all&p=2'); window.history.pushState({}, null, '?scope=all&p=2');
......
...@@ -29,7 +29,7 @@ describe('Dashboard', () => { ...@@ -29,7 +29,7 @@ describe('Dashboard', () => {
beforeEach(() => { beforeEach(() => {
setFixtures(` setFixtures(`
<div class="prometheus-graphs"></div> <div class="prometheus-graphs"></div>
<div class="nav-sidebar"></div> <div class="layout-page"></div>
`); `);
DashboardComponent = Vue.extend(Dashboard); DashboardComponent = Vue.extend(Dashboard);
}); });
...@@ -183,16 +183,16 @@ describe('Dashboard', () => { ...@@ -183,16 +183,16 @@ describe('Dashboard', () => {
jasmine.clock().uninstall(); 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({ const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'), el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true, showPanels: false }, propsData: { ...propsData, hasMetrics: true, showPanels: false },
}); });
expect(component.forceRedraw).toEqual(0); expect(component.elWidth).toEqual(0);
const navSidebarEl = document.querySelector('.nav-sidebar'); const pageLayoutEl = document.querySelector('.layout-page');
navSidebarEl.classList.add('nav-sidebar-collapsed'); pageLayoutEl.classList.add('page-with-icon-sidebar');
Vue.nextTick() Vue.nextTick()
.then(() => { .then(() => {
...@@ -200,7 +200,7 @@ describe('Dashboard', () => { ...@@ -200,7 +200,7 @@ describe('Dashboard', () => {
return Vue.nextTick(); return Vue.nextTick();
}) })
.then(() => { .then(() => {
expect(component.forceRedraw).toEqual(component.elWidth); expect(component.elWidth).toEqual(pageLayoutEl.clientWidth);
done(); done();
}) })
.catch(done.fail); .catch(done.fail);
......
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