[Reporting] Use updated saved object resolver (#113641)
* Update deprecated PDF job factory to use updated saved object resolver * Reuse data plugin service to gather index pattern attributes in the deprecated CSV export type
This commit is contained in:
parent
cc3f601bd5
commit
6b64103333
11 changed files with 78 additions and 82 deletions
|
@ -307,6 +307,16 @@ export class ReportingCore {
|
|||
return await this.getUiSettingsServiceFactory(savedObjectsClient);
|
||||
}
|
||||
|
||||
public async getDataViewsService(request: KibanaRequest) {
|
||||
const { savedObjects } = await this.getPluginStartDeps();
|
||||
const savedObjectsClient = savedObjects.getScopedClient(request);
|
||||
const { indexPatterns } = await this.getDataService();
|
||||
const { asCurrentUser: esClient } = (await this.getEsClient()).asScoped(request);
|
||||
const dataViews = await indexPatterns.dataViewsServiceFactory(savedObjectsClient, esClient);
|
||||
|
||||
return dataViews;
|
||||
}
|
||||
|
||||
public async getDataService() {
|
||||
const startDeps = await this.getPluginStartDeps();
|
||||
return startDeps.data;
|
||||
|
|
|
@ -6,29 +6,18 @@
|
|||
*/
|
||||
|
||||
import { CreateJobFn, CreateJobFnFactory } from '../../types';
|
||||
import {
|
||||
IndexPatternSavedObjectDeprecatedCSV,
|
||||
JobParamsDeprecatedCSV,
|
||||
TaskPayloadDeprecatedCSV,
|
||||
} from './types';
|
||||
import { JobParamsDeprecatedCSV, TaskPayloadDeprecatedCSV } from './types';
|
||||
|
||||
export const createJobFnFactory: CreateJobFnFactory<
|
||||
CreateJobFn<JobParamsDeprecatedCSV, TaskPayloadDeprecatedCSV>
|
||||
> = function createJobFactoryFn(_reporting, logger) {
|
||||
> = function createJobFactoryFn(reporting, logger) {
|
||||
return async function createJob(jobParams, context) {
|
||||
logger.warn(
|
||||
`The "/generate/csv" endpoint is deprecated and will be removed in Kibana 8.0. Please recreate the POST URL used to automate this CSV export.`
|
||||
`The "/generate/csv" endpoint is deprecated. Please recreate the POST URL used to automate this CSV export.`
|
||||
);
|
||||
|
||||
const savedObjectsClient = context.core.savedObjects.client;
|
||||
const indexPatternSavedObject = (await savedObjectsClient.get(
|
||||
'index-pattern',
|
||||
jobParams.indexPatternId
|
||||
)) as unknown as IndexPatternSavedObjectDeprecatedCSV;
|
||||
|
||||
return {
|
||||
isDeprecated: true,
|
||||
indexPatternSavedObject,
|
||||
...jobParams,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -12,6 +12,7 @@ import { ElasticsearchClient, IUiSettingsClient } from 'kibana/server';
|
|||
import moment from 'moment';
|
||||
import Puid from 'puid';
|
||||
import sinon from 'sinon';
|
||||
import type { DataView, DataViewsService } from 'src/plugins/data/common';
|
||||
import { ReportingConfig, ReportingCore } from '../../';
|
||||
import {
|
||||
FieldFormatsRegistry,
|
||||
|
@ -56,6 +57,8 @@ describe('CSV Execute Job', function () {
|
|||
let encryptedHeaders: any;
|
||||
|
||||
let configGetStub: any;
|
||||
let mockDataView: jest.Mocked<DataView>;
|
||||
let mockDataViewsService: jest.Mocked<DataViewsService>;
|
||||
let mockEsClient: DeeplyMockedKeys<ElasticsearchClient>;
|
||||
let mockReportingConfig: ReportingConfig;
|
||||
let mockReportingCore: ReportingCore;
|
||||
|
@ -81,10 +84,15 @@ describe('CSV Execute Job', function () {
|
|||
configGetStub.withArgs('csv', 'maxSizeBytes').returns(1024 * 1000); // 1mB
|
||||
configGetStub.withArgs('csv', 'scroll').returns({});
|
||||
mockReportingConfig = { get: configGetStub, kbnConfig: { get: configGetStub } };
|
||||
mockDataView = { fieldFormatMap: {}, fields: [] } as unknown as typeof mockDataView;
|
||||
mockDataViewsService = {
|
||||
get: jest.fn().mockResolvedValue(mockDataView),
|
||||
} as unknown as typeof mockDataViewsService;
|
||||
|
||||
mockReportingCore = await createMockReportingCore(createMockConfigSchema());
|
||||
mockReportingCore.getUiSettingsServiceFactory = () =>
|
||||
Promise.resolve(mockUiSettingsClient as unknown as IUiSettingsClient);
|
||||
mockReportingCore.getDataViewsService = jest.fn().mockResolvedValue(mockDataViewsService);
|
||||
mockReportingCore.setConfig(mockReportingConfig);
|
||||
|
||||
mockEsClient = (await mockReportingCore.getEsClient()).asScoped({} as any)
|
||||
|
@ -931,16 +939,14 @@ describe('CSV Execute Job', function () {
|
|||
fields: ['one', 'two'],
|
||||
conflictedTypesFields: [],
|
||||
searchRequest: { index: null, body: null },
|
||||
indexPatternSavedObject: {
|
||||
id: 'logstash-*',
|
||||
type: 'index-pattern',
|
||||
attributes: {
|
||||
title: 'logstash-*',
|
||||
fields: '[{"name":"one","type":"string"}, {"name":"two","type":"string"}]',
|
||||
fieldFormatMap: '{"one":{"id":"string","params":{"transform": "upper"}}}',
|
||||
},
|
||||
},
|
||||
indexPatternId: 'something',
|
||||
});
|
||||
|
||||
mockDataView.fieldFormatMap = { one: { id: 'string', params: { transform: 'upper' } } };
|
||||
mockDataView.fields = [
|
||||
{ name: 'one', type: 'string' },
|
||||
{ name: 'two', type: 'string' },
|
||||
] as typeof mockDataView.fields;
|
||||
await runTask('job123', jobParams, cancellationToken, stream);
|
||||
expect(content).not.toBe(null);
|
||||
const lines = content!.split('\n');
|
||||
|
|
|
@ -25,12 +25,14 @@ export const runTaskFnFactory: RunTaskFnFactory<RunTaskFn<TaskPayloadDeprecatedC
|
|||
const fakeRequest = reporting.getFakeRequest({ headers }, job.spaceId, logger);
|
||||
const uiSettingsClient = await reporting.getUiSettingsClient(fakeRequest, logger);
|
||||
const { asCurrentUser: elasticsearchClient } = elasticsearch.asScoped(fakeRequest);
|
||||
const dataViews = await reporting.getDataViewsService(fakeRequest);
|
||||
|
||||
const { maxSizeReached, csvContainsFormulas, warnings } = await generateCsv(
|
||||
job,
|
||||
config,
|
||||
uiSettingsClient,
|
||||
elasticsearchClient,
|
||||
dataViews,
|
||||
cancellationToken,
|
||||
stream
|
||||
);
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import type { DataView } from 'src/plugins/data/common';
|
||||
import {
|
||||
FieldFormatsGetConfigFn,
|
||||
FieldFormatsRegistry,
|
||||
|
@ -13,20 +14,18 @@ import {
|
|||
NumberFormat,
|
||||
FORMATS_UI_SETTINGS,
|
||||
} from 'src/plugins/field_formats/common';
|
||||
import { IndexPatternSavedObjectDeprecatedCSV } from '../types';
|
||||
import { fieldFormatMapFactory } from './field_format_map';
|
||||
|
||||
type ConfigValue = { number: { id: string; params: {} } } | string;
|
||||
|
||||
describe('field format map', function () {
|
||||
const indexPatternSavedObject: IndexPatternSavedObjectDeprecatedCSV = {
|
||||
timeFieldName: '@timestamp',
|
||||
title: 'logstash-*',
|
||||
attributes: {
|
||||
fields: '[{"name":"field1","type":"number"}, {"name":"field2","type":"number"}]',
|
||||
fieldFormatMap: '{"field1":{"id":"bytes","params":{"pattern":"0,0.[0]b"}}}',
|
||||
},
|
||||
};
|
||||
const dataView = {
|
||||
fields: [
|
||||
{ name: 'field1', type: 'number' },
|
||||
{ name: 'field2', type: 'number' },
|
||||
],
|
||||
fieldFormatMap: { field1: { id: 'bytes', params: { pattern: '0,0.[0]b' } } },
|
||||
} as unknown as DataView;
|
||||
const configMock: Record<string, ConfigValue> = {};
|
||||
configMock[FORMATS_UI_SETTINGS.FORMAT_DEFAULT_TYPE_MAP] = {
|
||||
number: { id: 'number', params: {} },
|
||||
|
@ -39,13 +38,9 @@ describe('field format map', function () {
|
|||
const fieldFormatsRegistry = new FieldFormatsRegistry();
|
||||
fieldFormatsRegistry.init(getConfig, {}, [BytesFormat, NumberFormat]);
|
||||
|
||||
const formatMap = fieldFormatMapFactory(
|
||||
indexPatternSavedObject,
|
||||
fieldFormatsRegistry,
|
||||
mockTimezone
|
||||
);
|
||||
const formatMap = fieldFormatMapFactory(dataView, fieldFormatsRegistry, mockTimezone);
|
||||
|
||||
it('should build field format map with entry per index pattern field', function () {
|
||||
it('should build field format map with entry per data view field', function () {
|
||||
expect(formatMap.has('field1')).to.be(true);
|
||||
expect(formatMap.has('field2')).to.be(true);
|
||||
expect(formatMap.has('field_not_in_index')).to.be(false);
|
||||
|
|
|
@ -6,22 +6,21 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import type { DataView, KBN_FIELD_TYPES } from 'src/plugins/data/common';
|
||||
import {
|
||||
FieldFormat,
|
||||
IFieldFormatsRegistry,
|
||||
FieldFormatConfig,
|
||||
} from 'src/plugins/field_formats/common';
|
||||
import { IndexPatternSavedObjectDeprecatedCSV } from '../types';
|
||||
|
||||
/**
|
||||
* Create a map of FieldFormat instances for index pattern fields
|
||||
*
|
||||
* @param {Object} indexPatternSavedObject
|
||||
* @param {DataView} dataView
|
||||
* @param {FieldFormatsService} fieldFormats
|
||||
* @return {Map} key: field name, value: FieldFormat instance
|
||||
*/
|
||||
export function fieldFormatMapFactory(
|
||||
indexPatternSavedObject: IndexPatternSavedObjectDeprecatedCSV,
|
||||
dataView: DataView | undefined,
|
||||
fieldFormatsRegistry: IFieldFormatsRegistry,
|
||||
timezone: string | undefined
|
||||
) {
|
||||
|
@ -32,10 +31,9 @@ export function fieldFormatMapFactory(
|
|||
const serverDateParams = { timezone };
|
||||
|
||||
// Add FieldFormat instances for fields with custom formatters
|
||||
if (_.has(indexPatternSavedObject, 'attributes.fieldFormatMap')) {
|
||||
const fieldFormatMap = JSON.parse(indexPatternSavedObject.attributes.fieldFormatMap);
|
||||
Object.keys(fieldFormatMap).forEach((fieldName) => {
|
||||
const formatConfig: FieldFormatConfig = fieldFormatMap[fieldName];
|
||||
if (dataView) {
|
||||
Object.keys(dataView.fieldFormatMap).forEach((fieldName) => {
|
||||
const formatConfig: FieldFormatConfig = dataView.fieldFormatMap[fieldName];
|
||||
const formatParams = {
|
||||
...formatConfig.params,
|
||||
...serverDateParams,
|
||||
|
@ -48,12 +46,11 @@ export function fieldFormatMapFactory(
|
|||
}
|
||||
|
||||
// Add default FieldFormat instances for non-custom formatted fields
|
||||
const indexFields = JSON.parse(_.get(indexPatternSavedObject, 'attributes.fields', '[]'));
|
||||
indexFields.forEach((field: any) => {
|
||||
dataView?.fields.forEach((field) => {
|
||||
if (!formatsMap.has(field.name)) {
|
||||
formatsMap.set(
|
||||
field.name,
|
||||
fieldFormatsRegistry.getDefaultInstance(field.type, [], serverDateParams)
|
||||
fieldFormatsRegistry.getDefaultInstance(field.type as KBN_FIELD_TYPES, [], serverDateParams)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { Writable } from 'stream';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ElasticsearchClient, IUiSettingsClient } from 'src/core/server';
|
||||
import type { DataView, DataViewsService } from 'src/plugins/data/common';
|
||||
import { ReportingConfig } from '../../../';
|
||||
import { createEscapeValue } from '../../../../../../../src/plugins/data/common';
|
||||
import { CancellationToken } from '../../../../../../plugins/reporting/common';
|
||||
|
@ -16,10 +17,7 @@ import { byteSizeValueToNumber } from '../../../../common/schema_utils';
|
|||
import { LevelLogger } from '../../../lib';
|
||||
import { getFieldFormats } from '../../../services';
|
||||
import { MaxSizeStringBuilder } from '../../csv_searchsource/generate_csv/max_size_string_builder';
|
||||
import {
|
||||
IndexPatternSavedObjectDeprecatedCSV,
|
||||
SavedSearchGeneratorResultDeprecatedCSV,
|
||||
} from '../types';
|
||||
import { SavedSearchGeneratorResultDeprecatedCSV } from '../types';
|
||||
import { checkIfRowsHaveFormulas } from './check_cells_for_formulas';
|
||||
import { fieldFormatMapFactory } from './field_format_map';
|
||||
import { createFlattenHit } from './flatten_hit';
|
||||
|
@ -44,7 +42,7 @@ interface SearchRequest {
|
|||
export interface GenerateCsvParams {
|
||||
browserTimezone?: string;
|
||||
searchRequest: SearchRequest;
|
||||
indexPatternSavedObject: IndexPatternSavedObjectDeprecatedCSV;
|
||||
indexPatternId: string;
|
||||
fields: string[];
|
||||
metaFields: string[];
|
||||
conflictedTypesFields: string[];
|
||||
|
@ -58,6 +56,7 @@ export function createGenerateCsv(logger: LevelLogger) {
|
|||
config: ReportingConfig,
|
||||
uiSettingsClient: IUiSettingsClient,
|
||||
elasticsearchClient: ElasticsearchClient,
|
||||
dataViews: DataViewsService,
|
||||
cancellationToken: CancellationToken,
|
||||
stream: Writable
|
||||
): Promise<SavedSearchGeneratorResultDeprecatedCSV> {
|
||||
|
@ -91,11 +90,17 @@ export function createGenerateCsv(logger: LevelLogger) {
|
|||
let csvContainsFormulas = false;
|
||||
|
||||
const flattenHit = createFlattenHit(fields, metaFields, conflictedTypesFields);
|
||||
let dataView: DataView | undefined;
|
||||
|
||||
try {
|
||||
dataView = await dataViews.get(job.indexPatternId);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to get the data view "${job.indexPatternId}": ${error}`);
|
||||
}
|
||||
|
||||
const formatsMap = await getFieldFormats()
|
||||
.fieldFormatServiceFactory(uiSettingsClient)
|
||||
.then((fieldFormats) =>
|
||||
fieldFormatMapFactory(job.indexPatternSavedObject, fieldFormats, settings.timezone)
|
||||
);
|
||||
.then((fieldFormats) => fieldFormatMapFactory(dataView, fieldFormats, settings.timezone));
|
||||
|
||||
const formatCsvValues = createFormatCsvValues(
|
||||
escapeValue,
|
||||
|
|
|
@ -5,20 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { FieldSpec } from 'src/plugins/data/common';
|
||||
import { BaseParams, BasePayload } from '../../types';
|
||||
|
||||
export type RawValue = string | object | null | undefined;
|
||||
|
||||
export interface IndexPatternSavedObjectDeprecatedCSV {
|
||||
title: string;
|
||||
timeFieldName: string;
|
||||
fields?: any[];
|
||||
attributes: {
|
||||
fields: string;
|
||||
fieldFormatMap: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface BaseParamsDeprecatedCSV {
|
||||
searchRequest: SearchRequestDeprecatedCSV;
|
||||
fields: string[];
|
||||
|
@ -31,10 +22,9 @@ export type JobParamsDeprecatedCSV = BaseParamsDeprecatedCSV &
|
|||
indexPatternId: string;
|
||||
};
|
||||
|
||||
// CSV create job method converts indexPatternID to indexPatternSavedObject
|
||||
export type TaskPayloadDeprecatedCSV = BaseParamsDeprecatedCSV &
|
||||
BasePayload & {
|
||||
indexPatternSavedObject: IndexPatternSavedObjectDeprecatedCSV;
|
||||
indexPatternId: string;
|
||||
};
|
||||
|
||||
export interface SearchRequestDeprecatedCSV {
|
||||
|
|
|
@ -48,11 +48,11 @@ test(`passes title through if provided`, async () => {
|
|||
test(`gets the title from the savedObject`, async () => {
|
||||
const createJobMock = jest.fn();
|
||||
const title = 'savedTitle';
|
||||
mockRequestHandlerContext.core.savedObjects.client.get.mockResolvedValue(
|
||||
createMockSavedObject({
|
||||
mockRequestHandlerContext.core.savedObjects.client.resolve.mockResolvedValue({
|
||||
saved_object: createMockSavedObject({
|
||||
attributes: { title },
|
||||
})
|
||||
);
|
||||
}),
|
||||
} as any);
|
||||
|
||||
await compatibilityShim(createJobMock, mockLogger)(
|
||||
createMockJobParams({ objectType: 'search', savedObjectId: 'abc' }),
|
||||
|
@ -72,9 +72,9 @@ test(`gets the title from the savedObject`, async () => {
|
|||
test(`passes the objectType and savedObjectId to the savedObjectsClient`, async () => {
|
||||
const createJobMock = jest.fn();
|
||||
const context = mockRequestHandlerContext;
|
||||
context.core.savedObjects.client.get.mockResolvedValue(
|
||||
createMockSavedObject({ attributes: { title: '' } })
|
||||
);
|
||||
context.core.savedObjects.client.resolve.mockResolvedValue({
|
||||
saved_object: createMockSavedObject({ attributes: { title: '' } }),
|
||||
} as any);
|
||||
|
||||
const objectType = 'search';
|
||||
const savedObjectId = 'abc';
|
||||
|
@ -92,10 +92,8 @@ test(`passes the objectType and savedObjectId to the savedObjectsClient`, async
|
|||
);
|
||||
expect(mockLogger.error.mock.calls.length).toBe(0);
|
||||
|
||||
const getMock = context.core.savedObjects.client.get.mock;
|
||||
expect(getMock.calls.length).toBe(1);
|
||||
expect(getMock.calls[0][0]).toBe(objectType);
|
||||
expect(getMock.calls[0][1]).toBe(savedObjectId);
|
||||
expect(context.core.savedObjects.client.resolve).toHaveBeenCalledTimes(1);
|
||||
expect(context.core.savedObjects.client.resolve).toHaveBeenCalledWith(objectType, savedObjectId);
|
||||
});
|
||||
|
||||
test(`logs no warnings when title and relativeUrls is passed`, async () => {
|
||||
|
|
|
@ -22,7 +22,10 @@ const getSavedObjectTitle = async (
|
|||
savedObjectId: string,
|
||||
savedObjectsClient: SavedObjectsClientContract
|
||||
) => {
|
||||
const savedObject = await savedObjectsClient.get<{ title: string }>(objectType, savedObjectId);
|
||||
const { saved_object: savedObject } = await savedObjectsClient.resolve<{ title: string }>(
|
||||
objectType,
|
||||
savedObjectId
|
||||
);
|
||||
return savedObject.attributes.title;
|
||||
};
|
||||
|
||||
|
|
|
@ -71,6 +71,7 @@ describe('Handle request to generate', () => {
|
|||
(report) => new Report({ ...report, _index: '.reporting-foo-index-234' })
|
||||
),
|
||||
} as unknown as ReportingStore);
|
||||
|
||||
mockRequest = getMockRequest();
|
||||
|
||||
mockResponseFactory = getMockResponseFactory();
|
||||
|
@ -80,6 +81,7 @@ describe('Handle request to generate', () => {
|
|||
|
||||
mockContext = getMockContext();
|
||||
mockContext.reporting = {} as ReportingSetup;
|
||||
|
||||
requestHandler = new RequestHandler(
|
||||
reportingCore,
|
||||
{ username: 'testymcgee' },
|
||||
|
@ -195,7 +197,6 @@ describe('Handle request to generate', () => {
|
|||
"output": Object {},
|
||||
"payload": Object {
|
||||
"browserTimezone": "UTC",
|
||||
"indexPatternSavedObject": undefined,
|
||||
"isDeprecated": true,
|
||||
"layout": Object {
|
||||
"id": "preserve_layout",
|
||||
|
|
Loading…
Reference in a new issue