Handle window and container resize events
Resizes metrics graph on window and sidebard width changes
This commit is contained in:
parent
2b0f4df021
commit
c974f4a82e
5 changed files with 86 additions and 30 deletions
|
@ -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`
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue