mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Discover] Fix CSV for ES|QL embeddable (#216325)
- Closes https://github.com/elastic/kibana/issues/215893 ## Summary This PR extract the logic from Share > Export > Generate CSV into new utils and uses it to fix CSV export for Dashboard panels. <img width="903" alt="Screenshot 2025-04-01 at 12 05 58" src="https://github.com/user-attachments/assets/20e611d7-b1da-4b50-a8fc-e18ac3db3a55" /> ## Testing Please test for both data view mode and ES|QL mode. The steps are the following: 1. Save a discover session 2. Add it to a new dashboard 3. Save the Dashboard and switch to View mode 4. Via panel actions, press "Generate CSV report" 5. Compare the CSV results with what is shown in the grid 6. Add a custom time range to the panel via panel actions "Settings" 7. Generate a CSV report and compare results again Also check that reporting still works when generated from Discover page. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
cf1cd55a49
commit
dc78614d29
8 changed files with 565 additions and 38 deletions
|
@ -14,6 +14,7 @@ import type {
|
|||
BasePayload,
|
||||
BasePayloadV2,
|
||||
CsvPagingStrategy,
|
||||
LocatorParams,
|
||||
} from '@kbn/reporting-common/types';
|
||||
|
||||
export * from './constants';
|
||||
|
@ -23,7 +24,12 @@ interface BaseParamsCSV {
|
|||
columns?: string[];
|
||||
}
|
||||
|
||||
interface BaseParamsCsvV2 {
|
||||
locatorParams: LocatorParams[];
|
||||
}
|
||||
|
||||
export type JobParamsCSV = BaseParamsCSV & BaseParams;
|
||||
export type JobParamsCsvV2 = BaseParamsCsvV2 & BaseParams;
|
||||
export type TaskPayloadCSV = BaseParamsCSV & BasePayload;
|
||||
|
||||
/**
|
||||
|
@ -33,6 +39,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';
|
||||
|
|
|
@ -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,8 +84,7 @@ interface Params {
|
|||
}
|
||||
|
||||
interface ExecutionParams {
|
||||
searchSource: SerializedSearchSourceFields;
|
||||
columns: string[] | undefined;
|
||||
searchModeParams: CsvSearchModeParams;
|
||||
title: string;
|
||||
analytics: AnalyticsServiceStart;
|
||||
i18nStart: I18nStart;
|
||||
|
@ -127,7 +135,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) => {
|
||||
|
@ -152,16 +169,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({
|
||||
|
@ -182,6 +198,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;
|
||||
|
||||
|
@ -196,14 +227,34 @@ 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 };
|
||||
|
||||
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 },
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -27,5 +27,7 @@
|
|||
"@kbn/react-kibana-mount",
|
||||
"@kbn/reporting-public",
|
||||
"@kbn/presentation-publishing",
|
||||
"@kbn/reporting-common",
|
||||
"@kbn/es-query",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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 '../..';
|
||||
|
||||
|
@ -31,35 +30,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,
|
||||
|
@ -78,7 +68,12 @@ export const reportingCsvShareProvider = ({
|
|||
const capabilityHasCsvReporting = application.capabilities.discover_v2?.generateCsv === true;
|
||||
|
||||
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$))
|
||||
|
@ -135,10 +130,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();
|
||||
|
||||
|
|
|
@ -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,
|
||||
}),
|
||||
};
|
||||
};
|
215
x-pack/test/functional/apps/discover/__snapshots__/reporting_embeddable.snap
generated
Normal file
215
x-pack/test/functional/apps/discover/__snapshots__/reporting_embeddable.snap
generated
Normal 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
|
||||
"
|
||||
`;
|
|
@ -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'));
|
||||
|
|
178
x-pack/test/functional/apps/discover/reporting_embeddable.ts
Normal file
178
x-pack/test/functional/apps/discover/reporting_embeddable.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue