Skip to content
Snippets Groups Projects
Verified Commit a4357b8f authored by Adriel Santiago's avatar Adriel Santiago
Browse files

Handle window and container resize events

Resizes metrics graph on window and sidebard width changes
parent 84ac9253
No related branches found
No related tags found
1 merge request!9399EE Compatibility Port: CE Handle window and container resize events
Pipeline #46076534 passed
......@@ -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
if the name does not exist this function will return `null`
......
<script>
import { GlAreaChart } from '@gitlab/ui';
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>
......@@ -2,7 +2,6 @@
// ee-only
import DashboardMixin from 'ee/monitoring/components/dashboard_mixin';
import _ from 'underscore';
import { s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import Flash from '../../flash';
......@@ -12,6 +11,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,
......@@ -101,29 +103,19 @@ 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);
this.servicePromises = [
this.service
.getGraphsData()
......@@ -148,12 +140,12 @@ export default {
);
}
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: {
......@@ -171,20 +163,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 v-if="showEnvironmentDropdown" class="environments d-flex align-items-center">
{{ s__('Metrics|Environment') }}
<div class="dropdown prepend-left-10">
......@@ -220,6 +213,7 @@ export default {
:key="graphIndex"
:graph-data="graphData"
:alert-data="getGraphAlerts(graphData.id)"
:container-width="elWidth"
group-id="monitor-area-chart"
>
<!-- EE content -->
......
......@@ -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');
......
......@@ -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);
});
......@@ -183,16 +183,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(() => {
......@@ -200,7 +200,7 @@ describe('Dashboard', () => {
return Vue.nextTick();
})
.then(() => {
expect(component.forceRedraw).toEqual(component.elWidth);
expect(component.elWidth).toEqual(pageLayoutEl.clientWidth);
done();
})
.catch(done.fail);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment