Commit dfe5e20c authored by Miguel Rincon's avatar Miguel Rincon

Define a single file as the chart formatter

Adds a dateformat file that defines defaults and contants to handle
multiple formats for charts timestamps.
parent 922be903
<script> <script>
import { GlResizeObserverDirective } from '@gitlab/ui'; import { GlResizeObserverDirective } from '@gitlab/ui';
import { GlHeatmap } from '@gitlab/ui/dist/charts'; import { GlHeatmap } from '@gitlab/ui/dist/charts';
import dateformat from 'dateformat';
import { graphDataValidatorForValues } from '../../utils'; import { graphDataValidatorForValues } from '../../utils';
import { formatDate, timezones, formats } from '../../format_date';
export default { export default {
components: { components: {
...@@ -17,6 +17,11 @@ export default { ...@@ -17,6 +17,11 @@ export default {
required: true, required: true,
validator: graphDataValidatorForValues.bind(null, false), validator: graphDataValidatorForValues.bind(null, false),
}, },
timezone: {
type: String,
required: false,
default: timezones.LOCAL,
},
}, },
data() { data() {
return { return {
...@@ -43,7 +48,7 @@ export default { ...@@ -43,7 +48,7 @@ export default {
return this.result.values.map(val => { return this.result.values.map(val => {
const [yLabel] = val; const [yLabel] = val;
return dateformat(new Date(yLabel), 'HH:MM:ss'); return formatDate(new Date(yLabel), { format: formats.shortTime, timezone: this.timezone });
}); });
}, },
result() { result() {
......
...@@ -2,18 +2,19 @@ ...@@ -2,18 +2,19 @@
import { omit, throttle } from 'lodash'; import { omit, throttle } from 'lodash';
import { GlLink, GlDeprecatedButton, GlTooltip, GlResizeObserverDirective } from '@gitlab/ui'; import { GlLink, GlDeprecatedButton, 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 { s__, __ } from '~/locale'; import { s__, __ } from '~/locale';
import { getSvgIconPathContent } from '~/lib/utils/icon_utils'; import { getSvgIconPathContent } from '~/lib/utils/icon_utils';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import { panelTypes, chartHeight, lineTypes, lineWidths, dateFormats } from '../../constants'; import { panelTypes, chartHeight, lineTypes, lineWidths } from '../../constants';
import { getYAxisOptions, getChartGrid, getTooltipFormatter } from './options'; import { getYAxisOptions, getChartGrid, getTooltipFormatter } from './options';
import { annotationsYAxis, generateAnnotationsSeries } from './annotations'; import { annotationsYAxis, generateAnnotationsSeries } from './annotations';
import { makeDataSeries } from '~/helpers/monitor_helper'; import { makeDataSeries } from '~/helpers/monitor_helper';
import { graphDataValidatorForValues } from '../../utils'; import { graphDataValidatorForValues } from '../../utils';
import { formatDate, timezones, formats } from '../../format_date';
export const timestampToISODate = timestamp => new Date(timestamp).toISOString();
const THROTTLED_DATAZOOM_WAIT = 1000; // milliseconds const THROTTLED_DATAZOOM_WAIT = 1000; // milliseconds
const timestampToISODate = timestamp => new Date(timestamp).toISOString();
const events = { const events = {
datazoom: 'datazoom', datazoom: 'datazoom',
...@@ -89,6 +90,11 @@ export default { ...@@ -89,6 +90,11 @@ export default {
required: false, required: false,
default: '', default: '',
}, },
timezone: {
type: String,
required: false,
default: timezones.LOCAL,
},
}, },
data() { data() {
return { return {
...@@ -163,7 +169,8 @@ export default { ...@@ -163,7 +169,8 @@ export default {
name: __('Time'), name: __('Time'),
type: 'time', type: 'time',
axisLabel: { axisLabel: {
formatter: date => dateFormat(date, dateFormats.timeOfDay), formatter: date =>
formatDate(date, { format: formats.shortTime, timezone: this.timezone }),
}, },
axisPointer: { axisPointer: {
snap: true, snap: true,
...@@ -271,12 +278,13 @@ export default { ...@@ -271,12 +278,13 @@ export default {
*/ */
formatAnnotationsTooltipText(params) { formatAnnotationsTooltipText(params) {
return { return {
title: dateFormat(params.data?.tooltipData?.title, dateFormats.default), title: formatDate(params.data?.tooltipData?.title, { timezone: this.timezone }),
content: params.data?.tooltipData?.content, content: params.data?.tooltipData?.content,
}; };
}, },
formatTooltipText(params) { formatTooltipText(params) {
this.tooltip.title = dateFormat(params.value, dateFormats.default); this.tooltip.title = formatDate(params.value, { timezone: this.timezone });
this.tooltip.content = []; this.tooltip.content = [];
params.seriesData.forEach(dataPoint => { params.seriesData.forEach(dataPoint => {
......
...@@ -127,11 +127,6 @@ export const lineWidths = { ...@@ -127,11 +127,6 @@ export const lineWidths = {
default: 2, default: 2,
}; };
export const dateFormats = {
timeOfDay: 'h:MM TT',
default: 'dd mmm yyyy, h:MMTT',
};
/** /**
* These Vuex store properties are allowed to be * These Vuex store properties are allowed to be
* replaced dynamically after component has been created * replaced dynamically after component has been created
......
import dateFormat from 'dateformat';
export const timezones = {
/**
* Renders a date with a local timezone
*/
LOCAL: 'LOCAL',
/**
* Renders at date with UTC
*/
UTC: 'UTC',
};
export const formats = {
shortTime: 'h:MM TT',
default: 'dd mmm yyyy, h:MMTT (Z)',
};
/**
* Formats a date for a metric dashboard or chart.
*
* Convenience wrapper of dateFormat with default formats
* and settings.
*
* dateFormat has some limitations and we could use `toLocaleString` instead
* See: https://gitlab.com/gitlab-org/gitlab/-/issues/219246
*
* @param {Date|String|Number} date
* @param {Object} options - Formatting options
* @param {string} options.format - Format or mask from `formats`.
* @param {string} options.timezone - Timezone abbreviation.
* Accepts "LOCAL" for the client local timezone.
*/
export const formatDate = (date, options = {}) => {
const { format = formats.default, timezone = timezones.LOCAL } = options;
const useUTC = timezone === timezones.UTC;
return dateFormat(date, format, useUTC);
};
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlHeatmap } from '@gitlab/ui/dist/charts'; import { GlHeatmap } from '@gitlab/ui/dist/charts';
import timezoneMock from 'timezone-mock';
import Heatmap from '~/monitoring/components/charts/heatmap.vue'; import Heatmap from '~/monitoring/components/charts/heatmap.vue';
import { graphDataPrometheusQueryRangeMultiTrack } from '../../mock_data'; import { graphDataPrometheusQueryRangeMultiTrack } from '../../mock_data';
describe('Heatmap component', () => { describe('Heatmap component', () => {
let heatmapChart; let wrapper;
let store; let store;
beforeEach(() => { const findChart = () => wrapper.find(GlHeatmap);
heatmapChart = shallowMount(Heatmap, {
const createWrapper = (props = {}) => {
wrapper = shallowMount(Heatmap, {
propsData: { propsData: {
graphData: graphDataPrometheusQueryRangeMultiTrack, graphData: graphDataPrometheusQueryRangeMultiTrack,
containerWidth: 100, containerWidth: 100,
...props,
}, },
store, store,
}); });
}); };
afterEach(() => {
heatmapChart.destroy();
});
describe('wrapped components', () => { describe('wrapped chart', () => {
describe('GitLab UI heatmap chart', () => {
let glHeatmapChart; let glHeatmapChart;
beforeEach(() => { beforeEach(() => {
glHeatmapChart = heatmapChart.find(GlHeatmap); createWrapper();
glHeatmapChart = findChart();
});
afterEach(() => {
wrapper.destroy();
}); });
it('is a Vue instance', () => { it('is a Vue instance', () => {
...@@ -34,11 +38,11 @@ describe('Heatmap component', () => { ...@@ -34,11 +38,11 @@ describe('Heatmap component', () => {
}); });
it('should display a label on the x axis', () => { it('should display a label on the x axis', () => {
expect(heatmapChart.vm.xAxisName).toBe(graphDataPrometheusQueryRangeMultiTrack.x_label); expect(wrapper.vm.xAxisName).toBe(graphDataPrometheusQueryRangeMultiTrack.x_label);
}); });
it('should display a label on the y axis', () => { it('should display a label on the y axis', () => {
expect(heatmapChart.vm.yAxisName).toBe(graphDataPrometheusQueryRangeMultiTrack.y_label); expect(wrapper.vm.yAxisName).toBe(graphDataPrometheusQueryRangeMultiTrack.y_label);
}); });
// According to the echarts docs https://echarts.apache.org/en/option.html#series-heatmap.data // According to the echarts docs https://echarts.apache.org/en/option.html#series-heatmap.data
...@@ -47,22 +51,51 @@ describe('Heatmap component', () => { ...@@ -47,22 +51,51 @@ describe('Heatmap component', () => {
// corresponding to the cell // corresponding to the cell
it('should return chartData with a length of x by y, with a length of 3 per array', () => { it('should return chartData with a length of x by y, with a length of 3 per array', () => {
const row = heatmapChart.vm.chartData[0]; const row = wrapper.vm.chartData[0];
expect(row.length).toBe(3); expect(row.length).toBe(3);
expect(heatmapChart.vm.chartData.length).toBe(30); expect(wrapper.vm.chartData.length).toBe(30);
}); });
it('returns a series of labels for the x axis', () => { it('returns a series of labels for the x axis', () => {
const { xAxisLabels } = heatmapChart.vm; const { xAxisLabels } = wrapper.vm;
expect(xAxisLabels.length).toBe(5); expect(xAxisLabels.length).toBe(5);
}); });
it('returns a series of labels for the y axis', () => { describe('y axis labels', () => {
const { yAxisLabels } = heatmapChart.vm; const gmtLabels = ['3:00 PM', '4:00 PM', '5:00 PM', '6:00 PM', '7:00 PM', '8:00 PM'];
it('y-axis labels are formatted in AM/PM format', () => {
expect(findChart().props('yAxisLabels')).toEqual(gmtLabels);
});
expect(yAxisLabels.length).toBe(6); describe('when in PT timezone', () => {
const ptLabels = ['8:00 AM', '9:00 AM', '10:00 AM', '11:00 AM', '12:00 PM', '1:00 PM'];
const utcLabels = gmtLabels; // Identical in this case
beforeAll(() => {
timezoneMock.register('US/Pacific');
});
afterAll(() => {
timezoneMock.unregister();
});
it('by default, y-axis is formatted in PT', () => {
createWrapper();
expect(findChart().props('yAxisLabels')).toEqual(ptLabels);
});
it('when the chart uses local timezone, y-axis is formatted in PT', () => {
createWrapper({ timezone: 'LOCAL' });
expect(findChart().props('yAxisLabels')).toEqual(ptLabels);
});
it('when the chart uses UTC, y-axis is formatted in UTC', () => {
createWrapper({ timezone: 'UTC' });
expect(findChart().props('yAxisLabels')).toEqual(utcLabels);
});
}); });
}); });
}); });
......
import { mount, shallowMount } from '@vue/test-utils'; import { mount, shallowMount } from '@vue/test-utils';
import { setTestTimeout } from 'helpers/timeout'; import { setTestTimeout } from 'helpers/timeout';
import timezoneMock from 'timezone-mock';
import { GlLink } from '@gitlab/ui'; import { GlLink } from '@gitlab/ui';
import { TEST_HOST } from 'jest/helpers/test_constants'; import { TEST_HOST } from 'jest/helpers/test_constants';
import { import {
...@@ -39,20 +40,26 @@ jest.mock('~/lib/utils/icon_utils', () => ({ ...@@ -39,20 +40,26 @@ jest.mock('~/lib/utils/icon_utils', () => ({
describe('Time series component', () => { describe('Time series component', () => {
let mockGraphData; let mockGraphData;
let store; let store;
let wrapper;
const createWrapper = (graphData = mockGraphData, mountingMethod = shallowMount) => const createWrapper = (
mountingMethod(TimeSeries, { { graphData = mockGraphData, ...props } = {},
mountingMethod = shallowMount,
) => {
wrapper = mountingMethod(TimeSeries, {
propsData: { propsData: {
graphData, graphData,
deploymentData: store.state.monitoringDashboard.deploymentData, deploymentData: store.state.monitoringDashboard.deploymentData,
annotations: store.state.monitoringDashboard.annotations, annotations: store.state.monitoringDashboard.annotations,
projectPath: `${TEST_HOST}${mockProjectDir}`, projectPath: `${TEST_HOST}${mockProjectDir}`,
...props,
}, },
store, store,
stubs: { stubs: {
GlPopover: true, GlPopover: true,
}, },
}); });
};
describe('With a single time series', () => { describe('With a single time series', () => {
beforeEach(() => { beforeEach(() => {
...@@ -76,39 +83,37 @@ describe('Time series component', () => { ...@@ -76,39 +83,37 @@ describe('Time series component', () => {
}); });
describe('general functions', () => { describe('general functions', () => {
let timeSeriesChart; const findChart = () => wrapper.find({ ref: 'chart' });
const findChart = () => timeSeriesChart.find({ ref: 'chart' });
beforeEach(() => { beforeEach(() => {
timeSeriesChart = createWrapper(mockGraphData, mount); createWrapper({}, mount);
return timeSeriesChart.vm.$nextTick(); return wrapper.vm.$nextTick();
}); });
it('allows user to override max value label text using prop', () => { it('allows user to override max value label text using prop', () => {
timeSeriesChart.setProps({ legendMaxText: 'legendMaxText' }); wrapper.setProps({ legendMaxText: 'legendMaxText' });
return timeSeriesChart.vm.$nextTick().then(() => { return wrapper.vm.$nextTick().then(() => {
expect(timeSeriesChart.props().legendMaxText).toBe('legendMaxText'); expect(wrapper.props().legendMaxText).toBe('legendMaxText');
}); });
}); });
it('allows user to override average value label text using prop', () => { it('allows user to override average value label text using prop', () => {
timeSeriesChart.setProps({ legendAverageText: 'averageText' }); wrapper.setProps({ legendAverageText: 'averageText' });
return timeSeriesChart.vm.$nextTick().then(() => { return wrapper.vm.$nextTick().then(() => {
expect(timeSeriesChart.props().legendAverageText).toBe('averageText'); expect(wrapper.props().legendAverageText).toBe('averageText');
}); });
}); });
it('chart sets a default height', () => { it('chart sets a default height', () => {
const wrapper = createWrapper(); createWrapper();
expect(wrapper.props('height')).toBe(chartHeight); expect(wrapper.props('height')).toBe(chartHeight);
}); });
it('chart has a configurable height', () => { it('chart has a configurable height', () => {
const mockHeight = 599; const mockHeight = 599;
const wrapper = createWrapper(); createWrapper();
wrapper.setProps({ height: mockHeight }); wrapper.setProps({ height: mockHeight });
return wrapper.vm.$nextTick().then(() => { return wrapper.vm.$nextTick().then(() => {
...@@ -141,8 +146,8 @@ describe('Time series component', () => { ...@@ -141,8 +146,8 @@ describe('Time series component', () => {
}), }),
}; };
timeSeriesChart = createWrapper(mockGraphData, mount); createWrapper({}, mount);
timeSeriesChart.vm.$nextTick(() => { wrapper.vm.$nextTick(() => {
findChart().vm.$emit('created', eChartMock); findChart().vm.$emit('created', eChartMock);
done(); done();
}); });
...@@ -153,8 +158,8 @@ describe('Time series component', () => { ...@@ -153,8 +158,8 @@ describe('Time series component', () => {
endValue = 1577840400000; // 2020-01-01T01:00:00.000Z endValue = 1577840400000; // 2020-01-01T01:00:00.000Z
eChartMock.handlers.datazoom(); eChartMock.handlers.datazoom();
expect(timeSeriesChart.emitted('datazoom')).toHaveLength(1); expect(wrapper.emitted('datazoom')).toHaveLength(1);
expect(timeSeriesChart.emitted('datazoom')[0]).toEqual([ expect(wrapper.emitted('datazoom')[0]).toEqual([
{ {
start: new Date(startValue).toISOString(), start: new Date(startValue).toISOString(),
end: new Date(endValue).toISOString(), end: new Date(endValue).toISOString(),
...@@ -172,7 +177,7 @@ describe('Time series component', () => { ...@@ -172,7 +177,7 @@ describe('Time series component', () => {
const mockLineSeriesData = () => ({ const mockLineSeriesData = () => ({
seriesData: [ seriesData: [
{ {
seriesName: timeSeriesChart.vm.chartData[0].name, seriesName: wrapper.vm.chartData[0].name,
componentSubType: 'line', componentSubType: 'line',
value: [mockDate, 5.55555], value: [mockDate, 5.55555],
dataIndex: 0, dataIndex: 0,
...@@ -210,86 +215,118 @@ describe('Time series component', () => { ...@@ -210,86 +215,118 @@ describe('Time series component', () => {
value: undefined, value: undefined,
})), })),
}; };
expect(timeSeriesChart.vm.formatTooltipText(seriesDataWithoutValue)).toBeUndefined(); expect(wrapper.vm.formatTooltipText(seriesDataWithoutValue)).toBeUndefined();
}); });
describe('when series is of line type', () => { describe('when series is of line type', () => {
beforeEach(done => { beforeEach(() => {
timeSeriesChart.vm.formatTooltipText(mockLineSeriesData()); createWrapper();
timeSeriesChart.vm.$nextTick(done); wrapper.vm.formatTooltipText(mockLineSeriesData());
return wrapper.vm.$nextTick();
}); });
it('formats tooltip title', () => { it('formats tooltip title', () => {
expect(timeSeriesChart.vm.tooltip.title).toBe('16 Jul 2019, 10:14AM'); expect(wrapper.vm.tooltip.title).toBe('16 Jul 2019, 10:14AM (GMT+0000)');
}); });
it('formats tooltip content', () => { it('formats tooltip content', () => {
const name = 'Status Code'; const name = 'Status Code';
const value = '5.556'; const value = '5.556';
const dataIndex = 0; const dataIndex = 0;
const seriesLabel = timeSeriesChart.find(GlChartSeriesLabel); const seriesLabel = wrapper.find(GlChartSeriesLabel);
expect(seriesLabel.vm.color).toBe(''); expect(seriesLabel.vm.color).toBe('');
expect(shallowWrapperContainsSlotText(seriesLabel, 'default', name)).toBe(true); expect(shallowWrapperContainsSlotText(seriesLabel, 'default', name)).toBe(true);
expect(timeSeriesChart.vm.tooltip.content).toEqual([ expect(wrapper.vm.tooltip.content).toEqual([
{ name, value, dataIndex, color: undefined }, { name, value, dataIndex, color: undefined },
]); ]);
expect( expect(
shallowWrapperContainsSlotText( shallowWrapperContainsSlotText(wrapper.find(GlAreaChart), 'tooltipContent', value),
timeSeriesChart.find(GlAreaChart),
'tooltipContent',
value,
),
).toBe(true); ).toBe(true);
}); });
describe('when in PT timezone', () => {
beforeAll(() => {
// Note: node.js env renders (GMT-0700), in the browser we see (PDT)
timezoneMock.register('US/Pacific');
});
afterAll(() => {
timezoneMock.unregister();
});
it('formats tooltip title in local timezone by default', () => {
createWrapper();
wrapper.vm.formatTooltipText(mockLineSeriesData());
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.vm.tooltip.title).toBe('16 Jul 2019, 3:14AM (GMT-0700)');
});
});
it('formats tooltip title in local timezone', () => {
createWrapper({ timezone: 'LOCAL' });
wrapper.vm.formatTooltipText(mockLineSeriesData());
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.vm.tooltip.title).toBe('16 Jul 2019, 3:14AM (GMT-0700)');
});
});
it('formats tooltip title in UTC format', () => {
createWrapper({ timezone: 'UTC' });
wrapper.vm.formatTooltipText(mockLineSeriesData());
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.vm.tooltip.title).toBe('16 Jul 2019, 10:14AM (UTC)');
});
});
});
}); });
describe('when series is of scatter type, for deployments', () => { describe('when series is of scatter type, for deployments', () => {
beforeEach(() => { beforeEach(() => {
timeSeriesChart.vm.formatTooltipText({ wrapper.vm.formatTooltipText({
...mockAnnotationsSeriesData, ...mockAnnotationsSeriesData,
seriesData: mockAnnotationsSeriesData.seriesData.map(data => ({ seriesData: mockAnnotationsSeriesData.seriesData.map(data => ({
...data, ...data,
data: annotationsMetadata, data: annotationsMetadata,
})), })),
}); });
return timeSeriesChart.vm.$nextTick; return wrapper.vm.$nextTick;
}); });
it('set tooltip type to deployments', () => { it('set tooltip type to deployments', () => {
expect(timeSeriesChart.vm.tooltip.type).toBe('deployments'); expect(wrapper.vm.tooltip.type).toBe('deployments');
}); });
it('formats tooltip title', () => { it('formats tooltip title', () => {
expect(timeSeriesChart.vm.tooltip.title).toBe('16 Jul 2019, 10:14AM'); expect(wrapper.vm.tooltip.title).toBe('16 Jul 2019, 10:14AM (GMT+0000)');
}); });
it('formats tooltip sha', () => { it('formats tooltip sha', () => {
expect(timeSeriesChart.vm.tooltip.sha).toBe('f5bcd1d9'); expect(wrapper.vm.tooltip.sha).toBe('f5bcd1d9');
}); });
it('formats tooltip commit url', () => { it('formats tooltip commit url', () => {
expect(timeSeriesChart.vm.tooltip.commitUrl).toBe(mockCommitUrl); expect(wrapper.vm.tooltip.commitUrl).toBe(mockCommitUrl);
}); });
}); });
describe('when series is of scatter type and deployments data is missing', () => { describe('when series is of scatter type and deployments data is missing', () => {
beforeEach(() => { beforeEach(() => {
timeSeriesChart.vm.formatTooltipText(mockAnnotationsSeriesData); wrapper.vm.formatTooltipText(mockAnnotationsSeriesData);
return timeSeriesChart.vm.$nextTick; return wrapper.vm.$nextTick;
}); });
it('formats tooltip title', () => { it('formats tooltip title', () => {
expect(timeSeriesChart.vm.tooltip.title).toBe('16 Jul 2019, 10:14AM'); expect(wrapper.vm.tooltip.title).toBe('16 Jul 2019, 10:14AM (GMT+0000)');
}); });
it('formats tooltip sha', () => { it('formats tooltip sha', () => {
expect(timeSeriesChart.vm.tooltip.sha).toBeUndefined(); expect(wrapper.vm.tooltip.sha).toBeUndefined();
}); });
it('formats tooltip commit url', () => { it('formats tooltip commit url', () => {
expect(timeSeriesChart.vm.tooltip.commitUrl).toBeUndefined(); expect(wrapper.vm.tooltip.commitUrl).toBeUndefined();
}); });
}); });
}); });
...@@ -313,10 +350,8 @@ describe('Time series component', () => { ...@@ -313,10 +350,8 @@ describe('Time series component', () => {
}; };
it('formats tooltip title and sets tooltip content', () => { it('formats tooltip title and sets tooltip content', () => {
const formattedTooltipData = timeSeriesChart.vm.formatAnnotationsTooltipText( const formattedTooltipData = wrapper.vm.formatAnnotationsTooltipText(mockMarkPoint);
mockMarkPoint, expect(formattedTooltipData.title).toBe('19 Feb 2020, 10:01AM (GMT+0000)');
);
expect(formattedTooltipData.title).toBe('19 Feb 2020, 10:01AM');
expect(formattedTooltipData.content).toBe(annotationsMetadata.tooltipData.content); expect(formattedTooltipData.content).toBe(annotationsMetadata.tooltipData.content);
}); });
}); });
...@@ -325,8 +360,8 @@ describe('Time series component', () => { ...@@ -325,8 +360,8 @@ describe('Time series component', () => {
const mockSvgName = 'mockSvgName'; const mockSvgName = 'mockSvgName';
beforeEach(done => { beforeEach(done => {
timeSeriesChart.vm.setSvg(mockSvgName); wrapper.vm.setSvg(mockSvgName);
timeSeriesChart.vm.$nextTick(done); wrapper.vm.$nextTick(done);
}); });
it('gets svg path content', () => { it('gets svg path content', () => {
...@@ -334,14 +369,14 @@ describe('Time series component', () => { ...@@ -334,14 +369,14 @@ describe('Time series component', () => {
}); });
it('sets svg path content', () => { it('sets svg path content', () => {
timeSeriesChart.vm.$nextTick(() => { wrapper.vm.$nextTick(() => {
expect(timeSeriesChart.vm.svgs[mockSvgName]).toBe(`path://${mockSvgPathContent}`); expect(wrapper.vm.svgs[mockSvgName]).toBe(`path://${mockSvgPathContent}`);
}); });
}); });
it('contains an svg object within an array to properly render icon', () => { it('contains an svg object within an array to properly render icon', () => {
timeSeriesChart.vm.$nextTick(() => { wrapper.vm.$nextTick(() => {
expect(timeSeriesChart.vm.chartOptions.dataZoom).toEqual([ expect(wrapper.vm.chartOptions.dataZoom).toEqual([
{ {
handleIcon: `path://${mockSvgPathContent}`, handleIcon: `path://${mockSvgPathContent}`,
}, },
...@@ -357,11 +392,11 @@ describe('Time series component', () => { ...@@ -357,11 +392,11 @@ describe('Time series component', () => {
jest.spyOn(Element.prototype, 'getBoundingClientRect').mockImplementation(() => ({ jest.spyOn(Element.prototype, 'getBoundingClientRect').mockImplementation(() => ({
width: mockWidth, width: mockWidth,
})); }));
timeSeriesChart.vm.onResize(); wrapper.vm.onResize();
}); });
it('sets area chart width', () => { it('sets area chart width', () => {
expect(timeSeriesChart.vm.width).toBe(mockWidth); expect(wrapper.vm.width).toBe(mockWidth);
}); });
}); });
}); });
...@@ -374,7 +409,7 @@ describe('Time series component', () => { ...@@ -374,7 +409,7 @@ describe('Time series component', () => {
const seriesData = () => chartData[0]; const seriesData = () => chartData[0];
beforeEach(() => { beforeEach(() => {
({ chartData } = timeSeriesChart.vm); ({ chartData } = wrapper.vm);
}); });
it('utilizes all data points', () => { it('utilizes all data points', () => {
...@@ -408,17 +443,17 @@ describe('Time series component', () => { ...@@ -408,17 +443,17 @@ describe('Time series component', () => {
}; };
it('arbitrary options', () => { it('arbitrary options', () => {
timeSeriesChart.setProps({ wrapper.setProps({
option: mockOption, option: mockOption,
}); });
return timeSeriesChart.vm.$nextTick().then(() => { return wrapper.vm.$nextTick().then(() => {
expect(getChartOptions()).toEqual(expect.objectContaining(mockOption)); expect(getChartOptions()).toEqual(expect.objectContaining(mockOption));
}); });
}); });
it('additional series', () => { it('additional series', () => {
timeSeriesChart.setProps({ wrapper.setProps({
option: { option: {
series: [ series: [
{ {
...@@ -430,7 +465,7 @@ describe('Time series component', () => { ...@@ -430,7 +465,7 @@ describe('Time series component', () => {
}, },
}); });
return timeSeriesChart.vm.$nextTick().then(() => { return wrapper.vm.$nextTick().then(() => {
const optionSeries = getChartOptions().series; const optionSeries = getChartOptions().series;
expect(optionSeries.length).toEqual(2); expect(optionSeries.length).toEqual(2);
...@@ -446,13 +481,13 @@ describe('Time series component', () => { ...@@ -446,13 +481,13 @@ describe('Time series component', () => {
}, },
}; };
timeSeriesChart.setProps({ wrapper.setProps({
option: { option: {
yAxis: mockCustomYAxisOption, yAxis: mockCustomYAxisOption,
}, },
}); });
return timeSeriesChart.vm.$nextTick().then(() => { return wrapper.vm.$nextTick().then(() => {
const { yAxis } = getChartOptions(); const { yAxis } = getChartOptions();
expect(yAxis[0]).toMatchObject(mockCustomYAxisOption); expect(yAxis[0]).toMatchObject(mockCustomYAxisOption);
...@@ -464,13 +499,13 @@ describe('Time series component', () => { ...@@ -464,13 +499,13 @@ describe('Time series component', () => {
name: 'Custom x axis label', name: 'Custom x axis label',
}; };
timeSeriesChart.setProps({ wrapper.setProps({
option: { option: {
xAxis: mockCustomXAxisOption, xAxis: mockCustomXAxisOption,
}, },
}); });
return timeSeriesChart.vm.$nextTick().then(() => { return wrapper.vm.$nextTick().then(() => {
const { xAxis } = getChartOptions(); const { xAxis } = getChartOptions();
expect(xAxis).toMatchObject(mockCustomXAxisOption); expect(xAxis).toMatchObject(mockCustomXAxisOption);
...@@ -499,7 +534,7 @@ describe('Time series component', () => { ...@@ -499,7 +534,7 @@ describe('Time series component', () => {
describe('annotationSeries', () => { describe('annotationSeries', () => {
it('utilizes deployment data', () => { it('utilizes deployment data', () => {
const annotationSeries = timeSeriesChart.vm.chartOptionSeries[0]; const annotationSeries = wrapper.vm.chartOptionSeries[0];
expect(annotationSeries.yAxisIndex).toBe(1); // same as annotations y axis expect(annotationSeries.yAxisIndex).toBe(1); // same as annotations y axis
expect(annotationSeries.data).toEqual([ expect(annotationSeries.data).toEqual([
expect.objectContaining({ expect.objectContaining({
...@@ -518,6 +553,45 @@ describe('Time series component', () => { ...@@ -518,6 +553,45 @@ describe('Time series component', () => {
}); });
}); });
describe('xAxisLabel', () => {
const mockDate = Date.UTC(2020, 4, 26, 20); // 8:00 PM in GMT
const useXAxisFormatter = date => {
const { xAxis } = getChartOptions();
const { formatter } = xAxis.axisLabel;
return formatter(date);
};
it('x-axis is formatted correctly in AM/PM format', () => {
expect(useXAxisFormatter(mockDate)).toEqual('8:00 PM');
});
describe('when in PT timezone', () => {
beforeAll(() => {
timezoneMock.register('US/Pacific');
});
afterAll(() => {
timezoneMock.unregister();
});
it('by default, values are formatted in PT', () => {
createWrapper();
expect(useXAxisFormatter(mockDate)).toEqual('1:00 PM');
});
it('when the chart uses local timezone, y-axis is formatted in PT', () => {
createWrapper({ timezone: 'LOCAL' });
expect(useXAxisFormatter(mockDate)).toEqual('1:00 PM');
});
it('when the chart uses UTC, y-axis is formatted in UTC', () => {
createWrapper({ timezone: 'UTC' });
expect(useXAxisFormatter(mockDate)).toEqual('8:00 PM');
});
});
});
describe('yAxisLabel', () => { describe('yAxisLabel', () => {
it('y-axis is configured correctly', () => { it('y-axis is configured correctly', () => {
const { yAxis } = getChartOptions(); const { yAxis } = getChartOptions();
...@@ -544,7 +618,7 @@ describe('Time series component', () => { ...@@ -544,7 +618,7 @@ describe('Time series component', () => {
}); });
afterEach(() => { afterEach(() => {
timeSeriesChart.destroy(); wrapper.destroy();
}); });
}); });
...@@ -562,19 +636,14 @@ describe('Time series component', () => { ...@@ -562,19 +636,14 @@ describe('Time series component', () => {
glChartComponents.forEach(dynamicComponent => { glChartComponents.forEach(dynamicComponent => {
describe(`GitLab UI: ${dynamicComponent.chartType}`, () => { describe(`GitLab UI: ${dynamicComponent.chartType}`, () => {
let timeSeriesAreaChart; const findChartComponent = () => wrapper.find(dynamicComponent.component);
const findChartComponent = () => timeSeriesAreaChart.find(dynamicComponent.component);
beforeEach(done => { beforeEach(done => {
timeSeriesAreaChart = createWrapper( createWrapper(
{ ...mockGraphData, type: dynamicComponent.chartType }, { graphData: { ...mockGraphData, type: dynamicComponent.chartType } },
mount, mount,
); );
timeSeriesAreaChart.vm.$nextTick(done); wrapper.vm.$nextTick(done);
});
afterEach(() => {
timeSeriesAreaChart.destroy();
}); });
it('is a Vue instance', () => { it('is a Vue instance', () => {
...@@ -585,17 +654,17 @@ describe('Time series component', () => { ...@@ -585,17 +654,17 @@ describe('Time series component', () => {
it('receives data properties needed for proper chart render', () => { it('receives data properties needed for proper chart render', () => {
const props = findChartComponent().props(); const props = findChartComponent().props();
expect(props.data).toBe(timeSeriesAreaChart.vm.chartData); expect(props.data).toBe(wrapper.vm.chartData);
expect(props.option).toBe(timeSeriesAreaChart.vm.chartOptions); expect(props.option).toBe(wrapper.vm.chartOptions);
expect(props.formatTooltipText).toBe(timeSeriesAreaChart.vm.formatTooltipText); expect(props.formatTooltipText).toBe(wrapper.vm.formatTooltipText);
expect(props.thresholds).toBe(timeSeriesAreaChart.vm.thresholds); expect(props.thresholds).toBe(wrapper.vm.thresholds);
}); });
it('recieves a tooltip title', done => { it('recieves a tooltip title', done => {
const mockTitle = 'mockTitle'; const mockTitle = 'mockTitle';
timeSeriesAreaChart.vm.tooltip.title = mockTitle; wrapper.vm.tooltip.title = mockTitle;
timeSeriesAreaChart.vm.$nextTick(() => { wrapper.vm.$nextTick(() => {
expect( expect(
shallowWrapperContainsSlotText(findChartComponent(), 'tooltipTitle', mockTitle), shallowWrapperContainsSlotText(findChartComponent(), 'tooltipTitle', mockTitle),
).toBe(true); ).toBe(true);
...@@ -608,12 +677,12 @@ describe('Time series component', () => { ...@@ -608,12 +677,12 @@ describe('Time series component', () => {
const commitUrl = `${mockProjectDir}/-/commit/${mockSha}`; const commitUrl = `${mockProjectDir}/-/commit/${mockSha}`;
beforeEach(done => { beforeEach(done => {
timeSeriesAreaChart.setData({ wrapper.setData({
tooltip: { tooltip: {
type: 'deployments', type: 'deployments',
}, },
}); });
timeSeriesAreaChart.vm.$nextTick(done); wrapper.vm.$nextTick(done);
}); });
it('uses deployment title', () => { it('uses deployment title', () => {
...@@ -623,11 +692,11 @@ describe('Time series component', () => { ...@@ -623,11 +692,11 @@ describe('Time series component', () => {
}); });
it('renders clickable commit sha in tooltip content', done => { it('renders clickable commit sha in tooltip content', done => {
timeSeriesAreaChart.vm.tooltip.sha = mockSha; wrapper.vm.tooltip.sha = mockSha;
timeSeriesAreaChart.vm.tooltip.commitUrl = commitUrl; wrapper.vm.tooltip.commitUrl = commitUrl;
timeSeriesAreaChart.vm.$nextTick(() => { wrapper.vm.$nextTick(() => {
const commitLink = timeSeriesAreaChart.find(GlLink); const commitLink = wrapper.find(GlLink);
expect(shallowWrapperContainsSlotText(commitLink, 'default', mockSha)).toBe(true); expect(shallowWrapperContainsSlotText(commitLink, 'default', mockSha)).toBe(true);
expect(commitLink.attributes('href')).toEqual(commitUrl); expect(commitLink.attributes('href')).toEqual(commitUrl);
...@@ -642,8 +711,6 @@ describe('Time series component', () => { ...@@ -642,8 +711,6 @@ describe('Time series component', () => {
describe('with multiple time series', () => { describe('with multiple time series', () => {
describe('General functions', () => { describe('General functions', () => {
let timeSeriesChart;
beforeEach(done => { beforeEach(done => {
store = createStore(); store = createStore();
const graphData = cloneDeep(metricsDashboardViewModel.panelGroups[0].panels[3]); const graphData = cloneDeep(metricsDashboardViewModel.panelGroups[0].panels[3]);
...@@ -651,21 +718,19 @@ describe('Time series component', () => { ...@@ -651,21 +718,19 @@ describe('Time series component', () => {
Object.assign(metric, { result: metricResultStatus.result }), Object.assign(metric, { result: metricResultStatus.result }),
); );
timeSeriesChart = createWrapper({ ...graphData, type: 'area-chart' }, mount); createWrapper({ graphData: { ...graphData, type: 'area-chart' } }, mount);
timeSeriesChart.vm.$nextTick(done); wrapper.vm.$nextTick(done);
}); });
afterEach(() => { afterEach(() => {
timeSeriesChart.destroy(); wrapper.destroy();
}); });
describe('Color match', () => { describe('Color match', () => {
let lineColors; let lineColors;
beforeEach(() => { beforeEach(() => {
lineColors = timeSeriesChart lineColors = wrapper.find(GlAreaChart).vm.series.map(item => item.lineStyle.color);
.find(GlAreaChart)
.vm.series.map(item => item.lineStyle.color);
}); });
it('should contain different colors for contiguous time series', () => { it('should contain different colors for contiguous time series', () => {
...@@ -675,7 +740,7 @@ describe('Time series component', () => { ...@@ -675,7 +740,7 @@ describe('Time series component', () => {
}); });
it('should match series color with tooltip label color', () => { it('should match series color with tooltip label color', () => {
const labels = timeSeriesChart.findAll(GlChartSeriesLabel); const labels = wrapper.findAll(GlChartSeriesLabel);
lineColors.forEach((color, index) => { lineColors.forEach((color, index) => {
const labelColor = labels.at(index).props('color'); const labelColor = labels.at(index).props('color');
...@@ -684,7 +749,7 @@ describe('Time series component', () => { ...@@ -684,7 +749,7 @@ describe('Time series component', () => {
}); });
it('should match series color with legend color', () => { it('should match series color with legend color', () => {
const legendColors = timeSeriesChart const legendColors = wrapper
.find(GlChartLegend) .find(GlChartLegend)
.props('seriesInfo') .props('seriesInfo')
.map(item => item.color); .map(item => item.color);
......
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