From 7791970bca1d00c863a9763483d5f10cd71f2b44 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Thu, 12 Sep 2019 18:34:25 -0700 Subject: [PATCH 01/14] Rename route file for consistency --- x-pack/legacy/plugins/reporting/server/routes/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/server/routes/index.ts b/x-pack/legacy/plugins/reporting/server/routes/index.ts index cef2137352fd32..cbfd096e7100bb 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/index.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/index.ts @@ -9,7 +9,7 @@ import { Request, ResponseToolkit } from 'hapi'; import { API_BASE_URL } from '../../common/constants'; import { KbnServer, Logger } from '../../types'; import { enqueueJobFactory } from '../lib/enqueue_job'; -import { registerGenerate } from './generate'; +import { registerGenerateFromJobParams } from './generate_from_jobparams'; import { registerGenerateCsvFromSavedObject } from './generate_from_savedobject'; import { registerGenerateCsvFromSavedObjectImmediate } from './generate_from_savedobject_immediate'; import { registerJobs } from './jobs'; @@ -60,7 +60,7 @@ export function registerRoutes(server: KbnServer, logger: Logger) { return err; } - registerGenerate(server, handler, handleError); + registerGenerateFromJobParams(server, handler, handleError); registerLegacy(server, handler, handleError); // Register beta panel-action download-related API's From 0640cab2b354a251b2a33d98f28ce6aad8afd885 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Thu, 12 Sep 2019 18:34:52 -0700 Subject: [PATCH 02/14] Generate via jobParams allows post payload --- ...generate.ts => generate_from_jobparams.ts} | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) rename x-pack/legacy/plugins/reporting/server/routes/{generate.ts => generate_from_jobparams.ts} (62%) diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts similarity index 62% rename from x-pack/legacy/plugins/reporting/server/routes/generate.ts rename to x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts index c1a3e39d21e9c2..1a2e0d15626061 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts @@ -5,6 +5,7 @@ */ import boom from 'boom'; +import Joi from 'joi'; import { Request, ResponseToolkit } from 'hapi'; import rison from 'rison-node'; import { API_BASE_URL } from '../../common/constants'; @@ -14,7 +15,7 @@ import { HandlerErrorFunction, HandlerFunction } from './types'; const BASE_GENERATE = `${API_BASE_URL}/generate`; -export function registerGenerate( +export function registerGenerateFromJobParams( server: KbnServer, handler: HandlerFunction, handleError: HandlerErrorFunction @@ -25,13 +26,38 @@ export function registerGenerate( server.route({ path: `${BASE_GENERATE}/{exportType}`, method: 'POST', - config: getRouteConfig(request => request.params.exportType), + config: { + ...getRouteConfig(request => request.params.exportType), + validate: { + params: Joi.object({ + exportType: Joi.string().required(), + }).required(), + payload: Joi.object({ + jobParams: Joi.string() + .optional() + .default(null), + }).allow(null), // allow optional payload + query: Joi.object({ + jobParams: Joi.string().default(null), + }).default(), + }, + }, handler: async (request: Request, h: ResponseToolkit) => { + let jobParamsRison: string; + + if (request.query.jobParams) { + jobParamsRison = request.query.jobParams; + } else if (request.payload && request.payload.jobParams) { + jobParamsRison = request.payload.jobParams; + } else { + throw boom.badRequest('A jobParams RISON string is required'); + } + const { exportType } = request.params; let response; try { // @ts-ignore - const jobParams = rison.decode(request.query.jobParams); + const jobParams = rison.decode(jobParamsRison); response = await handler(exportType, jobParams, request, h); } catch (err) { throw handleError(exportType, err); From 8dd32414fcdcba99387f0dd7563e87ecf1d631b7 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Thu, 12 Sep 2019 18:54:44 -0700 Subject: [PATCH 03/14] jobParams as post body --- .../plugins/reporting/public/lib/reporting_client.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/public/lib/reporting_client.ts b/x-pack/legacy/plugins/reporting/public/lib/reporting_client.ts index 8481d2993ffb34..b9574dfa457f3b 100644 --- a/x-pack/legacy/plugins/reporting/public/lib/reporting_client.ts +++ b/x-pack/legacy/plugins/reporting/public/lib/reporting_client.ts @@ -27,10 +27,14 @@ class ReportingClient { }; public createReportingJob = async (exportType: string, jobParams: any) => { - const query = { - jobParams: rison.encode(jobParams), - }; - const resp = await kfetch({ method: 'POST', pathname: `${API_BASE_URL}/${exportType}`, query }); + const jobParamsRison = rison.encode(jobParams); + const resp = await kfetch({ + method: 'POST', + pathname: `${API_BASE_URL}/${exportType}`, + body: JSON.stringify({ + jobParams: jobParamsRison, + }), + }); jobCompletionNotifications.add(resp.job.id); return resp; }; From df344d05f088dc3e647d6500344a8740f3ea1416 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Fri, 13 Sep 2019 10:28:08 -0700 Subject: [PATCH 04/14] [Reporting] Skip failing test --- x-pack/test/reporting/functional/reporting.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/reporting/functional/reporting.js b/x-pack/test/reporting/functional/reporting.js index 28894da586e78f..10373b506a80d6 100644 --- a/x-pack/test/reporting/functional/reporting.js +++ b/x-pack/test/reporting/functional/reporting.js @@ -73,7 +73,7 @@ export default function ({ getService, getPageObjects }) { await expectDisabledGenerateReportButton(); }); - it('becomes available when saved', async () => { + it.skip('becomes available when saved', async () => { await PageObjects.dashboard.saveDashboard('mypdfdash'); await PageObjects.reporting.openPdfReportingPanel(); await expectEnabledGenerateReportButton(); From 68db0d4db91516becc92d9fdbf645e97eabd7ffb Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Fri, 13 Sep 2019 11:24:22 -0700 Subject: [PATCH 05/14] fix types --- .../server/routes/generate_from_jobparams.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts index 1a2e0d15626061..682e4637ca18a5 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts @@ -45,12 +45,16 @@ export function registerGenerateFromJobParams( handler: async (request: Request, h: ResponseToolkit) => { let jobParamsRison: string; - if (request.query.jobParams) { - jobParamsRison = request.query.jobParams; - } else if (request.payload && request.payload.jobParams) { - jobParamsRison = request.payload.jobParams; + if (request.payload) { + const { jobParams: jobParamsPayload } = request.payload as { jobParams: string }; + jobParamsRison = jobParamsPayload; } else { - throw boom.badRequest('A jobParams RISON string is required'); + const { jobParams: queryJobParams } = request.query as { jobParams: string }; + if (queryJobParams) { + jobParamsRison = queryJobParams; + } else { + throw boom.badRequest('A jobParams RISON string is required'); + } } const { exportType } = request.params; From 1b7b3d8d72d81cd1843a9e52a5dfaab7cbed9809 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Fri, 13 Sep 2019 12:08:44 -0700 Subject: [PATCH 06/14] canvas pdf generation to use post payload --- .../workpad_header/workpad_export/utils.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.ts b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.ts index 99e50ee43eb109..dd8dae86e4068b 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.ts +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.ts @@ -6,7 +6,6 @@ import rison from 'rison-node'; import chrome from 'ui/chrome'; -import { QueryString } from 'ui/utils/query_string'; // @ts-ignore Untyped local. import { fetch } from '../../../../common/lib/fetch'; import { CanvasWorkpad } from '../../../../types'; @@ -54,13 +53,15 @@ export function getPdfUrl( title, }; - return `${reportingEntry}/printablePdf?${QueryString.param( - 'jobParams', - rison.encode(jobParams) - )}`; + return { + createPdfUri: `${reportingEntry}/printablePdf`, + createPdfPayload: { + jobParams: rison.encode(jobParams), + }, + }; } export function createPdf(...args: Arguments) { - const createPdfUri = getPdfUrl(...args); - return fetch.post(createPdfUri); + const { createPdfUri, createPdfPayload } = getPdfUrl(...args); + return fetch.post(createPdfUri, createPdfPayload); } From 411178e2e1cd2e07a1972fc05a0bc5e53827e876 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Fri, 13 Sep 2019 13:46:02 -0700 Subject: [PATCH 07/14] api test --- .../server/routes/generate_from_jobparams.ts | 9 +- .../reporting/api/generate/csv_job_params.ts | 88 +++++++++++++++++++ x-pack/test/reporting/api/generate/index.js | 1 + 3 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 x-pack/test/reporting/api/generate/csv_job_params.ts diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts index 682e4637ca18a5..939cda36d93a32 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts @@ -43,7 +43,7 @@ export function registerGenerateFromJobParams( }, }, handler: async (request: Request, h: ResponseToolkit) => { - let jobParamsRison: string; + let jobParamsRison: string | null; if (request.payload) { const { jobParams: jobParamsPayload } = request.payload as { jobParams: string }; @@ -53,14 +53,17 @@ export function registerGenerateFromJobParams( if (queryJobParams) { jobParamsRison = queryJobParams; } else { - throw boom.badRequest('A jobParams RISON string is required'); + jobParamsRison = null; } } + if (!jobParamsRison) { + throw boom.badRequest('A jobParams RISON string is required'); + } + const { exportType } = request.params; let response; try { - // @ts-ignore const jobParams = rison.decode(jobParamsRison); response = await handler(exportType, jobParams, request, h); } catch (err) { diff --git a/x-pack/test/reporting/api/generate/csv_job_params.ts b/x-pack/test/reporting/api/generate/csv_job_params.ts new file mode 100644 index 00000000000000..1cd0f287e5c2f6 --- /dev/null +++ b/x-pack/test/reporting/api/generate/csv_job_params.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import supertest from 'supertest'; + +// eslint-disable-next-line import/no-default-export +export default function({ getService }: { getService: any }) { + const esArchiver = getService('esArchiver'); + const supertestSvc = getService('supertest'); + const generateAPI = { + getCsvFromParamsInPayload: async (jobParams: object = {}) => { + return await supertestSvc + .post(`/api/reporting/generate/csv`) + .set('kbn-xsrf', 'xxx') + .send(jobParams); + }, + getCsvFromParamsInQueryString: async (jobParams: string = '') => { + return await supertestSvc + .post(`/api/reporting/generate/csv?jobParams=${jobParams}`) + .set('kbn-xsrf', 'xxx') + .send(jobParams); + }, + }; + + describe('Generation from Job Params', () => { + it('Rejects bogus jobParams', async () => { + // load test data that contains a saved search and documents + await esArchiver.load('reporting/logs'); + + const { status: resStatus, text: resText } = (await generateAPI.getCsvFromParamsInPayload({ + jobParams: 0, + })) as supertest.Response; + + expect(resStatus).to.eql(400); + expect(resText).to.match(/\\\"jobParams\\\" must be a string/); + + await esArchiver.unload('reporting/logs'); + }); + + it('Rejects empty jobParams', async () => { + // load test data that contains a saved search and documents + await esArchiver.load('reporting/logs'); + + const { + status: resStatus, + text: resText, + } = (await generateAPI.getCsvFromParamsInPayload()) as supertest.Response; + + expect(resStatus).to.eql(400); + expect(resText).to.match(/jobParams RISON string is required/); + + await esArchiver.unload('reporting/logs'); + }); + + it('Accepts jobParams in POST payload', async () => { + // load test data that contains a saved search and documents + await esArchiver.load('reporting/logs'); + + const { status: resStatus, text: resText } = (await generateAPI.getCsvFromParamsInPayload({ + jobParams: + "(conflictedTypesFields:!(),fields:!('@date',_id,_index,_score,_type,country,metric,name),indexPatternId:b6a10720-ce76-11e9-aa85-47efb3fa905c,metaFields:!(_source,_id,_type,_index,_score),searchRequest:(body:(_source:(excludes:!()),docvalue_fields:!((field:'@date',format:date_time)),query:(bool:(filter:!((match_all:()),(range:('@date':(format:strict_date_optional_time,gte:'2004-01-01T07:00:00.000Z',lte:'2019-09-13T00:43:32.540Z')))),must:!(),must_not:!(),should:!())),script_fields:(),sort:!(('@date':(order:desc,unmapped_type:boolean))),stored_fields:!('*'),version:!t),index:tests),title:'New Saved Search 4',type:search)", + })) as supertest.Response; + + expect(resStatus).to.eql(400); + expect(resText).to.match(/jobParams RISON string is required/); + + await esArchiver.unload('reporting/logs'); + }); + + it('Accepts jobParams in query string', async () => { + // load test data that contains a saved search and documents + await esArchiver.load('reporting/logs'); + + const { status: resStatus, text: resText } = (await generateAPI.getCsvFromParamsInQueryString( + '?jobParams=(conflictedTypesFields:!(),fields:!(%27@date%27,_id,_index,_score,_type,country,metric,name),indexPatternId:b6a10720-ce76-11e9-aa85-47efb3fa905c,metaFields:!(_source,_id,_type,_index,_score),searchRequest:(body:(_source:(excludes:!()),docvalue_fields:!((field:%27@date%27,format:date_time)),query:(bool:(filter:!((match_all:()),(range:(%27@date%27:(format:strict_date_optional_time,gte:%272004-01-01T07:00:00.000Z%27,lte:%272019-09-13T00:43:32.540Z%27)))),must:!(),must_not:!(),should:!())),script_fields:(),sort:!((%27@date%27:(order:desc,unmapped_type:boolean))),stored_fields:!(%27*%27),version:!t),index:tests),title:%27New%20Saved%20Search%204%27,type:search)' + )) as supertest.Response; + + expect(resStatus).to.eql(400); + expect(resText).to.match(/jobParams RISON string is required/); + + await esArchiver.unload('reporting/logs'); + }); + }); +} diff --git a/x-pack/test/reporting/api/generate/index.js b/x-pack/test/reporting/api/generate/index.js index 99286762f44f10..eeeef2b43f967a 100644 --- a/x-pack/test/reporting/api/generate/index.js +++ b/x-pack/test/reporting/api/generate/index.js @@ -8,5 +8,6 @@ export default function ({ loadTestFile }) { describe('CSV', function () { this.tags('ciGroup2'); loadTestFile(require.resolve('./csv_saved_search')); + loadTestFile(require.resolve('./csv_job_params')); }); } From 7039ee20111a9556b509cc8f1fdc8a7518f5e58e Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Fri, 13 Sep 2019 15:49:52 -0700 Subject: [PATCH 08/14] tests --- .../reporting/api/generate/csv_job_params.ts | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/x-pack/test/reporting/api/generate/csv_job_params.ts b/x-pack/test/reporting/api/generate/csv_job_params.ts index 1cd0f287e5c2f6..1237a41b734fca 100644 --- a/x-pack/test/reporting/api/generate/csv_job_params.ts +++ b/x-pack/test/reporting/api/generate/csv_job_params.ts @@ -20,7 +20,7 @@ export default function({ getService }: { getService: any }) { }, getCsvFromParamsInQueryString: async (jobParams: string = '') => { return await supertestSvc - .post(`/api/reporting/generate/csv?jobParams=${jobParams}`) + .post(`/api/reporting/generate/csv${jobParams}`) .set('kbn-xsrf', 'xxx') .send(jobParams); }, @@ -59,29 +59,21 @@ export default function({ getService }: { getService: any }) { it('Accepts jobParams in POST payload', async () => { // load test data that contains a saved search and documents await esArchiver.load('reporting/logs'); - - const { status: resStatus, text: resText } = (await generateAPI.getCsvFromParamsInPayload({ + const { status: resStatus } = (await generateAPI.getCsvFromParamsInPayload({ jobParams: "(conflictedTypesFields:!(),fields:!('@date',_id,_index,_score,_type,country,metric,name),indexPatternId:b6a10720-ce76-11e9-aa85-47efb3fa905c,metaFields:!(_source,_id,_type,_index,_score),searchRequest:(body:(_source:(excludes:!()),docvalue_fields:!((field:'@date',format:date_time)),query:(bool:(filter:!((match_all:()),(range:('@date':(format:strict_date_optional_time,gte:'2004-01-01T07:00:00.000Z',lte:'2019-09-13T00:43:32.540Z')))),must:!(),must_not:!(),should:!())),script_fields:(),sort:!(('@date':(order:desc,unmapped_type:boolean))),stored_fields:!('*'),version:!t),index:tests),title:'New Saved Search 4',type:search)", })) as supertest.Response; - - expect(resStatus).to.eql(400); - expect(resText).to.match(/jobParams RISON string is required/); - + expect(resStatus).to.eql(200); await esArchiver.unload('reporting/logs'); }); it('Accepts jobParams in query string', async () => { // load test data that contains a saved search and documents await esArchiver.load('reporting/logs'); - - const { status: resStatus, text: resText } = (await generateAPI.getCsvFromParamsInQueryString( + const { status: resStatus } = (await generateAPI.getCsvFromParamsInQueryString( '?jobParams=(conflictedTypesFields:!(),fields:!(%27@date%27,_id,_index,_score,_type,country,metric,name),indexPatternId:b6a10720-ce76-11e9-aa85-47efb3fa905c,metaFields:!(_source,_id,_type,_index,_score),searchRequest:(body:(_source:(excludes:!()),docvalue_fields:!((field:%27@date%27,format:date_time)),query:(bool:(filter:!((match_all:()),(range:(%27@date%27:(format:strict_date_optional_time,gte:%272004-01-01T07:00:00.000Z%27,lte:%272019-09-13T00:43:32.540Z%27)))),must:!(),must_not:!(),should:!())),script_fields:(),sort:!((%27@date%27:(order:desc,unmapped_type:boolean))),stored_fields:!(%27*%27),version:!t),index:tests),title:%27New%20Saved%20Search%204%27,type:search)' )) as supertest.Response; - - expect(resStatus).to.eql(400); - expect(resText).to.match(/jobParams RISON string is required/); - + expect(resStatus).to.eql(200); await esArchiver.unload('reporting/logs'); }); }); From d3fba778a733b47ee791148240968d096a22556d Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Tue, 17 Sep 2019 12:45:11 -0700 Subject: [PATCH 09/14] fix a typescript --- .../components/workpad_header/workpad_export/index.ts | 3 ++- .../components/workpad_header/workpad_export/utils.ts | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/index.ts b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/index.ts index c430ef6dd54d24..b4872a906648b9 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/index.ts +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/index.ts @@ -50,7 +50,8 @@ export const WorkpadExport = compose( enabled, getExportUrl: type => { if (type === 'pdf') { - return getAbsoluteUrl(getPdfUrl(workpad, { pageCount })); + const { createPdfUri } = getPdfUrl(workpad, { pageCount }); + return getAbsoluteUrl(createPdfUri); } throw new Error(`Unknown export type: ${type}`); diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.ts b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.ts index dd8dae86e4068b..b06bf5a74b528a 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.ts +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.ts @@ -19,10 +19,15 @@ interface PageCount { type Arguments = [CanvasWorkpad, PageCount]; +interface PdfUrlData { + createPdfUri: string; + createPdfPayload: { jobParams: string }; +} + export function getPdfUrl( { id, name: title, width, height }: CanvasWorkpad, { pageCount }: PageCount -) { +): PdfUrlData { const reportingEntry = chrome.addBasePath('/api/reporting/generate'); const canvasEntry = '/app/canvas#'; From 317212b1e70870f6e14521b4ca4e84ef2b26ba93 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Tue, 17 Sep 2019 14:39:47 -0700 Subject: [PATCH 10/14] fix fn test --- .../reporting/api/generate/csv_job_params.ts | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/x-pack/test/reporting/api/generate/csv_job_params.ts b/x-pack/test/reporting/api/generate/csv_job_params.ts index 1237a41b734fca..f75d4c54e6ff1d 100644 --- a/x-pack/test/reporting/api/generate/csv_job_params.ts +++ b/x-pack/test/reporting/api/generate/csv_job_params.ts @@ -27,24 +27,29 @@ export default function({ getService }: { getService: any }) { }; describe('Generation from Job Params', () => { - it('Rejects bogus jobParams', async () => { - // load test data that contains a saved search and documents - await esArchiver.load('reporting/logs'); + before(() => + Promise.all([ + esArchiver.load('reporting/logs'), + esArchiver.load('logstash_functional') + ]) + ); // prettier-ignore + after(() => + Promise.all([ + esArchiver.unload('reporting/logs'), + esArchiver.unload('logstash_functional') + ]) + ); // prettier-ignore + it('Rejects bogus jobParams', async () => { const { status: resStatus, text: resText } = (await generateAPI.getCsvFromParamsInPayload({ jobParams: 0, })) as supertest.Response; expect(resStatus).to.eql(400); expect(resText).to.match(/\\\"jobParams\\\" must be a string/); - - await esArchiver.unload('reporting/logs'); }); it('Rejects empty jobParams', async () => { - // load test data that contains a saved search and documents - await esArchiver.load('reporting/logs'); - const { status: resStatus, text: resText, @@ -52,29 +57,21 @@ export default function({ getService }: { getService: any }) { expect(resStatus).to.eql(400); expect(resText).to.match(/jobParams RISON string is required/); - - await esArchiver.unload('reporting/logs'); }); it('Accepts jobParams in POST payload', async () => { - // load test data that contains a saved search and documents - await esArchiver.load('reporting/logs'); const { status: resStatus } = (await generateAPI.getCsvFromParamsInPayload({ - jobParams: - "(conflictedTypesFields:!(),fields:!('@date',_id,_index,_score,_type,country,metric,name),indexPatternId:b6a10720-ce76-11e9-aa85-47efb3fa905c,metaFields:!(_source,_id,_type,_index,_score),searchRequest:(body:(_source:(excludes:!()),docvalue_fields:!((field:'@date',format:date_time)),query:(bool:(filter:!((match_all:()),(range:('@date':(format:strict_date_optional_time,gte:'2004-01-01T07:00:00.000Z',lte:'2019-09-13T00:43:32.540Z')))),must:!(),must_not:!(),should:!())),script_fields:(),sort:!(('@date':(order:desc,unmapped_type:boolean))),stored_fields:!('*'),version:!t),index:tests),title:'New Saved Search 4',type:search)", + jobParams: `(conflictedTypesFields:!(),fields:!('@timestamp',clientip,extension),indexPatternId:'logstash-*',metaFields:!(_source,_id,_type,_index,_score),searchRequest:(body:(_source:(excludes:!(),includes:!('@timestamp',clientip,extension)),docvalue_fields:!(),query:(bool:(filter:!((match_all:()),(range:('@timestamp':(gte:'2015-09-20T10:19:40.307Z',lt:'2015-09-20T10:26:56.221Z'))),(range:('@timestamp':(format:strict_date_optional_time,gte:'2004-09-17T21:19:34.213Z',lte:'2019-09-17T21:19:34.213Z')))),must:!(),must_not:!(),should:!())),script_fields:(),sort:!(('@timestamp':(order:desc,unmapped_type:boolean))),stored_fields:!('@timestamp',clientip,extension),version:!t),index:'logstash-*'),title:'A Saved Search With a DATE FILTER',type:search)`, })) as supertest.Response; expect(resStatus).to.eql(200); - await esArchiver.unload('reporting/logs'); }); it('Accepts jobParams in query string', async () => { - // load test data that contains a saved search and documents - await esArchiver.load('reporting/logs'); - const { status: resStatus } = (await generateAPI.getCsvFromParamsInQueryString( - '?jobParams=(conflictedTypesFields:!(),fields:!(%27@date%27,_id,_index,_score,_type,country,metric,name),indexPatternId:b6a10720-ce76-11e9-aa85-47efb3fa905c,metaFields:!(_source,_id,_type,_index,_score),searchRequest:(body:(_source:(excludes:!()),docvalue_fields:!((field:%27@date%27,format:date_time)),query:(bool:(filter:!((match_all:()),(range:(%27@date%27:(format:strict_date_optional_time,gte:%272004-01-01T07:00:00.000Z%27,lte:%272019-09-13T00:43:32.540Z%27)))),must:!(),must_not:!(),should:!())),script_fields:(),sort:!((%27@date%27:(order:desc,unmapped_type:boolean))),stored_fields:!(%27*%27),version:!t),index:tests),title:%27New%20Saved%20Search%204%27,type:search)' + const { status: resStatus, text: resText } = (await generateAPI.getCsvFromParamsInQueryString( + `jobParams=(conflictedTypesFields:!(),fields:!(%27@timestamp%27,clientip,extension),indexPatternId:%27logstash-*%27,metaFields:!(_source,_id,_type,_index,_score),searchRequest:(body:(_source:(excludes:!(),includes:!(%27@timestamp%27,clientip,extension)),docvalue_fields:!(),query:(bool:(filter:!((match_all:()),(range:(%27@timestamp%27:(gte:%272015-09-20T10:19:40.307Z%27,lt:%272015-09-20T10:26:56.221Z%27))),(range:(%27@timestamp%27:(format:strict_date_optional_time,gte:%272004-09-17T21:19:34.213Z%27,lte:%272019-09-17T21:19:34.213Z%27)))),must:!(),must_not:!(),should:!())),script_fields:(),sort:!((%27@timestamp%27:(order:desc,unmapped_type:boolean))),stored_fields:!(%27@timestamp%27,clientip,extension),version:!t),index:%27logstash-*%27),title:%27A%20Saved%20Search%20With%20a%20DATE%20FILTER%27,type:search)` )) as supertest.Response; + expect(resText).to.eql('abc'); expect(resStatus).to.eql(200); - await esArchiver.unload('reporting/logs'); }); }); } From ce9433c2c26926fdc27bd04f217df5cdec896421 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Tue, 17 Sep 2019 15:16:43 -0700 Subject: [PATCH 11/14] fix tests --- .../reporting/api/generate/csv_job_params.ts | 15 ++++++------ .../test/reporting/api/generate/fixtures.ts | 24 +++++++++++++++++++ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/x-pack/test/reporting/api/generate/csv_job_params.ts b/x-pack/test/reporting/api/generate/csv_job_params.ts index f75d4c54e6ff1d..51a0c07d089c30 100644 --- a/x-pack/test/reporting/api/generate/csv_job_params.ts +++ b/x-pack/test/reporting/api/generate/csv_job_params.ts @@ -6,6 +6,7 @@ import expect from '@kbn/expect'; import supertest from 'supertest'; +import { JOB_PARAMS_IN_POSTBODY, JOB_PARAMS_IN_QUERYSTRING } from './fixtures'; // eslint-disable-next-line import/no-default-export export default function({ getService }: { getService: any }) { @@ -20,9 +21,8 @@ export default function({ getService }: { getService: any }) { }, getCsvFromParamsInQueryString: async (jobParams: string = '') => { return await supertestSvc - .post(`/api/reporting/generate/csv${jobParams}`) - .set('kbn-xsrf', 'xxx') - .send(jobParams); + .post(`/api/reporting/generate/csv?jobParams=${jobParams}`) + .set('kbn-xsrf', 'xxx'); }, }; @@ -60,18 +60,19 @@ export default function({ getService }: { getService: any }) { }); it('Accepts jobParams in POST payload', async () => { - const { status: resStatus } = (await generateAPI.getCsvFromParamsInPayload({ - jobParams: `(conflictedTypesFields:!(),fields:!('@timestamp',clientip,extension),indexPatternId:'logstash-*',metaFields:!(_source,_id,_type,_index,_score),searchRequest:(body:(_source:(excludes:!(),includes:!('@timestamp',clientip,extension)),docvalue_fields:!(),query:(bool:(filter:!((match_all:()),(range:('@timestamp':(gte:'2015-09-20T10:19:40.307Z',lt:'2015-09-20T10:26:56.221Z'))),(range:('@timestamp':(format:strict_date_optional_time,gte:'2004-09-17T21:19:34.213Z',lte:'2019-09-17T21:19:34.213Z')))),must:!(),must_not:!(),should:!())),script_fields:(),sort:!(('@timestamp':(order:desc,unmapped_type:boolean))),stored_fields:!('@timestamp',clientip,extension),version:!t),index:'logstash-*'),title:'A Saved Search With a DATE FILTER',type:search)`, + const { status: resStatus, text: resText } = (await generateAPI.getCsvFromParamsInPayload({ + jobParams: JOB_PARAMS_IN_POSTBODY, })) as supertest.Response; expect(resStatus).to.eql(200); + expect(resText).to.match(/"jobtype":"csv"/); }); it('Accepts jobParams in query string', async () => { const { status: resStatus, text: resText } = (await generateAPI.getCsvFromParamsInQueryString( - `jobParams=(conflictedTypesFields:!(),fields:!(%27@timestamp%27,clientip,extension),indexPatternId:%27logstash-*%27,metaFields:!(_source,_id,_type,_index,_score),searchRequest:(body:(_source:(excludes:!(),includes:!(%27@timestamp%27,clientip,extension)),docvalue_fields:!(),query:(bool:(filter:!((match_all:()),(range:(%27@timestamp%27:(gte:%272015-09-20T10:19:40.307Z%27,lt:%272015-09-20T10:26:56.221Z%27))),(range:(%27@timestamp%27:(format:strict_date_optional_time,gte:%272004-09-17T21:19:34.213Z%27,lte:%272019-09-17T21:19:34.213Z%27)))),must:!(),must_not:!(),should:!())),script_fields:(),sort:!((%27@timestamp%27:(order:desc,unmapped_type:boolean))),stored_fields:!(%27@timestamp%27,clientip,extension),version:!t),index:%27logstash-*%27),title:%27A%20Saved%20Search%20With%20a%20DATE%20FILTER%27,type:search)` + JOB_PARAMS_IN_QUERYSTRING )) as supertest.Response; - expect(resText).to.eql('abc'); expect(resStatus).to.eql(200); + expect(resText).to.match(/"jobtype":"csv"/); }); }); } diff --git a/x-pack/test/reporting/api/generate/fixtures.ts b/x-pack/test/reporting/api/generate/fixtures.ts index f8ca41b95e2027..940e49d7928f6d 100644 --- a/x-pack/test/reporting/api/generate/fixtures.ts +++ b/x-pack/test/reporting/api/generate/fixtures.ts @@ -158,3 +158,27 @@ export const CSV_RESULT_NANOS = `date,message,"_id" "2015-01-01T12:10:30.123456789Z","Hello 2", "2015-01-01T12:10:30","Hello 1", `; + +export const JOB_PARAMS_IN_QUERYSTRING = + `(conflictedTypesFields:!(),fields:!(%27@timestamp%27,clientip,extens` + + `ion),indexPatternId:%27logstash-*%27,metaFields:!(_source,_id,_type,_index,_scor` + + `e),searchRequest:(body:(_source:(excludes:!(),includes:!(%27@timestamp%27,client` + + `ip,extension)),docvalue_fields:!(),query:(bool:(filter:!((match_all:()),(range:(` + + `%27@timestamp%27:(gte:%272015-09-20T10:19:40.307Z%27,lt:%272015-09-20T10:26:56.2` + + `21Z%27))),(range:(%27@timestamp%27:(format:strict_date_optional_time,gte:%272004` + + `-09-17T21:19:34.213Z%27,lte:%272019-09-17T21:19:34.213Z%27)))),must:!(),must_not` + + `:!(),should:!())),script_fields:(),sort:!((%27@timestamp%27:(order:desc,unmapped` + + `_type:boolean))),stored_fields:!(%27@timestamp%27,clientip,extension),version:!t` + + `),index:%27logstash-*%27),title:%27A%20Saved%20Search%20With%20a%20DATE%20FILTER` + + `%27,type:search)`; +export const JOB_PARAMS_IN_POSTBODY = + `(conflictedTypesFields:!(),fields:!('@timestamp',clientip,extension),indexPatt` + + `ernId:'logstash-*',metaFields:!(_source,_id,_type,_index,_score),searchRequest:(` + + `body:(_source:(excludes:!(),includes:!('@timestamp',clientip,extension)),docvalu` + + `e_fields:!(),query:(bool:(filter:!((match_all:()),(range:('@timestamp':(gte:'201` + + `5-09-20T10:19:40.307Z',lt:'2015-09-20T10:26:56.221Z'))),(range:('@timestamp':(fo` + + `rmat:strict_date_optional_time,gte:'2004-09-17T21:19:34.213Z',lte:'2019-09-17T21` + + `:19:34.213Z')))),must:!(),must_not:!(),should:!())),script_fields:(),sort:!(('@t` + + `imestamp':(order:desc,unmapped_type:boolean))),stored_fields:!('@timestamp',clie` + + `ntip,extension),version:!t),index:'logstash-*'),title:'A Saved Search With a DAT` + + `E FILTER',type:search)`; From 9319e87f7c129212b6c7dd306a8f2288e5cfb5a1 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Thu, 19 Sep 2019 12:22:51 -0700 Subject: [PATCH 12/14] fix tests --- .../reporting/api/generate/csv_job_params.ts | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/x-pack/test/reporting/api/generate/csv_job_params.ts b/x-pack/test/reporting/api/generate/csv_job_params.ts index 51a0c07d089c30..656bc0ae29e4e1 100644 --- a/x-pack/test/reporting/api/generate/csv_job_params.ts +++ b/x-pack/test/reporting/api/generate/csv_job_params.ts @@ -27,29 +27,25 @@ export default function({ getService }: { getService: any }) { }; describe('Generation from Job Params', () => { - before(() => - Promise.all([ - esArchiver.load('reporting/logs'), - esArchiver.load('logstash_functional') - ]) - ); // prettier-ignore - after(() => - Promise.all([ - esArchiver.unload('reporting/logs'), - esArchiver.unload('logstash_functional') - ]) - ); // prettier-ignore - it('Rejects bogus jobParams', async () => { + await esArchiver.load('reporting/logs'); + await esArchiver.load('logstash_functional'); + const { status: resStatus, text: resText } = (await generateAPI.getCsvFromParamsInPayload({ jobParams: 0, })) as supertest.Response; expect(resStatus).to.eql(400); expect(resText).to.match(/\\\"jobParams\\\" must be a string/); + + await esArchiver.unload('reporting/logs'); + await esArchiver.unload('logstash_functional'); }); it('Rejects empty jobParams', async () => { + await esArchiver.load('reporting/logs'); + await esArchiver.load('logstash_functional'); + const { status: resStatus, text: resText, @@ -57,22 +53,37 @@ export default function({ getService }: { getService: any }) { expect(resStatus).to.eql(400); expect(resText).to.match(/jobParams RISON string is required/); + + await esArchiver.unload('reporting/logs'); + await esArchiver.unload('logstash_functional'); }); it('Accepts jobParams in POST payload', async () => { + await esArchiver.load('reporting/logs'); + await esArchiver.load('logstash_functional'); + const { status: resStatus, text: resText } = (await generateAPI.getCsvFromParamsInPayload({ jobParams: JOB_PARAMS_IN_POSTBODY, })) as supertest.Response; expect(resStatus).to.eql(200); expect(resText).to.match(/"jobtype":"csv"/); + + await esArchiver.unload('reporting/logs'); + await esArchiver.unload('logstash_functional'); }); it('Accepts jobParams in query string', async () => { + await esArchiver.load('reporting/logs'); + await esArchiver.load('logstash_functional'); + const { status: resStatus, text: resText } = (await generateAPI.getCsvFromParamsInQueryString( JOB_PARAMS_IN_QUERYSTRING )) as supertest.Response; expect(resStatus).to.eql(200); expect(resText).to.match(/"jobtype":"csv"/); + + await esArchiver.unload('reporting/logs'); + await esArchiver.unload('logstash_functional'); }); }); } From 1703ccb2946e387445486db9710d98c13dec5390 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Thu, 19 Sep 2019 14:26:04 -0700 Subject: [PATCH 13/14] run esArchiver fewer times --- .../reporting/api/generate/csv_job_params.ts | 29 +++++-------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/x-pack/test/reporting/api/generate/csv_job_params.ts b/x-pack/test/reporting/api/generate/csv_job_params.ts index 656bc0ae29e4e1..c776d5b5f2ac33 100644 --- a/x-pack/test/reporting/api/generate/csv_job_params.ts +++ b/x-pack/test/reporting/api/generate/csv_job_params.ts @@ -27,25 +27,25 @@ export default function({ getService }: { getService: any }) { }; describe('Generation from Job Params', () => { - it('Rejects bogus jobParams', async () => { + before(async () => { await esArchiver.load('reporting/logs'); await esArchiver.load('logstash_functional'); + }); // prettier-ignore + after(async () => { + await esArchiver.unload('reporting/logs'); + await esArchiver.unload('logstash_functional'); + }); // prettier-ignore + it('Rejects bogus jobParams', async () => { const { status: resStatus, text: resText } = (await generateAPI.getCsvFromParamsInPayload({ jobParams: 0, })) as supertest.Response; expect(resStatus).to.eql(400); expect(resText).to.match(/\\\"jobParams\\\" must be a string/); - - await esArchiver.unload('reporting/logs'); - await esArchiver.unload('logstash_functional'); }); it('Rejects empty jobParams', async () => { - await esArchiver.load('reporting/logs'); - await esArchiver.load('logstash_functional'); - const { status: resStatus, text: resText, @@ -53,37 +53,22 @@ export default function({ getService }: { getService: any }) { expect(resStatus).to.eql(400); expect(resText).to.match(/jobParams RISON string is required/); - - await esArchiver.unload('reporting/logs'); - await esArchiver.unload('logstash_functional'); }); it('Accepts jobParams in POST payload', async () => { - await esArchiver.load('reporting/logs'); - await esArchiver.load('logstash_functional'); - const { status: resStatus, text: resText } = (await generateAPI.getCsvFromParamsInPayload({ jobParams: JOB_PARAMS_IN_POSTBODY, })) as supertest.Response; expect(resStatus).to.eql(200); expect(resText).to.match(/"jobtype":"csv"/); - - await esArchiver.unload('reporting/logs'); - await esArchiver.unload('logstash_functional'); }); it('Accepts jobParams in query string', async () => { - await esArchiver.load('reporting/logs'); - await esArchiver.load('logstash_functional'); - const { status: resStatus, text: resText } = (await generateAPI.getCsvFromParamsInQueryString( JOB_PARAMS_IN_QUERYSTRING )) as supertest.Response; expect(resStatus).to.eql(200); expect(resText).to.match(/"jobtype":"csv"/); - - await esArchiver.unload('reporting/logs'); - await esArchiver.unload('logstash_functional'); }); }); } From 5e6735b7ab7ebc2ef85e68c4008748208008b5b2 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Fri, 20 Sep 2019 23:22:39 -0700 Subject: [PATCH 14/14] return rison that came in invalid --- .../server/routes/generate_from_jobparams.ts | 2 +- .../reporting/api/generate/csv_job_params.ts | 14 +++---- .../test/reporting/api/generate/fixtures.ts | 38 ++++++++----------- 3 files changed, 23 insertions(+), 31 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts index 939cda36d93a32..9dc1e9d12e308b 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts @@ -67,7 +67,7 @@ export function registerGenerateFromJobParams( const jobParams = rison.decode(jobParamsRison); response = await handler(exportType, jobParams, request, h); } catch (err) { - throw handleError(exportType, err); + throw boom.badRequest(`invalid rison: ${jobParamsRison}`); } return response; }, diff --git a/x-pack/test/reporting/api/generate/csv_job_params.ts b/x-pack/test/reporting/api/generate/csv_job_params.ts index c776d5b5f2ac33..c8d6f11b74f9dd 100644 --- a/x-pack/test/reporting/api/generate/csv_job_params.ts +++ b/x-pack/test/reporting/api/generate/csv_job_params.ts @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import supertest from 'supertest'; -import { JOB_PARAMS_IN_POSTBODY, JOB_PARAMS_IN_QUERYSTRING } from './fixtures'; +import { JOB_PARAMS_RISON } from './fixtures'; // eslint-disable-next-line import/no-default-export export default function({ getService }: { getService: any }) { @@ -21,7 +21,7 @@ export default function({ getService }: { getService: any }) { }, getCsvFromParamsInQueryString: async (jobParams: string = '') => { return await supertestSvc - .post(`/api/reporting/generate/csv?jobParams=${jobParams}`) + .post(`/api/reporting/generate/csv?jobParams=${encodeURIComponent(jobParams)}`) .set('kbn-xsrf', 'xxx'); }, }; @@ -41,8 +41,8 @@ export default function({ getService }: { getService: any }) { jobParams: 0, })) as supertest.Response; - expect(resStatus).to.eql(400); expect(resText).to.match(/\\\"jobParams\\\" must be a string/); + expect(resStatus).to.eql(400); }); it('Rejects empty jobParams', async () => { @@ -57,18 +57,18 @@ export default function({ getService }: { getService: any }) { it('Accepts jobParams in POST payload', async () => { const { status: resStatus, text: resText } = (await generateAPI.getCsvFromParamsInPayload({ - jobParams: JOB_PARAMS_IN_POSTBODY, + jobParams: JOB_PARAMS_RISON, })) as supertest.Response; - expect(resStatus).to.eql(200); expect(resText).to.match(/"jobtype":"csv"/); + expect(resStatus).to.eql(200); }); it('Accepts jobParams in query string', async () => { const { status: resStatus, text: resText } = (await generateAPI.getCsvFromParamsInQueryString( - JOB_PARAMS_IN_QUERYSTRING + JOB_PARAMS_RISON )) as supertest.Response; - expect(resStatus).to.eql(200); expect(resText).to.match(/"jobtype":"csv"/); + expect(resStatus).to.eql(200); }); }); } diff --git a/x-pack/test/reporting/api/generate/fixtures.ts b/x-pack/test/reporting/api/generate/fixtures.ts index 940e49d7928f6d..a9fde9dfb93637 100644 --- a/x-pack/test/reporting/api/generate/fixtures.ts +++ b/x-pack/test/reporting/api/generate/fixtures.ts @@ -159,26 +159,18 @@ export const CSV_RESULT_NANOS = `date,message,"_id" "2015-01-01T12:10:30","Hello 1", `; -export const JOB_PARAMS_IN_QUERYSTRING = - `(conflictedTypesFields:!(),fields:!(%27@timestamp%27,clientip,extens` + - `ion),indexPatternId:%27logstash-*%27,metaFields:!(_source,_id,_type,_index,_scor` + - `e),searchRequest:(body:(_source:(excludes:!(),includes:!(%27@timestamp%27,client` + - `ip,extension)),docvalue_fields:!(),query:(bool:(filter:!((match_all:()),(range:(` + - `%27@timestamp%27:(gte:%272015-09-20T10:19:40.307Z%27,lt:%272015-09-20T10:26:56.2` + - `21Z%27))),(range:(%27@timestamp%27:(format:strict_date_optional_time,gte:%272004` + - `-09-17T21:19:34.213Z%27,lte:%272019-09-17T21:19:34.213Z%27)))),must:!(),must_not` + - `:!(),should:!())),script_fields:(),sort:!((%27@timestamp%27:(order:desc,unmapped` + - `_type:boolean))),stored_fields:!(%27@timestamp%27,clientip,extension),version:!t` + - `),index:%27logstash-*%27),title:%27A%20Saved%20Search%20With%20a%20DATE%20FILTER` + - `%27,type:search)`; -export const JOB_PARAMS_IN_POSTBODY = - `(conflictedTypesFields:!(),fields:!('@timestamp',clientip,extension),indexPatt` + - `ernId:'logstash-*',metaFields:!(_source,_id,_type,_index,_score),searchRequest:(` + - `body:(_source:(excludes:!(),includes:!('@timestamp',clientip,extension)),docvalu` + - `e_fields:!(),query:(bool:(filter:!((match_all:()),(range:('@timestamp':(gte:'201` + - `5-09-20T10:19:40.307Z',lt:'2015-09-20T10:26:56.221Z'))),(range:('@timestamp':(fo` + - `rmat:strict_date_optional_time,gte:'2004-09-17T21:19:34.213Z',lte:'2019-09-17T21` + - `:19:34.213Z')))),must:!(),must_not:!(),should:!())),script_fields:(),sort:!(('@t` + - `imestamp':(order:desc,unmapped_type:boolean))),stored_fields:!('@timestamp',clie` + - `ntip,extension),version:!t),index:'logstash-*'),title:'A Saved Search With a DAT` + - `E FILTER',type:search)`; +// This concatenates lines of multi-line string into a single line. +// It is so long strings can be entered at short widths, making syntax highlighting easier on editors +function singleLine(literals: TemplateStringsArray): string { + return literals[0].split('\n').join(''); +} + +export const JOB_PARAMS_RISON = singleLine`(conflictedTypesFields:!(),fields:!('@ti +mestamp',clientip,extension),indexPatternId:'logstash-*',metaFields:!(_source,_id,_type,_ +index,_score),searchRequest:(body:(_source:(excludes:!(),includes:!('@timestamp',clientip +,extension)),docvalue_fields:!(),query:(bool:(filter:!((match_all:()),(range:('@timestamp +':(gte:'2015-09-20T10:19:40.307Z',lt:'2015-09-20T10:26:56.221Z'))),(range:('@timestamp':( +format:strict_date_optional_time,gte:'2004-09-17T21:19:34.213Z',lte:'2019-09-17T21:19:34. +213Z')))),must:!(),must_not:!(),should:!())),script_fields:(),sort:!(('@timestamp':(order +:desc,unmapped_type:boolean))),stored_fields:!('@timestamp',clientip,extension),version:! +t),index:'logstash-*'),title:'A Saved Search With a DATE FILTER',type:search)`;