[Lens][Visualize][Inspector][Reporting] Unified check for CSV cells for known formula characters (and value escaping more in general) (#105221) (#105925)

*  Unify escaping logic for csv export

* 📝 Update api doc

*  Fix test with new escape logic

* 👌 First batch of feedback

* 💬 Fix typo

* 👌 Memoize function

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>

Co-authored-by: Marco Liberati <dej611@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2021-07-16 07:07:19 -04:00 committed by GitHub
parent 14f940f400
commit f1e89f550a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 183 additions and 73 deletions

View file

@ -10,5 +10,7 @@
exporters: {
datatableToCSV: typeof datatableToCSV;
CSV_MIME_TYPE: string;
cellHasFormulas: (val: string) => boolean;
tableHasFormulas: (columns: import("../../expressions").DatatableColumn[], rows: Record<string, any>[]) => boolean;
}
```

View file

@ -105,7 +105,7 @@ describe('Export CSV action', () => {
| Record<string, { content: string; type: string }>;
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',
},
});

View file

@ -94,6 +94,7 @@ export class ExportCSVAction implements Action<ExportContext> {
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,
};

View file

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

View file

@ -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';

View file

@ -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();
};
}

View file

@ -17,6 +17,7 @@ function getDefaultOptions() {
csvSeparator: ',',
quoteValues: true,
formatFactory,
escapeFormulaValues: false,
};
}
@ -71,4 +72,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');
});
});

View file

@ -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 }: 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<Record<string, ReturnType<FormatFactory>>>(
(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]))
);
});

View file

@ -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])))
);
};

View file

@ -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';

View file

@ -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,
};
/*

View file

@ -927,6 +927,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<string, any>[]) => boolean;
};
// Warning: (ae-missing-release-tag) "ExpressionFunctionKibana" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
@ -2783,25 +2785,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:410:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:410:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:410:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:413:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:422:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:423:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:424:1 - (ae-forgotten-export) The symbol "IpAddress" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:425:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:429:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:430:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:433:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:434:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:437: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:412:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:412:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:412:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:414:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:415:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:424:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:425:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:426:1 - (ae-forgotten-export) The symbol "IpAddress" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:427:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:431:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:432:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:435:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:436:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:439: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

View file

@ -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', () => {

View file

@ -7,12 +7,19 @@
*/
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';
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';
@ -30,6 +37,10 @@ interface DataDownloadOptionsProps {
fieldFormats: FieldFormatsStart;
}
const detectFormulasInTables = memoize((datatables: Datatable[]) =>
datatables.some(({ columns, rows }) => tableHasFormulas(columns, rows))
);
class DataDownloadOptions extends Component<DataDownloadOptionsProps, DataDownloadOptionsState> {
static propTypes = {
title: PropTypes.string.isRequired,
@ -74,6 +85,7 @@ class DataDownloadOptions extends Component<DataDownloadOptionsProps, DataDownlo
quoteValues: this.props.uiSettings.get('csv:quoteValues', true),
raw: !isFormatted,
formatFactory: this.props.fieldFormats.deserialize,
escapeFormulaValues: false,
}),
type: CSV_MIME_TYPE,
};
@ -96,6 +108,7 @@ class DataDownloadOptions extends Component<DataDownloadOptionsProps, DataDownlo
};
renderFormattedDownloads() {
const detectedFormulasInTables = detectFormulasInTables(this.props.datatables);
const button = (
<EuiButton iconType="arrowDown" iconSide="right" size="s" onClick={this.onTogglePopover}>
<FormattedMessage
@ -104,6 +117,20 @@ class DataDownloadOptions extends Component<DataDownloadOptionsProps, DataDownlo
/>
</EuiButton>
);
const downloadButton = detectedFormulasInTables ? (
<EuiToolTip
position="top"
content={i18n.translate('data.inspector.table.exportButtonFormulasWarning', {
defaultMessage:
'Your CSV contains characters which spreadsheet applications can interpret as formulas',
})}
>
{button}
</EuiToolTip>
) : (
button
);
const items = [
<EuiContextMenuItem
key="csv"
@ -139,7 +166,7 @@ class DataDownloadOptions extends Component<DataDownloadOptionsProps, DataDownlo
return (
<EuiPopover
id="inspectorDownloadData"
button={button}
button={downloadButton}
isOpen={this.state.isPopoverOpen}
closePopover={this.closePopover}
panelPaddingSize="none"

View file

@ -7,7 +7,13 @@
*/
import React, { memo, useState, useCallback } from 'react';
import { EuiButtonEmpty, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui';
import {
EuiButtonEmpty,
EuiContextMenuItem,
EuiContextMenuPanel,
EuiPopover,
EuiToolTip,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
@ -39,6 +45,8 @@ export const TableVisControls = memo(
services: { uiSettings },
} = useKibana<CoreStart>();
const detectedFormulasInTable = exporters.tableHasFormulas(columns, rows);
const onClickExport = useCallback(
(formatted: boolean) => {
const csvSeparator = uiSettings.get(CSV_SEPARATOR_SETTING);
@ -55,6 +63,7 @@ export const TableVisControls = memo(
quoteValues,
formatFactory: getFormatService().deserialize,
raw: !formatted,
escapeFormulaValues: false,
}
);
downloadFileAs(`${filename || 'unsaved'}.csv`, { content, type: exporters.CSV_MIME_TYPE });
@ -85,6 +94,20 @@ export const TableVisControls = memo(
</EuiButtonEmpty>
);
const downloadButton = detectedFormulasInTable ? (
<EuiToolTip
position="top"
content={i18n.translate('visTypeTable.vis.controls.exportButtonFormulasWarning', {
defaultMessage:
'Your CSV contains characters which spreadsheet applications can interpret as formulas',
})}
>
{button}
</EuiToolTip>
) : (
button
);
const items = [
<EuiContextMenuItem key="rawCsv" onClick={() => onClickExport(false)}>
<FormattedMessage id="visTypeTable.vis.controls.rawCSVButtonLabel" defaultMessage="Raw" />
@ -100,7 +123,7 @@ export const TableVisControls = memo(
return (
<EuiPopover
id="dataTableExportData"
button={button}
button={downloadButton}
isOpen={isPopoverOpen}
closePopover={closePopover}
panelPaddingSize="none"

View file

@ -9,9 +9,15 @@ import { isEqual } from 'lodash';
import { i18n } from '@kbn/i18n';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { TopNavMenuData } from '../../../../../src/plugins/navigation/public';
import { LensAppServices, LensTopNavActions, LensTopNavMenuProps } from './types';
import {
LensAppServices,
LensTopNavActions,
LensTopNavMenuProps,
LensTopNavTooltips,
} from './types';
import { downloadMultipleAs } from '../../../../../src/plugins/share/public';
import { trackUiEvent } from '../lens_ui_telemetry';
import { tableHasFormulas } from '../../../../../src/plugins/data/common';
import { exporters, IndexPattern } from '../../../../../src/plugins/data/public';
import { useKibana } from '../../../../../src/plugins/kibana_react/public';
import {
@ -30,6 +36,7 @@ function getLensTopNavConfig(options: {
isByValueMode: boolean;
allowByValue: boolean;
actions: LensTopNavActions;
tooltips: LensTopNavTooltips;
savingToLibraryPermitted: boolean;
savingToDashboardPermitted: boolean;
}): TopNavMenuData[] {
@ -41,6 +48,7 @@ function getLensTopNavConfig(options: {
showSaveAndReturn,
savingToLibraryPermitted,
savingToDashboardPermitted,
tooltips,
} = options;
const topNavMenu: TopNavMenuData[] = [];
@ -73,6 +81,7 @@ function getLensTopNavConfig(options: {
defaultMessage: 'Download the data as CSV file',
}),
disableButton: !enableExportToCSV,
tooltip: tooltips.showExportWarning,
});
if (showCancel) {
@ -213,6 +222,23 @@ export const LensTopNavMenu = ({
showCancel: Boolean(isLinkedToOriginatingApp),
savingToLibraryPermitted,
savingToDashboardPermitted,
tooltips: {
showExportWarning: () => {
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 applications can interpret as formulas',
});
}
}
return undefined;
},
},
actions: {
exportToCSV: () => {
if (!activeData) {
@ -230,6 +256,7 @@ export const LensTopNavMenu = ({
csvSeparator: uiSettings.get('csv:separator', ','),
quoteValues: uiSettings.get('csv:quoteValues', true),
formatFactory: data.fieldFormats.deserialize,
escapeFormulaValues: false,
}),
type: exporters.CSV_MIME_TYPE,
};

View file

@ -114,6 +114,10 @@ export interface LensAppServices {
dashboardFeatureFlag: DashboardFeatureFlagConfig;
}
export interface LensTopNavTooltips {
showExportWarning: () => string | undefined;
}
export interface LensTopNavActions {
saveAndReturn: () => void;
showSaveModal: () => void;

View file

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

View file

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

View file

@ -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));

View file

@ -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';

View file

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