Commit 06fe294e authored by Phil Hughes's avatar Phil Hughes

Merge branch '122013-open-logs-from-metrics-chart-datazoom' into 'master'

Time series chart filters out "view logs" link

See merge request gitlab-org/gitlab!24343
parents 256bd3a1 88aa3b52
<script> <script>
import { omit } from 'lodash'; import { omit, throttle } from 'lodash';
import { GlLink, GlButton, GlTooltip, GlResizeObserverDirective } from '@gitlab/ui'; import { GlLink, GlButton, GlTooltip, GlResizeObserverDirective } from '@gitlab/ui';
import { GlAreaChart, GlLineChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts'; import { GlAreaChart, GlLineChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
import dateFormat from 'dateformat'; import dateFormat from 'dateformat';
...@@ -18,6 +18,13 @@ import { ...@@ -18,6 +18,13 @@ import {
import { makeDataSeries } from '~/helpers/monitor_helper'; import { makeDataSeries } from '~/helpers/monitor_helper';
import { graphDataValidatorForValues } from '../../utils'; import { graphDataValidatorForValues } from '../../utils';
const THROTTLED_DATAZOOM_WAIT = 1000; // miliseconds
const timestampToISODate = timestamp => new Date(timestamp).toISOString();
const events = {
datazoom: 'datazoom',
};
export default { export default {
components: { components: {
GlAreaChart, GlAreaChart,
...@@ -98,6 +105,7 @@ export default { ...@@ -98,6 +105,7 @@ export default {
height: chartHeight, height: chartHeight,
svgs: {}, svgs: {},
primaryColor: null, primaryColor: null,
throttledDatazoom: null,
}; };
}, },
computed: { computed: {
...@@ -245,6 +253,11 @@ export default { ...@@ -245,6 +253,11 @@ export default {
this.setSvg('rocket'); this.setSvg('rocket');
this.setSvg('scroll-handle'); this.setSvg('scroll-handle');
}, },
destroyed() {
if (this.throttledDatazoom) {
this.throttledDatazoom.cancel();
}
},
methods: { methods: {
formatLegendLabel(query) { formatLegendLabel(query) {
return `${query.label}`; return `${query.label}`;
...@@ -287,8 +300,39 @@ export default { ...@@ -287,8 +300,39 @@ export default {
console.error('SVG could not be rendered correctly: ', e); console.error('SVG could not be rendered correctly: ', e);
}); });
}, },
onChartUpdated(chart) { onChartUpdated(eChart) {
[this.primaryColor] = chart.getOption().color; [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() { onResize() {
if (!this.$refs.chart) return; if (!this.$refs.chart) return;
...@@ -331,6 +375,7 @@ export default { ...@@ -331,6 +375,7 @@ export default {
:height="height" :height="height"
:average-text="legendAverageText" :average-text="legendAverageText"
:max-text="legendMaxText" :max-text="legendMaxText"
@created="onChartCreated"
@updated="onChartUpdated" @updated="onChartUpdated"
> >
<template v-if="tooltip.isDeployment"> <template v-if="tooltip.isDeployment">
......
...@@ -59,6 +59,11 @@ export default { ...@@ -59,6 +59,11 @@ export default {
default: 'panel-type-chart', default: 'panel-type-chart',
}, },
}, },
data() {
return {
zoomedTimeRange: null,
};
},
computed: { computed: {
...mapState('monitoringDashboard', ['deploymentData', 'projectPath', 'logsPath', 'timeRange']), ...mapState('monitoringDashboard', ['deploymentData', 'projectPath', 'logsPath', 'timeRange']),
alertWidgetAvailable() { alertWidgetAvailable() {
...@@ -72,8 +77,10 @@ export default { ...@@ -72,8 +77,10 @@ export default {
); );
}, },
logsPathWithTimeRange() { logsPathWithTimeRange() {
if (this.logsPath && this.logsPath !== invalidUrl && this.timeRange) { const timeRange = this.zoomedTimeRange || this.timeRange;
return timeRangeToUrl(this.timeRange, this.logsPath);
if (this.logsPath && this.logsPath !== invalidUrl && timeRange) {
return timeRangeToUrl(timeRange, this.logsPath);
} }
return null; return null;
}, },
...@@ -114,6 +121,10 @@ export default { ...@@ -114,6 +121,10 @@ export default {
}, },
downloadCSVOptions, downloadCSVOptions,
generateLinkToChartOptions, generateLinkToChartOptions,
onDatazoom({ start, end }) {
this.zoomedTimeRange = { start, end };
},
}, },
}; };
</script> </script>
...@@ -137,11 +148,13 @@ export default { ...@@ -137,11 +148,13 @@ export default {
<component <component
:is="monitorChartComponent" :is="monitorChartComponent"
v-else-if="graphDataHasMetrics" v-else-if="graphDataHasMetrics"
ref="timeChart"
:graph-data="graphData" :graph-data="graphData"
:deployment-data="deploymentData" :deployment-data="deploymentData"
:project-path="projectPath" :project-path="projectPath"
:thresholds="getGraphAlertValues(graphData.metrics)" :thresholds="getGraphAlertValues(graphData.metrics)"
:group-id="groupId" :group-id="groupId"
@datazoom="onDatazoom"
> >
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<alert-widget <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'; ...@@ -18,6 +18,15 @@ import * as iconUtils from '~/lib/utils/icon_utils';
const mockWidgets = 'mockWidgets'; const mockWidgets = 'mockWidgets';
const mockSvgPathContent = 'mockSvgPathContent'; 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', () => ({ jest.mock('~/lib/utils/icon_utils', () => ({
getSvgIconPathContent: jest.fn().mockImplementation(() => Promise.resolve(mockSvgPathContent)), getSvgIconPathContent: jest.fn().mockImplementation(() => Promise.resolve(mockSvgPathContent)),
})); }));
...@@ -94,6 +103,56 @@ describe('Time series component', () => { ...@@ -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('methods', () => {
describe('formatTooltipText', () => { describe('formatTooltipText', () => {
let mockDate; let mockDate;
......
...@@ -122,6 +122,7 @@ describe('Panel Type component', () => { ...@@ -122,6 +122,7 @@ describe('Panel Type component', () => {
const mockLogsPath = '/path/to/logs'; const mockLogsPath = '/path/to/logs';
const mockTimeRange = { duration: { seconds: 120 } }; const mockTimeRange = { duration: { seconds: 120 } };
const findTimeChart = () => wrapper.find({ ref: 'timeChart' });
const findViewLogsLink = () => wrapper.find({ ref: 'viewLogsLink' }); const findViewLogsLink = () => wrapper.find({ ref: 'viewLogsLink' });
beforeEach(() => { beforeEach(() => {
...@@ -168,8 +169,28 @@ describe('Panel Type component', () => { ...@@ -168,8 +169,28 @@ describe('Panel Type component', () => {
state.timeRange = mockTimeRange; state.timeRange = mockTimeRange;
return wrapper.vm.$nextTick(() => { return wrapper.vm.$nextTick(() => {
expect(findViewLogsLink().exists()).toBe(true); const href = `${mockLogsPath}?duration_seconds=${mockTimeRange.duration.seconds}`;
expect(findViewLogsLink().attributes('href')).toEqual('/path/to/logs?duration_seconds=120'); 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