Commit c974f4a8 authored by Adriel Santiago's avatar Adriel Santiago

Handle window and container resize events

Resizes metrics graph on window and sidebard width changes
parent 2b0f4df0
...@@ -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>
<script> <script>
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';
...@@ -9,6 +8,9 @@ import GraphGroup from './graph_group.vue'; ...@@ -9,6 +8,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,
...@@ -89,39 +91,29 @@ export default { ...@@ -89,39 +91,29 @@ 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);
if (!this.hasMetrics) { if (!this.hasMetrics) {
this.state = 'gettingStarted'; this.state = 'gettingStarted';
} else { } else {
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: {
...@@ -149,20 +141,21 @@ export default { ...@@ -149,20 +141,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 class="environments d-flex align-items-center"> <div 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">
...@@ -198,6 +191,7 @@ export default { ...@@ -198,6 +191,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"
/> />
</graph-group> </graph-group>
......
...@@ -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);
}); });
...@@ -164,16 +164,16 @@ describe('Dashboard', () => { ...@@ -164,16 +164,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(() => {
...@@ -181,7 +181,7 @@ describe('Dashboard', () => { ...@@ -181,7 +181,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