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>
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