Commit 88aa3b52 authored by Miguel Rincon's avatar Miguel Rincon

Time series chart filter view logs link

The time series chart filtered time range (datazoom)
becomes reflected in the View logs link

- Adds an emmitted datazoom event handler for time charts
  - Event handler is throttled.
- Responds to changes in zoom by updating the logs url.
parent 236f031d
<script>
import { omit } from 'lodash';
import { omit, throttle } from 'lodash';
import { GlLink, GlButton, GlTooltip, GlResizeObserverDirective } from '@gitlab/ui';
import { GlAreaChart, GlLineChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
import dateFormat from 'dateformat';
......@@ -18,6 +18,13 @@ import {
import { makeDataSeries } from '~/helpers/monitor_helper';
import { graphDataValidatorForValues } from '../../utils';
const THROTTLED_DATAZOOM_WAIT = 1000; // miliseconds
const timestampToISODate = timestamp => new Date(timestamp).toISOString();
const events = {
datazoom: 'datazoom',
};
export default {
components: {
GlAreaChart,
......@@ -98,6 +105,7 @@ export default {
height: chartHeight,
svgs: {},
primaryColor: null,
throttledDatazoom: null,
};
},
computed: {
......@@ -245,6 +253,11 @@ export default {
this.setSvg('rocket');
this.setSvg('scroll-handle');
},
destroyed() {
if (this.throttledDatazoom) {
this.throttledDatazoom.cancel();
}
},
methods: {
formatLegendLabel(query) {
return `${query.label}`;
......@@ -287,8 +300,39 @@ export default {
console.error('SVG could not be rendered correctly: ', e);
});
},
onChartUpdated(chart) {
[this.primaryColor] = chart.getOption().color;
onChartUpdated(eChart) {
[this.primaryColor] = eChart.getOption().color;
},
onChartCreated(eChart) {
// Emit a datazoom event that corresponds to the eChart
// `datazoom` event.
if (this.throttledDatazoom) {
// Chart can be created multiple times in this component's
// lifetime, remove previous handlers every time
// chart is created.
this.throttledDatazoom.cancel();
}
// Emitting is throttled to avoid flurries of calls when
// the user changes or scrolls the zoom bar.
this.throttledDatazoom = throttle(
() => {
const { startValue, endValue } = eChart.getOption().dataZoom[0];
this.$emit(events.datazoom, {
start: timestampToISODate(startValue),
end: timestampToISODate(endValue),
});
},
THROTTLED_DATAZOOM_WAIT,
{
leading: false,
},
);
eChart.off('datazoom');
eChart.on('datazoom', this.throttledDatazoom);
},
onResize() {
if (!this.$refs.chart) return;
......@@ -331,6 +375,7 @@ export default {
:height="height"
:average-text="legendAverageText"
:max-text="legendMaxText"
@created="onChartCreated"
@updated="onChartUpdated"
>
<template v-if="tooltip.isDeployment">
......
......@@ -59,6 +59,11 @@ export default {
default: 'panel-type-chart',
},
},
data() {
return {
zoomedTimeRange: null,
};
},
computed: {
...mapState('monitoringDashboard', ['deploymentData', 'projectPath', 'logsPath', 'timeRange']),
alertWidgetAvailable() {
......@@ -72,8 +77,10 @@ export default {
);
},
logsPathWithTimeRange() {
if (this.logsPath && this.logsPath !== invalidUrl && this.timeRange) {
return timeRangeToUrl(this.timeRange, this.logsPath);
const timeRange = this.zoomedTimeRange || this.timeRange;
if (this.logsPath && this.logsPath !== invalidUrl && timeRange) {
return timeRangeToUrl(timeRange, this.logsPath);
}
return null;
},
......@@ -114,6 +121,10 @@ export default {
},
downloadCSVOptions,
generateLinkToChartOptions,
onDatazoom({ start, end }) {
this.zoomedTimeRange = { start, end };
},
},
};
</script>
......@@ -137,11 +148,13 @@ export default {
<component
:is="monitorChartComponent"
v-else-if="graphDataHasMetrics"
ref="timeChart"
:graph-data="graphData"
:deployment-data="deploymentData"
:project-path="projectPath"
:thresholds="getGraphAlertValues(graphData.metrics)"
:group-id="groupId"
@datazoom="onDatazoom"
>
<div class="d-flex align-items-center">
<alert-widget
......
---
title: Time Series chart filtered time range (datazoom) becomes reflected in the View logs link
merge_request: 24343
author:
type: added
......@@ -18,6 +18,15 @@ import * as iconUtils from '~/lib/utils/icon_utils';
const mockWidgets = 'mockWidgets';
const mockSvgPathContent = 'mockSvgPathContent';
jest.mock('lodash/throttle', () =>
// this throttle mock executes immediately
jest.fn(func => {
// eslint-disable-next-line no-param-reassign
func.cancel = jest.fn();
return func;
}),
);
jest.mock('~/lib/utils/icon_utils', () => ({
getSvgIconPathContent: jest.fn().mockImplementation(() => Promise.resolve(mockSvgPathContent)),
}));
......@@ -94,6 +103,56 @@ describe('Time series component', () => {
});
});
describe('events', () => {
describe('datazoom', () => {
let eChartMock;
let startValue;
let endValue;
const findChart = () => timeSeriesChart.find({ ref: 'chart' });
beforeEach(done => {
eChartMock = {
handlers: {},
getOption: () => ({
dataZoom: [
{
startValue,
endValue,
},
],
}),
off: jest.fn(eChartEvent => {
delete eChartMock.handlers[eChartEvent];
}),
on: jest.fn((eChartEvent, fn) => {
eChartMock.handlers[eChartEvent] = fn;
}),
};
timeSeriesChart = makeTimeSeriesChart(mockGraphData);
timeSeriesChart.vm.$nextTick(() => {
findChart().vm.$emit('created', eChartMock);
done();
});
});
it('handles datazoom event from chart', () => {
startValue = 1577836800000; // 2020-01-01T00:00:00.000Z
endValue = 1577840400000; // 2020-01-01T01:00:00.000Z
eChartMock.handlers.datazoom();
expect(timeSeriesChart.emitted('datazoom')).toHaveLength(1);
expect(timeSeriesChart.emitted('datazoom')[0]).toEqual([
{
start: new Date(startValue).toISOString(),
end: new Date(endValue).toISOString(),
},
]);
});
});
});
describe('methods', () => {
describe('formatTooltipText', () => {
let mockDate;
......
......@@ -122,6 +122,7 @@ describe('Panel Type component', () => {
const mockLogsPath = '/path/to/logs';
const mockTimeRange = { duration: { seconds: 120 } };
const findTimeChart = () => wrapper.find({ ref: 'timeChart' });
const findViewLogsLink = () => wrapper.find({ ref: 'viewLogsLink' });
beforeEach(() => {
......@@ -168,8 +169,28 @@ describe('Panel Type component', () => {
state.timeRange = mockTimeRange;
return wrapper.vm.$nextTick(() => {
expect(findViewLogsLink().exists()).toBe(true);
expect(findViewLogsLink().attributes('href')).toEqual('/path/to/logs?duration_seconds=120');
const href = `${mockLogsPath}?duration_seconds=${mockTimeRange.duration.seconds}`;
expect(findViewLogsLink().attributes('href')).toMatch(href);
});
});
it('it is overriden when a datazoom event is received', () => {
state.logsPath = mockLogsPath;
state.timeRange = mockTimeRange;
const zoomedTimeRange = {
start: '2020-01-01T00:00:00.000Z',
end: '2020-01-01T01:00:00.000Z',
};
findTimeChart().vm.$emit('datazoom', zoomedTimeRange);
return wrapper.vm.$nextTick(() => {
const start = encodeURIComponent(zoomedTimeRange.start);
const end = encodeURIComponent(zoomedTimeRange.end);
expect(findViewLogsLink().attributes('href')).toMatch(
`${mockLogsPath}?start=${start}&end=${end}`,
);
});
});
});
......
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