Skip to content
Merged
2 changes: 1 addition & 1 deletion packages/kbn-optimizer/limits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ pageLoadAssetSize:
serverlessSearch: 26287
serverlessWorkplaceAI: 5078
sessionView: 47912
share: 58677
share: 64618
slo: 40437
snapshotRestore: 22068
spaces: 28871
Expand Down
2 changes: 2 additions & 0 deletions src/platform/packages/private/kbn-reporting/public/moon.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ dependsOn:
- '@kbn/ui-actions-plugin'
- '@kbn/actions-plugin'
- '@kbn/licensing-types'
- '@kbn/es-query'
- '@kbn/utility-types'
tags:
- shared-browser
- package
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* 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 crypto from 'crypto';
import { NEVER } from 'rxjs';

jest.mock('../../shared/get_search_csv_job_params', () => ({
getSearchCsvJobParams: jest.fn(() => ({
reportType: 'csv_v2',
decoratedJobParams: {},
})),
}));

import { getSearchCsvJobParams } from '../../shared/get_search_csv_job_params';
import { getCsvReportParams, getShareMenuItems } from './csv_export_config';

describe('csv export config', () => {
describe('getCsvReportParams', () => {
it('should return report params that use absolute time, when useAbsoluteTime is true', () => {
const reportParams = getCsvReportParams({
sharingData: {
isTextBased: true,
locatorParams: [
{
id: crypto.randomUUID(),
version: 'test',
params: {
timeRange: {
from: 'now-90d/d',
to: 'now',
},
},
},
],
getSearchSource: () => ({}),
columns: [],
absoluteTimeRange: {
from: '2021-01-01T00:00:00.000Z',
to: '2021-01-01T00:00:00.000Z',
},
title: 'test',
},
useAbsoluteTime: true,
});

expect(reportParams).toEqual(
expect.objectContaining({
locatorParams: expect.arrayContaining([
expect.objectContaining({
params: expect.objectContaining({
timeRange: {
from: '2021-01-01T00:00:00.000Z',
to: '2021-01-01T00:00:00.000Z',
},
}),
}),
]),
})
);
});
});

describe('getShareMenuItems', () => {
describe('generateReportingJobCSV', () => {
it('invokes getSearchModeParams with useAbsoluteTime set to true', () => {
const absoluteTimeRange = {
from: '2021-01-01T00:00:00.000Z',
to: '2021-01-02T00:00:00.000Z',
};
const sharingData = {
isTextBased: true,
locatorParams: [
{
id: crypto.randomUUID(),
version: 'test',
params: {
timeRange: { from: 'now-90d/d', to: 'now' },
},
},
],
getSearchSource: jest.fn(),
columns: [],
absoluteTimeRange,
title: 'test',
};

const apiClient = {
createReportingShareJob: jest.fn(() => new Promise(() => {})),
getManagementLink: jest.fn(),
getReportingPublicJobPath: jest.fn(),
getDecoratedJobParams: jest.fn((params) => params),
};

const shareMenu = getShareMenuItems({
apiClient,
startServices$: NEVER,
csvConfig: { maxRows: 0 },
isServerless: false,
} as unknown as Parameters<typeof getShareMenuItems>[0])({
objectType: 'search',
sharingData,
shareableUrlLocatorParams: undefined,
} as unknown as Parameters<ReturnType<typeof getShareMenuItems>>[0]);

(getSearchCsvJobParams as jest.Mock).mockClear();

// attempt to generate the asset export
void shareMenu.generateAssetExport({
intl: { formatMessage: jest.fn() },
} as unknown as Parameters<typeof shareMenu.generateAssetExport>[0]);

expect(getSearchCsvJobParams).toHaveBeenCalledWith(
expect.objectContaining({
searchModeParams: {
isEsqlMode: true,
locatorParams: [
expect.objectContaining({
params: expect.objectContaining({
timeRange: absoluteTimeRange,
}),
}),
],
},
})
);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,41 +11,64 @@ import React from 'react';
import { firstValueFrom } from 'rxjs';
import { i18n } from '@kbn/i18n';
import { toMountPoint } from '@kbn/react-kibana-mount';
import type { SerializedSearchSourceFields } from '@kbn/data-plugin/common';
import type { InjectedIntl } from '@kbn/i18n-react';
import { FormattedMessage } from '@kbn/i18n-react';
import type { ShareContext, ExportShare } from '@kbn/share-plugin/public';
import type { LocatorParams } from '@kbn/reporting-common/types';
import { EuiCallOut, EuiText } from '@elastic/eui';
import type { ReportParamsGetter, ReportParamsGetterOptions } from '../../../types';
import type { TimeRange } from '@kbn/es-query';
import type { ExportGenerationOpts } from '@kbn/share-plugin/public/types';
import type {
ReportingCSVSharingData,
ReportParamsGetter,
ReportParamsGetterOptions,
} from '../../../types';
import type { CsvSearchModeParams } from '../../shared/get_search_csv_job_params';
import { getSearchCsvJobParams } from '../../shared/get_search_csv_job_params';
import type { ExportModalShareOpts } from '../../share_context_menu';

const toAbsoluteTimeRange = (
locatorParams: LocatorParams[],
absoluteTimeRange: TimeRange | undefined
): LocatorParams[] => {
return locatorParams.map((lp) => {
if (!absoluteTimeRange) {
return lp;
}

return {
...lp,
params: {
...lp.params,
timeRange: absoluteTimeRange,
},
};
});
};

export const getCsvReportParams: ReportParamsGetter<
ReportParamsGetterOptions & { forShareUrl?: boolean },
ReportParamsGetterOptions<ReportingCSVSharingData> & {
forShareUrl?: boolean;
useAbsoluteTime?: boolean;
},
CsvSearchModeParams
> = ({ sharingData, forShareUrl = false }) => {
const getSearchSource = sharingData.getSearchSource as ({
addGlobalTimeFilter,
absoluteTime,
}: {
addGlobalTimeFilter?: boolean;
absoluteTime?: boolean;
}) => SerializedSearchSourceFields;
> = ({ sharingData, forShareUrl = false, useAbsoluteTime = false }) => {
const getSearchSource = sharingData.getSearchSource;

if (sharingData.isTextBased) {
// csv v2 uses locator params
const locatorParams = sharingData.locatorParams;

return {
isEsqlMode: true,
locatorParams: sharingData.locatorParams as LocatorParams[],
locatorParams: useAbsoluteTime
? toAbsoluteTimeRange(locatorParams, sharingData.absoluteTimeRange)
: locatorParams,
};
}

// csv v1 uses search source and columns
return {
isEsqlMode: false,
columns: sharingData.columns as string[] | undefined,
columns: sharingData.columns,
searchSource: getSearchSource({
addGlobalTimeFilter: true,
absoluteTime: !forShareUrl,
Expand All @@ -61,15 +84,28 @@ export const getShareMenuItems =
({
objectType,
sharingData,
}: ShareContext): ReturnType<ExportShare['config']> extends Promise<infer R> ? R : never => {
const getSearchModeParams = (forShareUrl?: boolean): CsvSearchModeParams =>
getCsvReportParams({ sharingData, forShareUrl });
shareableUrlLocatorParams,
}: ShareContext<ReportingCSVSharingData>): Awaited<
ReturnType<ExportShare<ReportingCSVSharingData>['config']>
> => {
const getSearchModeParams = ({
forShareUrl,
useAbsoluteTime,
}: {
forShareUrl?: boolean;
useAbsoluteTime?: boolean;
} = {}): CsvSearchModeParams =>
getCsvReportParams({
sharingData,
forShareUrl,
useAbsoluteTime,
});

const generateReportingJobCSV = ({ intl }: { intl: InjectedIntl }) => {
const generateReportingJobCSV = ({ intl }: ExportGenerationOpts) => {
const { reportType, decoratedJobParams } = getSearchCsvJobParams({
apiClient,
searchModeParams: getSearchModeParams(false),
title: sharingData.title as string,
searchModeParams: getSearchModeParams({ useAbsoluteTime: true }),
title: sharingData.title,
});

return firstValueFrom(startServices$).then(([startServices]) => {
Expand Down Expand Up @@ -128,21 +164,30 @@ export const getShareMenuItems =
defaultMessage: 'Export',
});

const { reportType, decoratedJobParams } = getSearchCsvJobParams({
const { reportType } = getSearchCsvJobParams({
apiClient,
searchModeParams: getSearchModeParams(true),
title: sharingData.title as string,
searchModeParams: getSearchModeParams({ forShareUrl: true }),
title: sharingData.title,
});

const relativePath = apiClient.getReportingPublicJobPath(reportType, decoratedJobParams);

const absoluteUrl = new URL(relativePath, window.location.href).toString();
const getAbsoluteUrl = () => {
const { reportType: _reportType, decoratedJobParams } = getSearchCsvJobParams({
apiClient,
searchModeParams: getSearchModeParams({
forShareUrl: true,
}),
title: sharingData.title,
});
const relativePath = apiClient.getReportingPublicJobPath(_reportType, decoratedJobParams);
return new URL(relativePath, window.location.href).toString();
};

return {
name: panelTitle,
exportType: reportType,
label: 'CSV',
icon: 'table',
supportsAbsoluteTime: true,
generateAssetExport: generateReportingJobCSV,
helpText: (
<FormattedMessage
Expand All @@ -167,7 +212,7 @@ export const getShareMenuItems =
'Allows to generate selected file format programmatically outside Kibana or in Watcher.',
}),
contentType: 'text',
generateAssetURIValue: () => absoluteUrl,
generateAssetURIValue: getAbsoluteUrl,
},
renderTotalHitsSizeWarning: (totalHits: number = 0): React.ReactNode => {
const maxRows = csvConfig?.maxRows || 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@
import type { ExportShare, RegisterShareIntegrationArgs } from '@kbn/share-plugin/public';
import type { ExportModalShareOpts } from '../../share_context_menu';
import { checkLicense } from '../../..';
import type { ReportingCSVSharingData } from '../../../types';

export const reportingCsvExportShareIntegration = ({
apiClient,
startServices$,
csvConfig,
isServerless,
}: ExportModalShareOpts): RegisterShareIntegrationArgs<ExportShare> => {
}: ExportModalShareOpts): RegisterShareIntegrationArgs<ExportShare<ReportingCSVSharingData>> => {
return {
id: 'csvReports',
groupId: 'export',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type * as Rx from 'rxjs';

import type { ApplicationStart, CoreStart } from '@kbn/core/public';
import type { ILicense } from '@kbn/licensing-types';
import type { SharingData } from '@kbn/share-plugin/public';

import type { ReportingAPIClient } from '../../reporting_api_client';
import type { ClientConfigType } from '../../types';
Expand Down Expand Up @@ -41,13 +42,8 @@ export interface ExportPanelShareOpts {
startServices$: Rx.Observable<StartServices>;
}

export interface ReportingSharingData {
title: string;
export interface ReportingSharingData extends SharingData {
reportingDisabled?: boolean;
locatorParams: {
id: string;
params: unknown;
};
}

export interface JobParamsProviderOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,7 @@
"@kbn/ui-actions-plugin",
"@kbn/actions-plugin",
"@kbn/licensing-types",
"@kbn/es-query",
"@kbn/utility-types"
]
}
Loading
Loading