[8.x] [Discover] Fix CSV for ES|QL embeddable (#216325) (#216851)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Discover] Fix CSV for ES|QL embeddable
(#216325)](https://github.com/elastic/kibana/pull/216325)

<!--- Backport version: 9.6.6 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sorenlouv/backport)

<!--BACKPORT [{"author":{"name":"Julia
Rechkunova","email":"julia.rechkunova@elastic.co"},"sourceCommit":{"committedDate":"2025-04-02T13:14:55Z","message":"[Discover]
Fix CSV for ES|QL embeddable (#216325)\n\n- Closes
https://github.com/elastic/kibana/issues/215893\n\n## Summary\n\nThis PR
extract the logic from Share > Export > Generate CSV into new\nutils and
uses it to fix CSV export for Dashboard panels.\n\n<img width=\"903\"
alt=\"Screenshot 2025-04-01 at 12 05
58\"\nsrc=\"https://github.com/user-attachments/assets/20e611d7-b1da-4b50-a8fc-e18ac3db3a55\"\n/>\n\n\n##
Testing\n\nPlease test for both data view mode and ES|QL mode. The steps
are the\nfollowing:\n1. Save a discover session\n2. Add it to a new
dashboard\n3. Save the Dashboard and switch to View mode\n4. Via panel
actions, press \"Generate CSV report\"\n5. Compare the CSV results with
what is shown in the grid\n6. Add a custom time range to the panel via
panel actions \"Settings\"\n7. Generate a CSV report and compare results
again\n\nAlso check that reporting still works when generated from
Discover page.\n\n### Checklist\n\n- [x] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common scenarios\n- [x] [Flaky
Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\nused on any tests changed\n\n---------\n\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"dc78614d29da8f64f77840d3fc02846fcacfe2e5","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Feature:Discover","release_note:fix","v9.0.0","Team:DataDiscovery","backport:version","v8.18.0","v9.1.0","v8.19.0"],"title":"[Discover]
Fix CSV for ES|QL
embeddable","number":216325,"url":"https://github.com/elastic/kibana/pull/216325","mergeCommit":{"message":"[Discover]
Fix CSV for ES|QL embeddable (#216325)\n\n- Closes
https://github.com/elastic/kibana/issues/215893\n\n## Summary\n\nThis PR
extract the logic from Share > Export > Generate CSV into new\nutils and
uses it to fix CSV export for Dashboard panels.\n\n<img width=\"903\"
alt=\"Screenshot 2025-04-01 at 12 05
58\"\nsrc=\"https://github.com/user-attachments/assets/20e611d7-b1da-4b50-a8fc-e18ac3db3a55\"\n/>\n\n\n##
Testing\n\nPlease test for both data view mode and ES|QL mode. The steps
are the\nfollowing:\n1. Save a discover session\n2. Add it to a new
dashboard\n3. Save the Dashboard and switch to View mode\n4. Via panel
actions, press \"Generate CSV report\"\n5. Compare the CSV results with
what is shown in the grid\n6. Add a custom time range to the panel via
panel actions \"Settings\"\n7. Generate a CSV report and compare results
again\n\nAlso check that reporting still works when generated from
Discover page.\n\n### Checklist\n\n- [x] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common scenarios\n- [x] [Flaky
Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\nused on any tests changed\n\n---------\n\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"dc78614d29da8f64f77840d3fc02846fcacfe2e5"}},"sourceBranch":"main","suggestedTargetBranches":["9.0","8.18","8.x"],"targetPullRequestStates":[{"branch":"9.0","label":"v9.0.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.18","label":"v8.18.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/216325","number":216325,"mergeCommit":{"message":"[Discover]
Fix CSV for ES|QL embeddable (#216325)\n\n- Closes
https://github.com/elastic/kibana/issues/215893\n\n## Summary\n\nThis PR
extract the logic from Share > Export > Generate CSV into new\nutils and
uses it to fix CSV export for Dashboard panels.\n\n<img width=\"903\"
alt=\"Screenshot 2025-04-01 at 12 05
58\"\nsrc=\"https://github.com/user-attachments/assets/20e611d7-b1da-4b50-a8fc-e18ac3db3a55\"\n/>\n\n\n##
Testing\n\nPlease test for both data view mode and ES|QL mode. The steps
are the\nfollowing:\n1. Save a discover session\n2. Add it to a new
dashboard\n3. Save the Dashboard and switch to View mode\n4. Via panel
actions, press \"Generate CSV report\"\n5. Compare the CSV results with
what is shown in the grid\n6. Add a custom time range to the panel via
panel actions \"Settings\"\n7. Generate a CSV report and compare results
again\n\nAlso check that reporting still works when generated from
Discover page.\n\n### Checklist\n\n- [x] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common scenarios\n- [x] [Flaky
Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\nused on any tests changed\n\n---------\n\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"dc78614d29da8f64f77840d3fc02846fcacfe2e5"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->
This commit is contained in:
Julia Rechkunova 2025-04-15 11:58:38 +02:00 committed by GitHub
parent 0cc28d0e71
commit 3f4ae8c651
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 575 additions and 38 deletions

View file

@ -14,6 +14,7 @@ import type {
BasePayload,
BasePayloadV2,
CsvPagingStrategy,
LocatorParams,
} from '@kbn/reporting-common/types';
export * from './constants';
@ -34,7 +35,12 @@ interface BaseParamsCSV {
columns?: string[];
}
interface BaseParamsCsvV2 {
locatorParams: LocatorParams[];
}
export type JobParamsCSV = BaseParamsCSV & BaseParams;
export type JobParamsCsvV2 = BaseParamsCsvV2 & BaseParams;
export type TaskPayloadCSV = BaseParamsCSV & BasePayload;
/**
@ -44,6 +50,7 @@ export type TaskPayloadCSV = BaseParamsCSV & BasePayload;
* @public
*/
export type JobAppParamsCSV = Omit<JobParamsCSV, 'browserTimezone' | 'version'>;
export type JobAppParamsCsvV2 = Omit<JobParamsCsvV2, 'browserTimezone' | 'version'>;
interface CsvFromSavedObjectBase {
objectType: 'search';

View file

@ -16,7 +16,7 @@ import {
I18nStart,
NotificationsSetup,
} from '@kbn/core/public';
import { DataPublicPluginStart, SerializedSearchSourceFields } from '@kbn/data-plugin/public';
import { DataPublicPluginStart, type SerializedSearchSourceFields } from '@kbn/data-plugin/public';
import {
loadSharingDataHelpers,
SEARCH_EMBEDDABLE_TYPE,
@ -25,6 +25,7 @@ import {
HasTimeRange,
} from '@kbn/discover-plugin/public';
import { LicensingPluginStart } from '@kbn/licensing-plugin/public';
import { DISCOVER_APP_LOCATOR, type DiscoverAppLocatorParams } from '@kbn/discover-plugin/common';
import {
apiCanAccessViewMode,
apiHasType,
@ -34,16 +35,24 @@ import {
EmbeddableApiContext,
getInheritedViewMode,
HasType,
type PublishesSavedObjectId,
PublishesTitle,
type PublishesUnifiedSearch,
} from '@kbn/presentation-publishing';
import { toMountPoint } from '@kbn/react-kibana-mount';
import { CSV_REPORTING_ACTION, JobAppParamsCSV } from '@kbn/reporting-export-types-csv-common';
import { CSV_REPORTING_ACTION } from '@kbn/reporting-export-types-csv-common';
import { SavedSearch } from '@kbn/saved-search-plugin/public';
import type { UiActionsActionDefinition as ActionDefinition } from '@kbn/ui-actions-plugin/public';
import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
import type { ClientConfigType } from '@kbn/reporting-public/types';
import { checkLicense } from '@kbn/reporting-public/license_check';
import {
getSearchCsvJobParams,
CsvSearchModeParams,
} from '@kbn/reporting-public/share/shared/get_search_csv_job_params';
import type { ReportingAPIClient } from '@kbn/reporting-public/reporting_api_client';
import { LocatorParams } from '@kbn/reporting-common/types';
import { isOfAggregateQueryType } from '@kbn/es-query';
import { getI18nStrings } from './strings';
export interface PanelActionDependencies {
@ -75,7 +84,7 @@ interface Params {
usesUiCapabilities: boolean;
}
interface ExecutionParams {
interface ExecutionParamsOld {
searchSource: SerializedSearchSourceFields;
columns: string[] | undefined;
title: string;
@ -83,6 +92,13 @@ interface ExecutionParams {
i18nStart: I18nStart;
}
interface ExecutionParams {
searchModeParams: CsvSearchModeParams;
title: string;
analytics: AnalyticsServiceStart;
i18nStart: I18nStart;
}
type GetCsvActionApi = HasType &
PublishesSavedSearch &
CanAccessViewMode &
@ -134,7 +150,16 @@ export class ReportingCsvPanelAction implements ActionDefinition<EmbeddableApiCo
public async getSharingData(savedSearch: SavedSearch) {
const [{ uiSettings }, { data }] = await firstValueFrom(this.startServices$);
const { getSharingData } = await loadSharingDataHelpers();
return await getSharingData(savedSearch.searchSource, savedSearch, { uiSettings, data });
return await getSharingData(
savedSearch.searchSource,
savedSearch,
{ uiSettings, data },
this.isEsqlMode(savedSearch)
);
}
private isEsqlMode(savedSearch: SavedSearch) {
return isOfAggregateQueryType(savedSearch.searchSource.getField('query'));
}
public isCompatible = async (context: EmbeddableApiContext) => {
@ -164,7 +189,8 @@ export class ReportingCsvPanelAction implements ActionDefinition<EmbeddableApiCo
* Requires `xpack.reporting.csv.enablePanelActionDownload: true` in kibana.yml
* @deprecated
*/
private executeDownload = async (params: ExecutionParams) => {
private executeDownload = async (params: ExecutionParamsOld) => {
// Deprecated and does not support ES|QL mode
const [startServices] = await firstValueFrom(this.startServices$);
const { searchSource, columns, title } = params;
const immediateJobParams = this.apiClient.getDecoratedJobParams({
@ -223,16 +249,15 @@ export class ReportingCsvPanelAction implements ActionDefinition<EmbeddableApiCo
private executeGenerate = async (params: ExecutionParams) => {
const [startServices] = await firstValueFrom(this.startServices$);
const { searchSource, columns, title } = params;
const csvJobParams = this.apiClient.getDecoratedJobParams<JobAppParamsCSV>({
searchSource,
columns,
const { searchModeParams, title } = params;
const { reportType, decoratedJobParams } = getSearchCsvJobParams({
apiClient: this.apiClient,
searchModeParams,
title,
objectType: 'search',
});
await this.apiClient
.createReportingJob('csv_searchsource', csvJobParams)
.createReportingJob(reportType, decoratedJobParams)
.then((job) => {
if (job) {
this.notifications.toasts.addSuccess({
@ -253,6 +278,21 @@ export class ReportingCsvPanelAction implements ActionDefinition<EmbeddableApiCo
});
};
private getDiscoverLocatorParamsForEsqlCSV = (
api: PublishesSavedSearch & Partial<PublishesSavedObjectId & PublishesUnifiedSearch>,
searchSourceFields: SerializedSearchSourceFields,
columns: string[]
): DiscoverAppLocatorParams => {
const savedObjectId = api.savedObjectId$?.getValue();
return {
...(savedObjectId ? { savedSearchId: savedObjectId } : {}),
query: searchSourceFields.query,
filters: searchSourceFields.parent?.filter, // time range filter
columns,
};
};
public execute = async (context: EmbeddableApiContext) => {
const { embeddable } = context;
@ -267,17 +307,38 @@ export class ReportingCsvPanelAction implements ActionDefinition<EmbeddableApiCo
}
const [{ i18n: i18nStart, analytics }] = await firstValueFrom(this.startServices$);
const title = embeddable.title$.getValue() ?? '';
const executionParamsCommon = { title, i18nStart, analytics };
const { columns, getSearchSource } = await this.getSharingData(savedSearch);
const searchSource = getSearchSource({
addGlobalTimeFilter: !embeddable.hasTimeRange(),
absoluteTime: true,
});
const title = embeddable.title$.getValue() ?? '';
const executionParams = { searchSource, columns, title, savedSearch, i18nStart, analytics };
if (this.enablePanelActionDownload) {
const executionParams = { searchSource, columns, title, savedSearch, i18nStart, analytics };
return this.executeDownload(executionParams);
}
return this.executeGenerate(executionParams);
if (this.isEsqlMode(savedSearch)) {
return this.executeGenerate({
...executionParamsCommon,
searchModeParams: {
isEsqlMode: true,
locatorParams: [
{
id: DISCOVER_APP_LOCATOR,
params: this.getDiscoverLocatorParamsForEsqlCSV(embeddable, searchSource, columns),
} as LocatorParams,
],
},
});
}
return this.executeGenerate({
...executionParamsCommon,
searchModeParams: { isEsqlMode: false, searchSource, columns },
});
};
}

View file

@ -27,5 +27,7 @@
"@kbn/react-kibana-mount",
"@kbn/reporting-public",
"@kbn/presentation-publishing",
"@kbn/reporting-common",
"@kbn/es-query",
]
}

View file

@ -11,12 +11,11 @@ import { i18n } from '@kbn/i18n';
import { toMountPoint } from '@kbn/react-kibana-mount';
import React from 'react';
import { firstValueFrom } from 'rxjs';
import { CSV_JOB_TYPE, CSV_JOB_TYPE_V2 } from '@kbn/reporting-export-types-csv-common';
import type { SearchSourceFields } from '@kbn/data-plugin/common';
import type { SerializedSearchSourceFields } from '@kbn/data-plugin/common';
import { FormattedMessage, InjectedIntl } from '@kbn/i18n-react';
import { ShareContext, ShareMenuItemV2 } from '@kbn/share-plugin/public';
import { LocatorParams } from '@kbn/reporting-common/types';
import { getSearchCsvJobParams, CsvSearchModeParams } from '../shared/get_search_csv_job_params';
import type { ExportModalShareOpts } from '.';
import { checkLicense } from '../..';
@ -32,35 +31,26 @@ export const reportingCsvShareProvider = ({
return [];
}
// only csv v2 supports esql (isTextBased) reports
// TODO: whole csv reporting should move to v2 https://github.com/elastic/kibana/issues/151190
const reportType = sharingData.isTextBased ? CSV_JOB_TYPE_V2 : CSV_JOB_TYPE;
const getSearchSource = sharingData.getSearchSource as ({
addGlobalTimeFilter,
absoluteTime,
}: {
addGlobalTimeFilter?: boolean;
absoluteTime?: boolean;
}) => SearchSourceFields;
}) => SerializedSearchSourceFields;
const jobParams = {
title: sharingData.title as string,
objectType,
};
const getJobParams = (forShareUrl?: boolean) => {
if (reportType === CSV_JOB_TYPE_V2) {
const getSearchModeParams = (forShareUrl?: boolean): CsvSearchModeParams => {
if (sharingData.isTextBased) {
// csv v2 uses locator params
return {
...jobParams,
locatorParams: sharingData.locatorParams as [Record<string, unknown>],
isEsqlMode: true,
locatorParams: sharingData.locatorParams as LocatorParams[],
};
}
// csv v1 uses search source and columns
return {
...jobParams,
isEsqlMode: false,
columns: sharingData.columns as string[] | undefined,
searchSource: getSearchSource({
addGlobalTimeFilter: true,
@ -84,7 +74,12 @@ export const reportingCsvShareProvider = ({
}
const generateReportingJobCSV = ({ intl }: { intl: InjectedIntl }) => {
const decoratedJobParams = apiClient.getDecoratedJobParams(getJobParams());
const { reportType, decoratedJobParams } = getSearchCsvJobParams({
apiClient,
searchModeParams: getSearchModeParams(false),
title: sharingData.title as string,
});
return apiClient
.createReportingShareJob(reportType, decoratedJobParams)
.then(() => firstValueFrom(startServices$))
@ -141,10 +136,13 @@ export const reportingCsvShareProvider = ({
const reportingUrl = new URL(window.location.origin);
const relativePath = apiClient.getReportingPublicJobPath(
reportType,
apiClient.getDecoratedJobParams(getJobParams(true))
);
const { reportType, decoratedJobParams } = getSearchCsvJobParams({
apiClient,
searchModeParams: getSearchModeParams(true),
title: sharingData.title as string,
});
const relativePath = apiClient.getReportingPublicJobPath(reportType, decoratedJobParams);
const absoluteUrl = new URL(relativePath, window.location.href).toString();

View file

@ -0,0 +1,75 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import {
CSV_JOB_TYPE_V2,
CSV_JOB_TYPE,
JobAppParamsCSV,
JobAppParamsCsvV2,
} from '@kbn/reporting-export-types-csv-common';
import type { SerializedSearchSourceFields } from '@kbn/data-plugin/common';
import { LocatorParams, BaseParams } from '@kbn/reporting-common/types';
import type { ReportingAPIClient } from '../../reporting_api_client';
export type CsvSearchModeParams =
| {
isEsqlMode: false;
searchSource: SerializedSearchSourceFields;
columns: string[] | undefined;
}
| {
isEsqlMode: true;
locatorParams: LocatorParams[];
};
interface GetSearchCsvJobParams {
apiClient: ReportingAPIClient;
searchModeParams: CsvSearchModeParams;
title: string;
}
export const getSearchCsvJobParams = ({
apiClient,
searchModeParams,
title,
}: GetSearchCsvJobParams): {
reportType: typeof CSV_JOB_TYPE_V2 | typeof CSV_JOB_TYPE;
decoratedJobParams: BaseParams;
} => {
// only csv v2 supports esql reports
// TODO: whole csv reporting should move to v2 https://github.com/elastic/kibana/issues/151190
const reportType = searchModeParams.isEsqlMode ? CSV_JOB_TYPE_V2 : CSV_JOB_TYPE;
const commonJobParams = {
title,
objectType: 'search',
};
if (searchModeParams.isEsqlMode) {
// csv v2 uses locator params
return {
reportType,
decoratedJobParams: apiClient.getDecoratedJobParams<JobAppParamsCsvV2>({
...commonJobParams,
locatorParams: searchModeParams.locatorParams,
}),
};
}
// csv v1 uses search source and columns
return {
reportType,
decoratedJobParams: apiClient.getDecoratedJobParams<JobAppParamsCSV>({
...commonJobParams,
columns: searchModeParams.columns,
searchSource: searchModeParams.searchSource,
}),
};
};

View file

@ -0,0 +1,215 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`discover Discover Embeddable - Generate CSV report per panel Generate Embeddable CSV for savedDiscoverSessionWithDataView generates a report with custom time range 1`] = `
"\\"@timestamp\\",extension
\\"Sep 21, 2015 @ 00:59:00.367\\",jpg
\\"Sep 20, 2015 @ 19:49:05.899\\",jpg
\\"Sep 20, 2015 @ 19:29:43.926\\",jpg
\\"Sep 20, 2015 @ 17:38:42.285\\",jpg
\\"Sep 20, 2015 @ 17:08:30.609\\",jpg
\\"Sep 20, 2015 @ 16:53:17.163\\",jpg
\\"Sep 20, 2015 @ 16:45:00.480\\",jpg
\\"Sep 20, 2015 @ 16:08:49.527\\",css
\\"Sep 20, 2015 @ 15:55:31.002\\",css
\\"Sep 20, 2015 @ 15:32:20.876\\",css
\\"Sep 20, 2015 @ 15:20:47.204\\",png
\\"Sep 20, 2015 @ 15:14:43.471\\",css
\\"Sep 20, 2015 @ 15:14:26.330\\",png
\\"Sep 20, 2015 @ 15:03:45.263\\",jpg
\\"Sep 20, 2015 @ 14:55:58.779\\",jpg
\\"Sep 20, 2015 @ 14:47:52.748\\",css
\\"Sep 20, 2015 @ 14:44:34.702\\",jpg
\\"Sep 20, 2015 @ 14:39:50.742\\",jpg
\\"Sep 20, 2015 @ 14:39:11.084\\",jpg
\\"Sep 20, 2015 @ 14:25:09.526\\",jpg
\\"Sep 20, 2015 @ 14:21:12.441\\",php
\\"Sep 20, 2015 @ 14:18:32.300\\",gif
\\"Sep 20, 2015 @ 14:14:23.696\\",jpg
\\"Sep 20, 2015 @ 14:14:13.170\\",jpg
\\"Sep 20, 2015 @ 13:32:59.544\\",css
\\"Sep 20, 2015 @ 13:14:11.094\\",jpg
\\"Sep 20, 2015 @ 13:09:17.207\\",jpg
\\"Sep 20, 2015 @ 12:50:29.152\\",jpg
\\"Sep 20, 2015 @ 12:44:47.907\\",jpg
\\"Sep 20, 2015 @ 12:35:46.175\\",jpg
\\"Sep 20, 2015 @ 12:35:16.811\\",css
\\"Sep 20, 2015 @ 12:34:41.080\\",jpg
\\"Sep 20, 2015 @ 12:27:57.163\\",jpg
\\"Sep 20, 2015 @ 12:25:20.687\\",jpg
\\"Sep 20, 2015 @ 12:24:50.429\\",jpg
\\"Sep 20, 2015 @ 11:47:17.612\\",jpg
\\"Sep 20, 2015 @ 11:41:19.508\\",jpg
\\"Sep 20, 2015 @ 11:37:37.381\\",jpg
\\"Sep 20, 2015 @ 11:30:51.674\\",css
\\"Sep 20, 2015 @ 11:24:51.803\\",jpg
\\"Sep 20, 2015 @ 11:14:23.740\\",jpg
\\"Sep 20, 2015 @ 11:03:40.696\\",css
\\"Sep 20, 2015 @ 10:58:13.547\\",jpg
\\"Sep 20, 2015 @ 10:53:07.793\\",jpg
\\"Sep 20, 2015 @ 10:42:16.645\\",png
\\"Sep 20, 2015 @ 10:41:14.358\\",css
\\"Sep 20, 2015 @ 10:31:30.074\\",css
\\"Sep 20, 2015 @ 10:31:00.654\\",jpg
\\"Sep 20, 2015 @ 10:30:25.744\\",css
\\"Sep 20, 2015 @ 10:23:27.213\\",png
\\"Sep 20, 2015 @ 10:16:44.923\\",jpg
\\"Sep 20, 2015 @ 09:48:34.742\\",css
\\"Sep 20, 2015 @ 09:48:21.935\\",jpg
\\"Sep 20, 2015 @ 09:43:53.380\\",jpg
\\"Sep 20, 2015 @ 09:38:59.682\\",jpg
\\"Sep 20, 2015 @ 09:22:15.681\\",jpg
\\"Sep 20, 2015 @ 09:21:34.096\\",jpg
\\"Sep 20, 2015 @ 09:11:27.068\\",gif
\\"Sep 20, 2015 @ 09:08:30.796\\",css
\\"Sep 20, 2015 @ 08:49:57.121\\",jpg
\\"Sep 20, 2015 @ 08:40:41.779\\",png
\\"Sep 20, 2015 @ 08:34:53.186\\",css
\\"Sep 20, 2015 @ 08:15:38.922\\",png
\\"Sep 20, 2015 @ 07:50:33.356\\",jpg
\\"Sep 20, 2015 @ 07:23:02.614\\",css
\\"Sep 20, 2015 @ 07:18:15.800\\",css
\\"Sep 20, 2015 @ 07:07:01.048\\",jpg
\\"Sep 20, 2015 @ 06:41:00.585\\",css
\\"Sep 20, 2015 @ 06:39:27.633\\",jpg
\\"Sep 20, 2015 @ 06:26:36.476\\",png
\\"Sep 20, 2015 @ 06:26:10.475\\",css
\\"Sep 20, 2015 @ 06:24:30.150\\",jpg
\\"Sep 20, 2015 @ 06:03:53.342\\",jpg
\\"Sep 20, 2015 @ 05:48:01.031\\",gif
\\"Sep 20, 2015 @ 04:32:06.465\\",jpg
\\"Sep 20, 2015 @ 04:22:45.702\\",jpg
\\"Sep 20, 2015 @ 04:18:54.867\\",css
\\"Sep 20, 2015 @ 04:11:49.921\\",jpg
\\"Sep 20, 2015 @ 03:48:32.880\\",jpg
\\"Sep 20, 2015 @ 03:44:10.788\\",jpg
"
`;
exports[`discover Discover Embeddable - Generate CSV report per panel Generate Embeddable CSV for savedDiscoverSessionWithDataView generates a report with global time range 1`] = `
"\\"@timestamp\\",extension
\\"Sep 21, 2015 @ 11:26:44.669\\",jpg
\\"Sep 21, 2015 @ 11:17:34.550\\",jpg
\\"Sep 21, 2015 @ 10:49:38.164\\",jpg
\\"Sep 21, 2015 @ 10:48:46.371\\",jpg
\\"Sep 21, 2015 @ 10:33:15.502\\",jpg
\\"Sep 21, 2015 @ 10:15:36.162\\",jpg
\\"Sep 21, 2015 @ 10:10:20.302\\",jpg
\\"Sep 21, 2015 @ 10:07:18.887\\",php
\\"Sep 21, 2015 @ 09:59:45.555\\",jpg
\\"Sep 21, 2015 @ 09:36:07.770\\",jpg
\\"Sep 21, 2015 @ 09:20:36.111\\",css
\\"Sep 21, 2015 @ 09:14:28.585\\",jpg
\\"Sep 21, 2015 @ 08:52:59.787\\",jpg
\\"Sep 21, 2015 @ 08:42:28.854\\",jpg
\\"Sep 21, 2015 @ 08:07:44.865\\",jpg
\\"Sep 21, 2015 @ 08:05:13.743\\",css
\\"Sep 21, 2015 @ 07:19:42.176\\",jpg
\\"Sep 21, 2015 @ 06:49:26.535\\",jpg
\\"Sep 21, 2015 @ 06:31:49.112\\",jpg
\\"Sep 21, 2015 @ 05:34:13.304\\",gif
\\"Sep 21, 2015 @ 05:06:15.814\\",jpg
\\"Sep 21, 2015 @ 04:08:39.759\\",png
\\"Sep 21, 2015 @ 03:41:43.705\\",jpg
\\"Sep 21, 2015 @ 02:54:47.500\\",jpg
\\"Sep 21, 2015 @ 00:59:00.367\\",jpg
\\"Sep 20, 2015 @ 19:49:05.899\\",jpg
\\"Sep 20, 2015 @ 19:29:43.926\\",jpg
\\"Sep 20, 2015 @ 17:38:42.285\\",jpg
\\"Sep 20, 2015 @ 17:08:30.609\\",jpg
\\"Sep 20, 2015 @ 16:53:17.163\\",jpg
\\"Sep 20, 2015 @ 16:45:00.480\\",jpg
\\"Sep 20, 2015 @ 16:08:49.527\\",css
\\"Sep 20, 2015 @ 15:55:31.002\\",css
\\"Sep 20, 2015 @ 15:32:20.876\\",css
\\"Sep 20, 2015 @ 15:20:47.204\\",png
\\"Sep 20, 2015 @ 15:14:43.471\\",css
\\"Sep 20, 2015 @ 15:14:26.330\\",png
\\"Sep 20, 2015 @ 15:03:45.263\\",jpg
\\"Sep 20, 2015 @ 14:55:58.779\\",jpg
\\"Sep 20, 2015 @ 14:47:52.748\\",css
\\"Sep 20, 2015 @ 14:44:34.702\\",jpg
\\"Sep 20, 2015 @ 14:39:50.742\\",jpg
\\"Sep 20, 2015 @ 14:39:11.084\\",jpg
\\"Sep 20, 2015 @ 14:25:09.526\\",jpg
\\"Sep 20, 2015 @ 14:21:12.441\\",php
\\"Sep 20, 2015 @ 14:18:32.300\\",gif
\\"Sep 20, 2015 @ 14:14:23.696\\",jpg
\\"Sep 20, 2015 @ 14:14:13.170\\",jpg
\\"Sep 20, 2015 @ 13:32:59.544\\",css
\\"Sep 20, 2015 @ 13:14:11.094\\",jpg
\\"Sep 20, 2015 @ 13:09:17.207\\",jpg
\\"Sep 20, 2015 @ 12:50:29.152\\",jpg
\\"Sep 20, 2015 @ 12:44:47.907\\",jpg
\\"Sep 20, 2015 @ 12:35:46.175\\",jpg
\\"Sep 20, 2015 @ 12:35:16.811\\",css
\\"Sep 20, 2015 @ 12:34:41.080\\",jpg
\\"Sep 20, 2015 @ 12:27:57.163\\",jpg
\\"Sep 20, 2015 @ 12:25:20.687\\",jpg
\\"Sep 20, 2015 @ 12:24:50.429\\",jpg
\\"Sep 20, 2015 @ 11:47:17.612\\",jpg
\\"Sep 20, 2015 @ 11:41:19.508\\",jpg
\\"Sep 20, 2015 @ 11:37:37.381\\",jpg
\\"Sep 20, 2015 @ 11:30:51.674\\",css
\\"Sep 20, 2015 @ 11:24:51.803\\",jpg
\\"Sep 20, 2015 @ 11:14:23.740\\",jpg
\\"Sep 20, 2015 @ 11:03:40.696\\",css
\\"Sep 20, 2015 @ 10:58:13.547\\",jpg
\\"Sep 20, 2015 @ 10:53:07.793\\",jpg
\\"Sep 20, 2015 @ 10:42:16.645\\",png
\\"Sep 20, 2015 @ 10:41:14.358\\",css
\\"Sep 20, 2015 @ 10:31:30.074\\",css
\\"Sep 20, 2015 @ 10:31:00.654\\",jpg
\\"Sep 20, 2015 @ 10:30:25.744\\",css
\\"Sep 20, 2015 @ 10:23:27.213\\",png
\\"Sep 20, 2015 @ 10:16:44.923\\",jpg
\\"Sep 20, 2015 @ 09:48:34.742\\",css
\\"Sep 20, 2015 @ 09:48:21.935\\",jpg
\\"Sep 20, 2015 @ 09:43:53.380\\",jpg
\\"Sep 20, 2015 @ 09:38:59.682\\",jpg
\\"Sep 20, 2015 @ 09:22:15.681\\",jpg
\\"Sep 20, 2015 @ 09:21:34.096\\",jpg
\\"Sep 20, 2015 @ 09:11:27.068\\",gif
\\"Sep 20, 2015 @ 09:08:30.796\\",css
\\"Sep 20, 2015 @ 08:49:57.121\\",jpg
\\"Sep 20, 2015 @ 08:40:41.779\\",png
\\"Sep 20, 2015 @ 08:34:53.186\\",css
\\"Sep 20, 2015 @ 08:15:38.922\\",png
\\"Sep 20, 2015 @ 07:50:33.356\\",jpg
\\"Sep 20, 2015 @ 07:23:02.614\\",css
\\"Sep 20, 2015 @ 07:18:15.800\\",css
\\"Sep 20, 2015 @ 07:07:01.048\\",jpg
\\"Sep 20, 2015 @ 06:41:00.585\\",css
\\"Sep 20, 2015 @ 06:39:27.633\\",jpg
\\"Sep 20, 2015 @ 06:26:36.476\\",png
\\"Sep 20, 2015 @ 06:26:10.475\\",css
\\"Sep 20, 2015 @ 06:24:30.150\\",jpg
\\"Sep 20, 2015 @ 06:03:53.342\\",jpg
\\"Sep 20, 2015 @ 05:48:01.031\\",gif
\\"Sep 20, 2015 @ 04:32:06.465\\",jpg
\\"Sep 20, 2015 @ 04:22:45.702\\",jpg
\\"Sep 20, 2015 @ 04:18:54.867\\",css
\\"Sep 20, 2015 @ 04:11:49.921\\",jpg
\\"Sep 20, 2015 @ 03:48:32.880\\",jpg
\\"Sep 20, 2015 @ 03:44:10.788\\",jpg
"
`;
exports[`discover Discover Embeddable - Generate CSV report per panel Generate Embeddable CSV for savedDiscoverSessionWithESQL generates a report with custom time range 1`] = `
"averageB,extension
\\"10497.149253731342\\",png
\\"5738.34725848564\\",css
\\"5536.247799152266\\",jpg
\\"4868.18064516129\\",php
\\"466.22712933753945\\",gif
"
`;
exports[`discover Discover Embeddable - Generate CSV report per panel Generate Embeddable CSV for savedDiscoverSessionWithESQL generates a report with global time range 1`] = `
"averageB,extension
\\"10429.042836041359\\",png
\\"5765.214962121212\\",css
\\"5563.784938941655\\",jpg
\\"4884.612149532711\\",php
\\"471.8752783964365\\",gif
"
`;

View file

@ -13,6 +13,7 @@ export default function ({ loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./preserve_url'));
loadTestFile(require.resolve('./async_scripted_fields'));
loadTestFile(require.resolve('./reporting'));
loadTestFile(require.resolve('./reporting_embeddable'));
loadTestFile(require.resolve('./error_handling'));
loadTestFile(require.resolve('./saved_queries'));
loadTestFile(require.resolve('./saved_searches'));

View file

@ -0,0 +1,178 @@
/*
* 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 expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const kibanaServer = getService('kibanaServer');
const retry = getService('retry');
const { reporting, common, discover, timePicker, header, dashboard, unifiedFieldList } =
getPageObjects([
'reporting',
'common',
'discover',
'timePicker',
'header',
'dashboard',
'unifiedFieldList',
]);
const monacoEditor = getService('monacoEditor');
const queryBar = getService('queryBar');
const testSubjects = getService('testSubjects');
const toasts = getService('toasts');
const dataGrid = getService('dataGrid');
const dashboardAddPanel = getService('dashboardAddPanel');
const dashboardPanelActions = getService('dashboardPanelActions');
const dashboardCustomizePanel = getService('dashboardCustomizePanel');
const dashboardBadgeActions = getService('dashboardBadgeActions');
const GENERATE_CSV_DATA_TEST_SUBJ = 'embeddablePanelAction-generateCsvReport';
const SAVED_DISCOVER_SESSION_WITH_DATA_VIEW = 'savedDiscoverSessionWithDataView';
const SAVED_DISCOVER_SESSION_WITH_ESQL = 'savedDiscoverSessionWithESQL';
const getDashboardPanelReport = async (title: string, { timeout } = { timeout: 60 * 1000 }) => {
await toasts.dismissAll();
await dashboardPanelActions.expectExistsPanelAction(GENERATE_CSV_DATA_TEST_SUBJ, title);
await dashboardPanelActions.clickPanelActionByTitle(GENERATE_CSV_DATA_TEST_SUBJ, title);
await testSubjects.existOrFail('csvReportStarted'); /* validate toast panel */
const url = await reporting.getReportURL(timeout);
const res = await reporting.getResponse(url ?? '');
expect(res.status).to.equal(200);
expect(res.get('content-type')).to.equal('text/csv; charset=utf-8');
return res;
};
const addEmbeddableToDashboard = async (title: string) => {
await dashboardAddPanel.addSavedSearch(title);
await header.waitUntilLoadingHasFinished();
await dashboard.waitForRenderComplete();
const rows = await dataGrid.getDocTableRows();
expect(rows.length).to.be.above(0);
await dashboard.saveDashboard('test-csv', {
saveAsNew: true,
waitDialogIsClosed: false,
exitFromEditMode: true,
});
await header.waitUntilLoadingHasFinished();
await dashboard.waitForRenderComplete();
};
const setDashboardGlobalTimeRange = async () => {
const fromTime = 'Sep 19, 2015 @ 16:31:44.000';
const toTime = 'Sep 21, 2015 @ 11:31:44.000';
await timePicker.setAbsoluteRange(fromTime, toTime);
};
const setDashboardPanelCustomTimeRange = async () => {
await dashboardPanelActions.customizePanel();
await dashboardCustomizePanel.enableCustomTimeRange();
const toTime = 'Sep 21, 2015 @ 01:31:44.000';
const endTimeTestSubj =
'customizePanelTimeRangeDatePicker > superDatePickerendDatePopoverButton';
await retry.waitFor(`endDate is set to ${toTime}`, async () => {
await testSubjects.click(endTimeTestSubj);
await testSubjects.click('superDatePickerAbsoluteDateInput');
await timePicker.inputValue('superDatePickerAbsoluteDateInput', toTime);
await testSubjects.click(endTimeTestSubj);
const actualToTime = await testSubjects.getVisibleText(endTimeTestSubj);
return toTime === actualToTime;
});
await dashboardCustomizePanel.clickSaveButton();
await dashboard.waitForRenderComplete();
await dashboardBadgeActions.expectExistsTimeRangeBadgeAction();
};
describe('Discover Embeddable - Generate CSV report per panel', () => {
before(async () => {
await kibanaServer.savedObjects.cleanStandardList();
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional');
await kibanaServer.importExport.load(
'src/platform/test/functional/fixtures/kbn_archiver/discover'
);
await timePicker.setDefaultAbsoluteRangeViaUiSettings();
await common.navigateToApp('discover');
await header.waitUntilLoadingHasFinished();
await discover.waitUntilSearchingHasFinished();
await discover.selectIndexPattern('logstash-*');
// create and save a discover session with filters in data view mode
await unifiedFieldList.waitUntilSidebarHasLoaded();
await unifiedFieldList.clickFieldListItem('geo.src');
await unifiedFieldList.clickFieldListPlusFilter('geo.src', 'US');
await header.waitUntilLoadingHasFinished();
await discover.waitUntilSearchingHasFinished();
await unifiedFieldList.clickFieldListItemAdd('extension');
await header.waitUntilLoadingHasFinished();
await discover.waitUntilSearchingHasFinished();
await queryBar.setQuery('machine.os : ios');
await queryBar.submitQuery();
await header.waitUntilLoadingHasFinished();
await discover.waitUntilSearchingHasFinished();
expect(await dataGrid.getDocCount()).to.be(222);
await discover.saveSearch(SAVED_DISCOVER_SESSION_WITH_DATA_VIEW);
await header.waitUntilLoadingHasFinished();
// create and save a discover session with filters in ES|QL mode
await discover.clickNewSearchButton();
await discover.selectTextBaseLang();
await header.waitUntilLoadingHasFinished();
await discover.waitUntilSearchingHasFinished();
await monacoEditor.setCodeEditorValue(
'from logstash-* | sort @timestamp desc | stats averageB = avg(bytes) by extension | sort averageB desc'
);
await testSubjects.click('querySubmitButton');
await header.waitUntilLoadingHasFinished();
await discover.waitUntilSearchingHasFinished();
expect(await dataGrid.getDocCount()).to.be(5);
await discover.saveSearch(SAVED_DISCOVER_SESSION_WITH_ESQL);
await header.waitUntilLoadingHasFinished();
});
after(async () => {
await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional');
await kibanaServer.savedObjects.cleanStandardList();
});
[SAVED_DISCOVER_SESSION_WITH_DATA_VIEW, SAVED_DISCOVER_SESSION_WITH_ESQL].map(
(title: string) => {
describe(`Generate Embeddable CSV for ${title}`, () => {
beforeEach(async () => {
await kibanaServer.savedObjects.clean({ types: ['dashboard'] });
});
it('generates a report with global time range', async () => {
await dashboard.navigateToApp();
await dashboard.clickNewDashboard();
await setDashboardGlobalTimeRange();
await addEmbeddableToDashboard(title);
const { text: csvFile } = await getDashboardPanelReport(title);
expectSnapshot(csvFile).toMatch();
});
it('generates a report with custom time range', async () => {
await dashboard.navigateToApp();
await dashboard.clickNewDashboard();
await setDashboardGlobalTimeRange();
await addEmbeddableToDashboard(title);
await setDashboardPanelCustomTimeRange();
const { text: csvFile } = await getDashboardPanelReport(title);
expectSnapshot(csvFile).toMatch();
});
});
}
);
});
}