diff --git a/public/app/core/specs/file_export.test.ts b/public/app/core/specs/file_export.test.ts index ab254a94f2b..49a7b9caf6d 100644 --- a/public/app/core/specs/file_export.test.ts +++ b/public/app/core/specs/file_export.test.ts @@ -28,7 +28,7 @@ describe('file_export', () => { describe('when exporting series as rows', () => { it('should export points in proper order', () => { - const text = fileExport.convertSeriesListToCsv(ctx.seriesList, ctx.timeFormat); + const text = fileExport.convertSeriesListToCsv(ctx.seriesList, { dateTimeFormat: ctx.timeFormat }); const expectedText = '"Series";"Time";"Value"\r\n' + '"series_1";"1500026100";1\r\n' + @@ -48,7 +48,7 @@ describe('file_export', () => { describe('when exporting series as columns', () => { it('should export points in proper order', () => { - const text = fileExport.convertSeriesListToCsvColumns(ctx.seriesList, ctx.timeFormat); + const text = fileExport.convertSeriesListToCsvColumns(ctx.seriesList, { dateTimeFormat: ctx.timeFormat }); const expectedText = '"Time";"series_1";"series_2"\r\n' + '"1500026100";1;11\r\n' + @@ -65,7 +65,7 @@ describe('file_export', () => { const expectedSeries1DataPoints = ctx.seriesList[0].datapoints.slice(); const expectedSeries2DataPoints = ctx.seriesList[1].datapoints.slice(); - fileExport.convertSeriesListToCsvColumns(ctx.seriesList, ctx.timeFormat); + fileExport.convertSeriesListToCsvColumns(ctx.seriesList, { dateTimeFormat: ctx.timeFormat }); expect(expectedSeries1DataPoints).toEqual(ctx.seriesList[0].datapoints); expect(expectedSeries2DataPoints).toEqual(ctx.seriesList[1].datapoints); diff --git a/public/app/core/utils/file_export.ts b/public/app/core/utils/file_export.ts index 212c619e7ea..14fc10cfbb6 100644 --- a/public/app/core/utils/file_export.ts +++ b/public/app/core/utils/file_export.ts @@ -1,7 +1,7 @@ import { isBoolean, isNumber, sortedUniq, sortedIndexOf, unescape as htmlUnescaped } from 'lodash'; import { saveAs } from 'file-saver'; import { isNullOrUndefined } from 'util'; -import { dateTime } from '@grafana/data'; +import { dateTime, TimeZone } from '@grafana/data'; const DEFAULT_DATETIME_FORMAT = 'YYYY-MM-DDTHH:mm:ssZ'; const POINT_TIME_INDEX = 1; @@ -12,7 +12,19 @@ const END_ROW = '\r\n'; const QUOTE = '"'; const EXPORT_FILENAME = 'grafana_data_export.csv'; -function csvEscaped(text) { +interface SeriesListToCsvColumnsOptions { + dateTimeFormat: string; + excel: boolean; + timezone: TimeZone; +} + +const defaultOptions: SeriesListToCsvColumnsOptions = { + dateTimeFormat: DEFAULT_DATETIME_FORMAT, + excel: false, + timezone: '', +}; + +function csvEscaped(text: string) { if (!text) { return text; } @@ -25,13 +37,13 @@ function csvEscaped(text) { } const domParser = new DOMParser(); -function htmlDecoded(text) { +function htmlDecoded(text: string) { if (!text) { return text; } const regexp = /&[^;]+;/g; - function htmlDecoded(value) { + function htmlDecoded(value: string) { const parsedDom = domParser.parseFromString(value, 'text/html'); return parsedDom.body.textContent; } @@ -58,14 +70,19 @@ function formatRow(row, addEndRowDelimiter = true) { return addEndRowDelimiter ? text + END_ROW : text; } -export function convertSeriesListToCsv(seriesList, dateTimeFormat = DEFAULT_DATETIME_FORMAT, excel = false) { +export function convertSeriesListToCsv(seriesList, options: Partial) { + const { dateTimeFormat, excel, timezone } = { ...defaultOptions, ...options }; let text = formatSpecialHeader(excel) + formatRow(['Series', 'Time', 'Value']); for (let seriesIndex = 0; seriesIndex < seriesList.length; seriesIndex += 1) { for (let i = 0; i < seriesList[seriesIndex].datapoints.length; i += 1) { text += formatRow( [ seriesList[seriesIndex].alias, - dateTime(seriesList[seriesIndex].datapoints[i][POINT_TIME_INDEX]).format(dateTimeFormat), + timezone === 'utc' + ? dateTime(seriesList[seriesIndex].datapoints[i][POINT_TIME_INDEX]) + .utc() + .format(dateTimeFormat) + : dateTime(seriesList[seriesIndex].datapoints[i][POINT_TIME_INDEX]).format(dateTimeFormat), seriesList[seriesIndex].datapoints[i][POINT_VALUE_INDEX], ], i < seriesList[seriesIndex].datapoints.length - 1 || seriesIndex < seriesList.length - 1 @@ -75,12 +92,13 @@ export function convertSeriesListToCsv(seriesList, dateTimeFormat = DEFAULT_DATE return text; } -export function exportSeriesListToCsv(seriesList, dateTimeFormat = DEFAULT_DATETIME_FORMAT, excel = false) { - const text = convertSeriesListToCsv(seriesList, dateTimeFormat, excel); +export function exportSeriesListToCsv(seriesList, options: Partial) { + const text = convertSeriesListToCsv(seriesList, options); saveSaveBlob(text, EXPORT_FILENAME); } -export function convertSeriesListToCsvColumns(seriesList, dateTimeFormat = DEFAULT_DATETIME_FORMAT, excel = false) { +export function convertSeriesListToCsvColumns(seriesList, options: Partial) { + const { dateTimeFormat, excel, timezone } = { ...defaultOptions, ...options }; // add header let text = formatSpecialHeader(excel) + @@ -96,7 +114,13 @@ export function convertSeriesListToCsvColumns(seriesList, dateTimeFormat = DEFAU // make text for (let i = 0; i < extendedDatapointsList[0].length; i += 1) { - const timestamp = dateTime(extendedDatapointsList[0][i][POINT_TIME_INDEX]).format(dateTimeFormat); + const timestamp = + timezone === 'utc' + ? dateTime(extendedDatapointsList[0][i][POINT_TIME_INDEX]) + .utc() + .format(dateTimeFormat) + : dateTime(extendedDatapointsList[0][i][POINT_TIME_INDEX]).format(dateTimeFormat); + text += formatRow( [timestamp].concat( extendedDatapointsList.map(datapoints => { @@ -143,8 +167,8 @@ function mergeSeriesByTime(seriesList) { return result; } -export function exportSeriesListToCsvColumns(seriesList, dateTimeFormat = DEFAULT_DATETIME_FORMAT, excel = false) { - const text = convertSeriesListToCsvColumns(seriesList, dateTimeFormat, excel); +export function exportSeriesListToCsvColumns(seriesList, options: Partial) { + const text = convertSeriesListToCsvColumns(seriesList, options); saveSaveBlob(text, EXPORT_FILENAME); } diff --git a/public/app/features/dashboard/components/ExportDataModal/ExportDataModalCtrl.ts b/public/app/features/dashboard/components/ExportDataModal/ExportDataModalCtrl.ts index f87daa94ee7..023b60ac074 100644 --- a/public/app/features/dashboard/components/ExportDataModal/ExportDataModalCtrl.ts +++ b/public/app/features/dashboard/components/ExportDataModal/ExportDataModalCtrl.ts @@ -1,6 +1,7 @@ import angular from 'angular'; import * as fileExport from 'app/core/utils/file_export'; import appEvents from 'app/core/app_events'; +import { DashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; export class ExportDataModalCtrl { private data: any[]; @@ -9,14 +10,23 @@ export class ExportDataModalCtrl { dateTimeFormat = 'YYYY-MM-DDTHH:mm:ssZ'; excel = false; + /** @ngInject */ + constructor(private dashboardSrv: DashboardSrv) {} + export() { + const timezone = this.dashboardSrv.getCurrent().timezone; + const options = { + excel: this.excel, + dateTimeFormat: this.dateTimeFormat, + timezone, + }; if (this.panel === 'table') { fileExport.exportTableDataToCsv(this.data, this.excel); } else { if (this.asRows) { - fileExport.exportSeriesListToCsv(this.data, this.dateTimeFormat, this.excel); + fileExport.exportSeriesListToCsv(this.data, options); } else { - fileExport.exportSeriesListToCsvColumns(this.data, this.dateTimeFormat, this.excel); + fileExport.exportSeriesListToCsvColumns(this.data, options); } }