From fd943756d6e3f32915967e0fd54e1cd328b28cce Mon Sep 17 00:00:00 2001 From: dej611 Date: Mon, 12 Jul 2021 15:45:10 +0200 Subject: [PATCH 1/6] :sparkles: Unify escaping logic for csv export --- src/plugins/data/common/exports/constants.ts | 11 +++++++ .../data/common/exports}/escape_value.test.ts | 5 ++-- .../data/common/exports}/escape_value.ts | 14 ++++----- .../data/common/exports/export_csv.test.ts | 12 ++++++++ .../data/common/exports/export_csv.tsx | 26 ++++------------- .../data/common/exports/formula_checks.ts | 21 ++++++++++++++ src/plugins/data/common/exports/index.ts | 3 ++ src/plugins/data/public/index.ts | 4 ++- .../components/download_options.tsx | 29 +++++++++++++++++-- .../public/components/table_vis_controls.tsx | 26 +++++++++++++++-- .../lens/public/app_plugin/lens_top_nav.tsx | 28 +++++++++++++++++- .../plugins/lens/public/app_plugin/types.ts | 4 +++ .../generate_csv/check_cells_for_formulas.ts | 2 +- .../export_types/csv/generate_csv/index.ts | 2 +- .../generate_csv/cell_has_formula.ts | 12 -------- .../generate_csv/generate_csv.ts | 2 +- .../generate_csv/get_export_settings.ts | 2 +- 17 files changed, 150 insertions(+), 53 deletions(-) create mode 100644 src/plugins/data/common/exports/constants.ts rename {x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv => src/plugins/data/common/exports}/escape_value.test.ts (93%) rename {x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv => src/plugins/data/common/exports}/escape_value.ts (65%) create mode 100644 src/plugins/data/common/exports/formula_checks.ts delete mode 100644 x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/cell_has_formula.ts diff --git a/src/plugins/data/common/exports/constants.ts b/src/plugins/data/common/exports/constants.ts new file mode 100644 index 00000000000000..9c256d4d3ea97e --- /dev/null +++ b/src/plugins/data/common/exports/constants.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const CSV_FORMULA_CHARS = ['=', '+', '-', '@']; +export const nonAlphaNumRE = /[^a-zA-Z0-9]/; +export const allDoubleQuoteRE = /"/g; diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/escape_value.test.ts b/src/plugins/data/common/exports/escape_value.test.ts similarity index 93% rename from x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/escape_value.test.ts rename to src/plugins/data/common/exports/escape_value.test.ts index 6bc1a3b4627a68..9463af644d4179 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/escape_value.test.ts +++ b/src/plugins/data/common/exports/escape_value.test.ts @@ -1,8 +1,9 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import expect from '@kbn/expect'; diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/escape_value.ts b/src/plugins/data/common/exports/escape_value.ts similarity index 65% rename from x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/escape_value.ts rename to src/plugins/data/common/exports/escape_value.ts index e4dbdb2f396a46..9277f792a4b864 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/escape_value.ts +++ b/src/plugins/data/common/exports/escape_value.ts @@ -1,15 +1,14 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ +import { allDoubleQuoteRE, nonAlphaNumRE } from './constants'; +import { cellHasFormulas } from './formula_checks'; -import { RawValue } from '../types'; -import { cellHasFormulas } from './cell_has_formula'; - -const nonAlphaNumRE = /[^a-zA-Z0-9]/; -const allDoubleQuoteRE = /"/g; +type RawValue = string | object | null | undefined; export function createEscapeValue( quoteValues: boolean, @@ -22,7 +21,6 @@ export function createEscapeValue( return `"${formulasEscaped.replace(allDoubleQuoteRE, '""')}"`; } } - return val == null ? '' : val.toString(); }; } diff --git a/src/plugins/data/common/exports/export_csv.test.ts b/src/plugins/data/common/exports/export_csv.test.ts index 2070ec9417cd16..2fde17fc1f438b 100644 --- a/src/plugins/data/common/exports/export_csv.test.ts +++ b/src/plugins/data/common/exports/export_csv.test.ts @@ -71,4 +71,16 @@ describe('CSV exporter', () => { 'columnOne\r\n"Formatted_""value"""\r\n' ); }); + + test('should escape formulas', () => { + const datatable = getDataTable(); + datatable.rows[0].col1 = '=1'; + expect( + datatableToCSV(datatable, { + ...getDefaultOptions(), + escapeFormulaValues: true, + formatFactory: () => ({ convert: (v: unknown) => v } as FieldFormat), + }) + ).toMatch('columnOne\r\n"\'=1"\r\n'); + }); }); diff --git a/src/plugins/data/common/exports/export_csv.tsx b/src/plugins/data/common/exports/export_csv.tsx index f52e227c667cc7..0b10a86cc961e7 100644 --- a/src/plugins/data/common/exports/export_csv.tsx +++ b/src/plugins/data/common/exports/export_csv.tsx @@ -10,40 +10,26 @@ import { FormatFactory } from 'src/plugins/data/common/field_formats/utils'; import { Datatable } from 'src/plugins/expressions'; +import { createEscapeValue } from './escape_value'; export const LINE_FEED_CHARACTER = '\r\n'; -const nonAlphaNumRE = /[^a-zA-Z0-9]/; -const allDoubleQuoteRE = /"/g; export const CSV_MIME_TYPE = 'text/plain;charset=utf-8'; -// TODO: enhance this later on -function escape(val: object | string, quoteValues: boolean) { - if (val != null && typeof val === 'object') { - val = val.valueOf(); - } - - val = String(val); - - if (quoteValues && nonAlphaNumRE.test(val)) { - val = `"${val.replace(allDoubleQuoteRE, '""')}"`; - } - - return val; -} - interface CSVOptions { csvSeparator: string; quoteValues: boolean; + escapeFormulaValues?: boolean; formatFactory: FormatFactory; raw?: boolean; } export function datatableToCSV( { columns, rows }: Datatable, - { csvSeparator, quoteValues, formatFactory, raw }: CSVOptions + { csvSeparator, quoteValues, formatFactory, raw, escapeFormulaValues = false }: CSVOptions ) { + const escapeValues = createEscapeValue(quoteValues, escapeFormulaValues); // Build the header row by its names - const header = columns.map((col) => escape(col.name, quoteValues)); + const header = columns.map((col) => escapeValues(col.name)); const formatters = columns.reduce>>( (memo, { id, meta }) => { @@ -56,7 +42,7 @@ export function datatableToCSV( // Convert the array of row objects to an array of row arrays const csvRows = rows.map((row) => { return columns.map((column) => - escape(raw ? row[column.id] : formatters[column.id].convert(row[column.id]), quoteValues) + escapeValues(raw ? row[column.id] : formatters[column.id].convert(row[column.id])) ); }); diff --git a/src/plugins/data/common/exports/formula_checks.ts b/src/plugins/data/common/exports/formula_checks.ts new file mode 100644 index 00000000000000..eed339c15781c9 --- /dev/null +++ b/src/plugins/data/common/exports/formula_checks.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { startsWith } from 'lodash'; +import { Datatable } from 'src/plugins/expressions'; +import { CSV_FORMULA_CHARS } from './constants'; + +export const cellHasFormulas = (val: string) => + CSV_FORMULA_CHARS.some((formulaChar) => startsWith(val, formulaChar)); + +export const tableHasFormulas = (columns: Datatable['columns'], rows: Datatable['rows']) => { + return ( + columns.some(({ name }) => cellHasFormulas(name)) || + rows.some((row) => columns.some(({ id }) => cellHasFormulas(row[id]))) + ); +}; diff --git a/src/plugins/data/common/exports/index.ts b/src/plugins/data/common/exports/index.ts index 9e6fb10fae84c4..1dbc4a17091bc5 100644 --- a/src/plugins/data/common/exports/index.ts +++ b/src/plugins/data/common/exports/index.ts @@ -7,3 +7,6 @@ */ export { datatableToCSV, CSV_MIME_TYPE } from './export_csv'; +export { createEscapeValue } from './escape_value'; +export { CSV_FORMULA_CHARS } from './constants'; +export { cellHasFormulas, tableHasFormulas } from './formula_checks'; diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index e9e50ebfaf138c..b5cc3f04f055d3 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -209,10 +209,12 @@ export { * Exporters (CSV) */ -import { datatableToCSV, CSV_MIME_TYPE } from '../common'; +import { datatableToCSV, CSV_MIME_TYPE, cellHasFormulas, tableHasFormulas } from '../common'; export const exporters = { datatableToCSV, CSV_MIME_TYPE, + cellHasFormulas, + tableHasFormulas, }; /* diff --git a/src/plugins/data/public/utils/table_inspector_view/components/download_options.tsx b/src/plugins/data/public/utils/table_inspector_view/components/download_options.tsx index 07230a29ca3f5a..6ff4a1f8b2e55d 100644 --- a/src/plugins/data/public/utils/table_inspector_view/components/download_options.tsx +++ b/src/plugins/data/public/utils/table_inspector_view/components/download_options.tsx @@ -11,8 +11,14 @@ import PropTypes from 'prop-types'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { EuiButton, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; -import { CSV_MIME_TYPE, datatableToCSV } from '../../../../common'; +import { + EuiButton, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiPopover, + EuiToolTip, +} from '@elastic/eui'; +import { CSV_MIME_TYPE, datatableToCSV, tableHasFormulas } from '../../../../common'; import { Datatable } from '../../../../../expressions'; import { downloadMultipleAs } from '../../../../../share/public'; import { FieldFormatsStart } from '../../../field_formats'; @@ -96,6 +102,9 @@ class DataDownloadOptions extends Component + tableHasFormulas(columns, rows) + ); const button = ( ); + const downloadButton = detectedFormulasInTables ? ( + + {button} + + ) : ( + button + ); + const items = [ (); + const detectedFormulasInTable = exporters.tableHasFormulas(columns, rows); + const onClickExport = useCallback( (formatted: boolean) => { const csvSeparator = uiSettings.get(CSV_SEPARATOR_SETTING); @@ -85,6 +93,20 @@ export const TableVisControls = memo( ); + const downloadButton = detectedFormulasInTable ? ( + + {button} + + ) : ( + button + ); + const items = [ onClickExport(false)}> @@ -100,7 +122,7 @@ export const TableVisControls = memo( return ( { + if (activeData) { + const datatables = Object.values(activeData); + const formulaDetected = datatables.some((datatable) => { + return tableHasFormulas(datatable.columns, datatable.rows); + }); + if (formulaDetected) { + return i18n.translate('xpack.lens.app.downloadButtonFormulasWarning', { + defaultMessage: + 'Your CSV contains characters which spreadsheet application can interpret as formulas', + }); + } + } + return undefined; + }, + }, actions: { exportToCSV: () => { if (!activeData) { diff --git a/x-pack/plugins/lens/public/app_plugin/types.ts b/x-pack/plugins/lens/public/app_plugin/types.ts index 7f1c21fa5a9bdf..499ba3c86bca57 100644 --- a/x-pack/plugins/lens/public/app_plugin/types.ts +++ b/x-pack/plugins/lens/public/app_plugin/types.ts @@ -114,6 +114,10 @@ export interface LensAppServices { dashboardFeatureFlag: DashboardFeatureFlagConfig; } +export interface LensTopNavTooltips { + showExportWarning: () => string | undefined; +} + export interface LensTopNavActions { saveAndReturn: () => void; showSaveModal: () => void; diff --git a/x-pack/plugins/reporting/server/export_types/csv/generate_csv/check_cells_for_formulas.ts b/x-pack/plugins/reporting/server/export_types/csv/generate_csv/check_cells_for_formulas.ts index f650bbaed12717..709c0ccd67e9ce 100644 --- a/x-pack/plugins/reporting/server/export_types/csv/generate_csv/check_cells_for_formulas.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/generate_csv/check_cells_for_formulas.ts @@ -6,7 +6,7 @@ */ import { pick, keys, values, some } from 'lodash'; -import { cellHasFormulas } from '../../csv_searchsource/generate_csv/cell_has_formula'; +import { cellHasFormulas } from '../../../../../../../src/plugins/data/common'; interface IFlattened { [header: string]: string; diff --git a/x-pack/plugins/reporting/server/export_types/csv/generate_csv/index.ts b/x-pack/plugins/reporting/server/export_types/csv/generate_csv/index.ts index e5ed04f4cab66e..c1018030827c06 100644 --- a/x-pack/plugins/reporting/server/export_types/csv/generate_csv/index.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/generate_csv/index.ts @@ -8,12 +8,12 @@ import { i18n } from '@kbn/i18n'; import { ElasticsearchClient, IUiSettingsClient } from 'src/core/server'; import { ReportingConfig } from '../../../'; +import { createEscapeValue } from '../../../../../../../src/plugins/data/common'; import { CancellationToken } from '../../../../../../plugins/reporting/common'; import { CSV_BOM_CHARS } from '../../../../common/constants'; import { byteSizeValueToNumber } from '../../../../common/schema_utils'; import { LevelLogger } from '../../../lib'; import { getFieldFormats } from '../../../services'; -import { createEscapeValue } from '../../csv_searchsource/generate_csv/escape_value'; import { MaxSizeStringBuilder } from '../../csv_searchsource/generate_csv/max_size_string_builder'; import { IndexPatternSavedObjectDeprecatedCSV, diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/cell_has_formula.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/cell_has_formula.ts deleted file mode 100644 index aa361fcfbc1950..00000000000000 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/cell_has_formula.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { startsWith } from 'lodash'; -import { CSV_FORMULA_CHARS } from '../../../../common/constants'; - -export const cellHasFormulas = (val: string) => - CSV_FORMULA_CHARS.some((formulaChar) => startsWith(val, formulaChar)); diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts index 1d70b656fed360..254bc0ae21f6ca 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts @@ -22,6 +22,7 @@ import { SearchFieldValue, SearchSourceFields, tabifyDocs, + cellHasFormulas, } from '../../../../../../../src/plugins/data/common'; import { KbnServerError } from '../../../../../../../src/plugins/kibana_utils/server'; import { CancellationToken } from '../../../../common'; @@ -30,7 +31,6 @@ import { byteSizeValueToNumber } from '../../../../common/schema_utils'; import { LevelLogger } from '../../../lib'; import { TaskRunResult } from '../../../lib/tasks'; import { JobParamsCSV } from '../types'; -import { cellHasFormulas } from './cell_has_formula'; import { CsvExportSettings, getExportSettings } from './get_export_settings'; import { MaxSizeStringBuilder } from './max_size_string_builder'; diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/get_export_settings.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/get_export_settings.ts index 17a10f3034242d..0b8815836367f2 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/get_export_settings.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/get_export_settings.ts @@ -8,6 +8,7 @@ import { ByteSizeValue } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; import { IUiSettingsClient } from 'kibana/server'; +import { createEscapeValue } from '../../../../../../../src/plugins/data/common'; import { ReportingConfig } from '../../../'; import { CSV_BOM_CHARS, @@ -16,7 +17,6 @@ import { UI_SETTINGS_CSV_SEPARATOR, } from '../../../../common/constants'; import { LevelLogger } from '../../../lib'; -import { createEscapeValue } from './escape_value'; export interface CsvExportSettings { timezone: string; From 2601e6a5d207cb3ae21c0aa37e9cc64144edc197 Mon Sep 17 00:00:00 2001 From: dej611 Date: Mon, 12 Jul 2021 16:26:49 +0200 Subject: [PATCH 2/6] :memo: Update api doc --- ...na-plugin-plugins-data-public.exporters.md | 2 + src/plugins/data/public/public.api.md | 40 ++++++++++--------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.exporters.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.exporters.md index 883dbcfe289cb2..efba24c008264d 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.exporters.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.exporters.md @@ -10,5 +10,7 @@ exporters: { datatableToCSV: typeof datatableToCSV; CSV_MIME_TYPE: string; + cellHasFormulas: (val: string) => boolean; + tableHasFormulas: (columns: import("../../expressions").DatatableColumn[], rows: Record[]) => boolean; } ``` diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 66d81d058fc77e..f11d721549d6b7 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -929,6 +929,8 @@ export type ExistsFilter = Filter & { export const exporters: { datatableToCSV: typeof datatableToCSV; CSV_MIME_TYPE: string; + cellHasFormulas: (val: string) => boolean; + tableHasFormulas: (columns: import("../../expressions").DatatableColumn[], rows: Record[]) => boolean; }; // Warning: (ae-missing-release-tag) "ExpressionFunctionKibana" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -2763,25 +2765,25 @@ export interface WaitUntilNextSessionCompletesOptions { // src/plugins/data/public/index.ts:170:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:170:26 - (ae-forgotten-export) The symbol "HistogramFormat" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:213:23 - (ae-forgotten-export) The symbol "datatableToCSV" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:409:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:409:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:409:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:411:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:421:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:422:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:423:1 - (ae-forgotten-export) The symbol "IpAddress" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:424:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:428:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:429:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:432:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:433:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:436:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:240:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:240:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:240:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:240:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:240:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:411:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:411:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:411:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:413:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:414:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:423:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:424:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:425:1 - (ae-forgotten-export) The symbol "IpAddress" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:426:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:430:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:431:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:434:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:435:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:438:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:34:5 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts // src/plugins/data/public/search/session/session_service.ts:62:5 - (ae-forgotten-export) The symbol "UrlGeneratorStateMapping" needs to be exported by the entry point index.d.ts From 1f921ce441c794a838e5d26106c2807e3aaaafb7 Mon Sep 17 00:00:00 2001 From: dej611 Date: Mon, 12 Jul 2021 18:29:06 +0200 Subject: [PATCH 3/6] :white_check_mark: Fix test with new escape logic --- .../public/application/actions/export_csv_action.test.tsx | 2 +- .../utils/table_inspector_view/components/data_view.test.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/dashboard/public/application/actions/export_csv_action.test.tsx b/src/plugins/dashboard/public/application/actions/export_csv_action.test.tsx index 20144b47e474bd..2d12d6e7975284 100644 --- a/src/plugins/dashboard/public/application/actions/export_csv_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/export_csv_action.test.tsx @@ -105,7 +105,7 @@ describe('Export CSV action', () => { | Record; expect(result).toEqual({ 'Hello Kibana.csv': { - content: `First Name,Last Name${LINE_FEED_CHARACTER}Kibana,undefined${LINE_FEED_CHARACTER}`, + content: `First Name,Last Name${LINE_FEED_CHARACTER}Kibana,${LINE_FEED_CHARACTER}`, type: 'text/plain;charset=utf-8', }, }); diff --git a/src/plugins/data/public/utils/table_inspector_view/components/data_view.test.tsx b/src/plugins/data/public/utils/table_inspector_view/components/data_view.test.tsx index 4b5a80e9b07439..e6b40bcb5936c4 100644 --- a/src/plugins/data/public/utils/table_inspector_view/components/data_view.test.tsx +++ b/src/plugins/data/public/utils/table_inspector_view/components/data_view.test.tsx @@ -16,6 +16,7 @@ jest.mock('../../../../../share/public', () => ({ })); jest.mock('../../../../common', () => ({ datatableToCSV: jest.fn().mockReturnValue('csv'), + tableHasFormulas: jest.fn().mockReturnValue(false), })); describe('Inspector Data View', () => { From 53bc2b8f4240900998ab43b1881f92f4f1a80fb0 Mon Sep 17 00:00:00 2001 From: dej611 Date: Wed, 14 Jul 2021 17:32:21 +0200 Subject: [PATCH 4/6] :ok_hand: First batch of feedback --- .../public/application/actions/export_csv_action.tsx | 1 + src/plugins/data/common/exports/export_csv.test.ts | 1 + src/plugins/data/common/exports/export_csv.tsx | 4 ++-- .../table_inspector_view/components/download_options.tsx | 1 + .../vis_type_table/public/components/table_vis_controls.tsx | 1 + x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx | 1 + 6 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/plugins/dashboard/public/application/actions/export_csv_action.tsx b/src/plugins/dashboard/public/application/actions/export_csv_action.tsx index c2f651360da1bd..dd6eeb92ef932d 100644 --- a/src/plugins/dashboard/public/application/actions/export_csv_action.tsx +++ b/src/plugins/dashboard/public/application/actions/export_csv_action.tsx @@ -94,6 +94,7 @@ export class ExportCSVAction implements Action { csvSeparator: this.params.core.uiSettings.get('csv:separator', ','), quoteValues: this.params.core.uiSettings.get('csv:quoteValues', true), formatFactory, + escapeFormulaValues: false, }), type: exporters.CSV_MIME_TYPE, }; diff --git a/src/plugins/data/common/exports/export_csv.test.ts b/src/plugins/data/common/exports/export_csv.test.ts index 2fde17fc1f438b..8bf44fe48a5890 100644 --- a/src/plugins/data/common/exports/export_csv.test.ts +++ b/src/plugins/data/common/exports/export_csv.test.ts @@ -17,6 +17,7 @@ function getDefaultOptions() { csvSeparator: ',', quoteValues: true, formatFactory, + escapeFormulaValues: false, }; } diff --git a/src/plugins/data/common/exports/export_csv.tsx b/src/plugins/data/common/exports/export_csv.tsx index 0b10a86cc961e7..d4477e72b64c41 100644 --- a/src/plugins/data/common/exports/export_csv.tsx +++ b/src/plugins/data/common/exports/export_csv.tsx @@ -18,14 +18,14 @@ export const CSV_MIME_TYPE = 'text/plain;charset=utf-8'; interface CSVOptions { csvSeparator: string; quoteValues: boolean; - escapeFormulaValues?: boolean; + escapeFormulaValues: boolean; formatFactory: FormatFactory; raw?: boolean; } export function datatableToCSV( { columns, rows }: Datatable, - { csvSeparator, quoteValues, formatFactory, raw, escapeFormulaValues = false }: CSVOptions + { csvSeparator, quoteValues, formatFactory, raw, escapeFormulaValues }: CSVOptions ) { const escapeValues = createEscapeValue(quoteValues, escapeFormulaValues); // Build the header row by its names diff --git a/src/plugins/data/public/utils/table_inspector_view/components/download_options.tsx b/src/plugins/data/public/utils/table_inspector_view/components/download_options.tsx index 6ff4a1f8b2e55d..b4b6d7f62e7452 100644 --- a/src/plugins/data/public/utils/table_inspector_view/components/download_options.tsx +++ b/src/plugins/data/public/utils/table_inspector_view/components/download_options.tsx @@ -80,6 +80,7 @@ class DataDownloadOptions extends Component Date: Wed, 14 Jul 2021 17:33:32 +0200 Subject: [PATCH 5/6] :speech_balloon: Fix typo --- .../utils/table_inspector_view/components/download_options.tsx | 2 +- .../vis_type_table/public/components/table_vis_controls.tsx | 2 +- x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/data/public/utils/table_inspector_view/components/download_options.tsx b/src/plugins/data/public/utils/table_inspector_view/components/download_options.tsx index b4b6d7f62e7452..55c6fb37c80d4a 100644 --- a/src/plugins/data/public/utils/table_inspector_view/components/download_options.tsx +++ b/src/plugins/data/public/utils/table_inspector_view/components/download_options.tsx @@ -119,7 +119,7 @@ class DataDownloadOptions extends Component {button} diff --git a/src/plugins/vis_type_table/public/components/table_vis_controls.tsx b/src/plugins/vis_type_table/public/components/table_vis_controls.tsx index 1ef7346103060b..458bca4a540611 100644 --- a/src/plugins/vis_type_table/public/components/table_vis_controls.tsx +++ b/src/plugins/vis_type_table/public/components/table_vis_controls.tsx @@ -99,7 +99,7 @@ export const TableVisControls = memo( position="top" content={i18n.translate('visTypeTable.vis.controls.exportButtonFormulasWarning', { defaultMessage: - 'Your CSV contains characters which spreadsheet application can interpret as formulas', + 'Your CSV contains characters which spreadsheet applications can interpret as formulas', })} > {button} diff --git a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx index 8205e32b359617..5f348d2a88ea08 100644 --- a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx +++ b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx @@ -232,7 +232,7 @@ export const LensTopNavMenu = ({ if (formulaDetected) { return i18n.translate('xpack.lens.app.downloadButtonFormulasWarning', { defaultMessage: - 'Your CSV contains characters which spreadsheet application can interpret as formulas', + 'Your CSV contains characters which spreadsheet applications can interpret as formulas', }); } } From 756e252bceb77ac7787db4e39ecd1667191e8293 Mon Sep 17 00:00:00 2001 From: dej611 Date: Thu, 15 Jul 2021 17:07:01 +0200 Subject: [PATCH 6/6] :ok_hand: Memoize function --- .../table_inspector_view/components/download_options.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/plugins/data/public/utils/table_inspector_view/components/download_options.tsx b/src/plugins/data/public/utils/table_inspector_view/components/download_options.tsx index 55c6fb37c80d4a..e79a1c2b52e03d 100644 --- a/src/plugins/data/public/utils/table_inspector_view/components/download_options.tsx +++ b/src/plugins/data/public/utils/table_inspector_view/components/download_options.tsx @@ -7,6 +7,7 @@ */ import React, { Component } from 'react'; +import { memoize } from 'lodash'; import PropTypes from 'prop-types'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -36,6 +37,10 @@ interface DataDownloadOptionsProps { fieldFormats: FieldFormatsStart; } +const detectFormulasInTables = memoize((datatables: Datatable[]) => + datatables.some(({ columns, rows }) => tableHasFormulas(columns, rows)) +); + class DataDownloadOptions extends Component { static propTypes = { title: PropTypes.string.isRequired, @@ -103,9 +108,7 @@ class DataDownloadOptions extends Component - tableHasFormulas(columns, rows) - ); + const detectedFormulasInTables = detectFormulasInTables(this.props.datatables); const button = (