Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/kbn-optimizer/limits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ pageLoadAssetSize:
serverlessSearch: 26393
serverlessWorkplaceAI: 6736
sessionView: 47912
share: 58677
share: 64618
slo: 36645
snapshotRestore: 22068
spaces: 30665
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: 'tableDensityNormal',
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