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(() => { describe('wrapped chart', () => {
heatmapChart.destroy(); let glHeatmapChart;
});
describe('wrapped components', () => { beforeEach(() => {
describe('GitLab UI heatmap chart', () => { createWrapper();
let glHeatmapChart; glHeatmapChart = findChart();
});
beforeEach(() => { afterEach(() => {
glHeatmapChart = heatmapChart.find(GlHeatmap); wrapper.destroy();
}); });
it('is a Vue instance', () => { it('is a Vue instance', () => {
expect(glHeatmapChart.isVueInstance()).toBe(true); expect(glHeatmapChart.isVueInstance()).toBe(true);
}); });
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
// each row of the heatmap chart is represented by an array inside another parent array // each row of the heatmap chart is represented by an array inside another parent array
// e.g. [[0, 0, 10]], the format represents the column, the row and finally the value // e.g. [[0, 0, 10]], the format represents the column, the row and finally the value
// 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', () => {
const { xAxisLabels } = wrapper.vm;
expect(xAxisLabels.length).toBe(5);
});
it('returns a series of labels for the x axis', () => { describe('y axis labels', () => {
const { xAxisLabels } = heatmapChart.vm; const gmtLabels = ['3:00 PM', '4:00 PM', '5:00 PM', '6:00 PM', '7:00 PM', '8:00 PM'];
expect(xAxisLabels.length).toBe(5); it('y-axis labels are formatted in AM/PM format', () => {
expect(findChart().props('yAxisLabels')).toEqual(gmtLabels);
}); });
it('returns a series of labels for the y axis', () => { describe('when in PT timezone', () => {
const { yAxisLabels } = heatmapChart.vm; 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);
});
expect(yAxisLabels.length).toBe(6); it('when the chart uses UTC, y-axis is formatted in UTC', () => {
createWrapper({ timezone: 'UTC' });
expect(findChart().props('yAxisLabels')).toEqual(utcLabels);
});
}); });
}); });
}); });
......
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