diff --git a/x-pack/platform/plugins/private/reporting/server/routes/common/request_handler/schedule_request_handler.test.ts b/x-pack/platform/plugins/private/reporting/server/routes/common/request_handler/schedule_request_handler.test.ts index 89e68b7939e90..1ceb2ae3df9de 100644 --- a/x-pack/platform/plugins/private/reporting/server/routes/common/request_handler/schedule_request_handler.test.ts +++ b/x-pack/platform/plugins/private/reporting/server/routes/common/request_handler/schedule_request_handler.test.ts @@ -330,6 +330,100 @@ describe('Handle request to schedule', () => { ); }); + test('creates a scheduled_report saved object and rrule dtstart', async () => { + const report = await requestHandler.enqueueJob({ + exportTypeId: 'printablePdfV2', + jobParams: mockJobParams, + schedule: { + rrule: { dtstart: '2025-06-23T14:17:19.765Z', freq: 1, interval: 2, tzid: 'UTC' }, + }, + }); + + const { id, created_at: _created_at, payload, ...snapObj } = report; + expect(snapObj).toMatchInlineSnapshot(` + Object { + "created_by": "testymcgee", + "jobtype": "printable_pdf_v2", + "meta": Object { + "isDeprecated": false, + "layout": "preserve_layout", + "objectType": "cool_object_type", + }, + "migration_version": "unknown", + "notification": undefined, + "schedule": Object { + "rrule": Object { + "dtstart": "2025-06-23T14:17:19.765Z", + "freq": 1, + "interval": 2, + "tzid": "UTC", + }, + }, + } + `); + expect(payload).toMatchInlineSnapshot(` + Object { + "browserTimezone": "UTC", + "isDeprecated": false, + "layout": Object { + "id": "preserve_layout", + }, + "locatorParams": Array [], + "objectType": "cool_object_type", + "title": "cool_title", + "version": "unknown", + } + `); + + expect(auditLogger.log).toHaveBeenCalledWith({ + event: { + action: 'scheduled_report_schedule', + category: ['database'], + outcome: 'unknown', + type: ['creation'], + }, + kibana: { + saved_object: { id: 'mock-report-id', name: 'cool_title', type: 'scheduled_report' }, + }, + message: 'User is creating scheduled report [id=mock-report-id] [name=cool_title]', + }); + + expect(soClient.create).toHaveBeenCalledWith( + 'scheduled_report', + { + jobType: 'printable_pdf_v2', + createdAt: expect.any(String), + createdBy: 'testymcgee', + title: 'cool_title', + enabled: true, + payload: JSON.stringify(payload), + schedule: { + rrule: { + dtstart: '2025-06-23T14:17:19.765Z', + freq: 1, + interval: 2, + tzid: 'UTC', + }, + }, + migrationVersion: 'unknown', + meta: { + objectType: 'cool_object_type', + layout: 'preserve_layout', + isDeprecated: false, + }, + }, + { id: 'mock-report-id' } + ); + + expect(reportingCore.scheduleRecurringTask).toHaveBeenCalledWith(mockRequest, { + id: 'foo', + jobtype: 'printable_pdf_v2', + schedule: { + rrule: { dtstart: '2025-06-23T14:17:19.765Z', freq: 1, interval: 2, tzid: 'UTC' }, + }, + }); + }); + test('throws errors from so client create', async () => { soClient.create = jest.fn().mockImplementationOnce(async () => { throw new Error('SO create error'); @@ -400,6 +494,34 @@ describe('Handle request to schedule', () => { expect(requestHandler.getSchedule()).toEqual({ rrule: { freq: 1, interval: 2 } }); }); + test('parse schedule with dtstart from body', () => { + // @ts-ignore body is a read-only property + mockRequest.body = { + jobParams: rison.encode(mockJobParams), + schedule: { rrule: { dtstart: '2025-06-23T14:17:19.765Z', freq: 1, interval: 2 } }, + }; + expect(requestHandler.getSchedule()).toEqual({ + rrule: { dtstart: '2025-06-23T14:17:19.765Z', freq: 1, interval: 2 }, + }); + }); + + test('handles invalid rrule.dtstart string', () => { + let error: { statusCode: number; body: string } | undefined; + try { + // @ts-ignore body is a read-only property + mockRequest.body = { + jobParams: rison.encode(mockJobParams), + schedule: { rrule: { dtstart: 'i am not a date', freq: 1, interval: 2 } }, + }; + requestHandler.getSchedule(); + } catch (err) { + error = err; + } + + expect(error?.statusCode).toBe(400); + expect(error?.body).toBe('Invalid startedAt date: i am not a date'); + }); + test('handles missing schedule', () => { let error: { statusCode: number; body: string } | undefined; try { diff --git a/x-pack/platform/plugins/private/reporting/server/routes/common/request_handler/schedule_request_handler.ts b/x-pack/platform/plugins/private/reporting/server/routes/common/request_handler/schedule_request_handler.ts index b5db6f062350c..4440342224faa 100644 --- a/x-pack/platform/plugins/private/reporting/server/routes/common/request_handler/schedule_request_handler.ts +++ b/x-pack/platform/plugins/private/reporting/server/routes/common/request_handler/schedule_request_handler.ts @@ -9,7 +9,7 @@ import moment from 'moment'; import { schema } from '@kbn/config-schema'; import { isEmpty, omit } from 'lodash'; -import { RruleSchedule, scheduleRruleSchemaV1 } from '@kbn/task-manager-plugin/server'; +import { RruleSchedule, scheduleRruleSchemaV2 } from '@kbn/task-manager-plugin/server'; import { SavedObjectsUtils } from '@kbn/core/server'; import { IKibanaResponse } from '@kbn/core/server'; import { RawNotification } from '../../../saved_objects/scheduled_report/schemas/latest'; @@ -34,7 +34,7 @@ const MAX_ALLOWED_EMAILS = 30; const validation = { params: schema.object({ exportType: schema.string({ minLength: 2 }) }), body: schema.object({ - schedule: scheduleRruleSchemaV1, + schedule: scheduleRruleSchemaV2, notification: schema.maybe(rawNotificationSchema), jobParams: schema.string(), }), @@ -85,6 +85,13 @@ export class ScheduleRequestHandler extends RequestHandler< }); } + if (rruleDef.dtstart && !moment(rruleDef.dtstart).isValid()) { + throw res.customError({ + statusCode: 400, + body: `Invalid startedAt date: ${rruleDef.dtstart}`, + }); + } + return schedule; } diff --git a/x-pack/platform/plugins/private/reporting/server/routes/common/scheduled/scheduled_query.test.ts b/x-pack/platform/plugins/private/reporting/server/routes/common/scheduled/scheduled_query.test.ts index e553bdee2ecf5..a999c43311e7a 100644 --- a/x-pack/platform/plugins/private/reporting/server/routes/common/scheduled/scheduled_query.test.ts +++ b/x-pack/platform/plugins/private/reporting/server/routes/common/scheduled/scheduled_query.test.ts @@ -1137,6 +1137,85 @@ describe('transformResponse', () => { }); }); + it('should correctly transform the responses with rrule.dtstart field', () => { + expect( + transformResponse( + mockLogger, + { + ...soResponse, + saved_objects: savedObjects.map((so) => ({ + ...so, + attributes: { + ...so.attributes, + schedule: { + ...so.attributes.schedule, + rrule: { + ...so.attributes.schedule.rrule, + dtstart: new Date().toISOString(), + }, + }, + }, + score: 0, + })), + }, + lastRunResponse + ) + ).toEqual({ + page: 1, + per_page: 10, + total: 2, + data: [ + { + id: 'aa8b6fb3-cf61-4903-bce3-eec9ddc823ca', + created_at: '2025-05-06T21:10:17.137Z', + created_by: 'elastic', + enabled: true, + jobtype: 'printable_pdf_v2', + last_run: '2025-05-06T12:00:00.500Z', + next_run: expect.any(String), + payload: jsonPayload, + schedule: { + rrule: { + dtstart: expect.any(String), + freq: 3, + interval: 3, + byhour: [12], + byminute: [0], + tzid: 'UTC', + }, + }, + space_id: 'a-space', + title: '[Logs] Web Traffic', + }, + { + id: '2da1cb75-04c7-4202-a9f0-f8bcce63b0f4', + created_at: '2025-05-06T21:12:06.584Z', + created_by: 'not-elastic', + enabled: true, + jobtype: 'PNGV2', + last_run: '2025-05-06T21:12:07.198Z', + next_run: expect.any(String), + notification: { + email: { + to: ['user@elastic.co'], + }, + }, + payload: jsonPayload, + title: 'Another cool dashboard', + schedule: { + rrule: { + dtstart: expect.any(String), + freq: 1, + interval: 3, + tzid: 'UTC', + }, + }, + space_id: 'a-space', + }, + ], + }); + }); + it('handles malformed payload', () => { const malformedSo = { ...savedObjects[0], diff --git a/x-pack/platform/plugins/private/reporting/server/routes/internal/schedule/integration_tests/scheduling_from_jobparams.test.ts b/x-pack/platform/plugins/private/reporting/server/routes/internal/schedule/integration_tests/scheduling_from_jobparams.test.ts index 8f45317e886f7..62906ac49d956 100644 --- a/x-pack/platform/plugins/private/reporting/server/routes/internal/schedule/integration_tests/scheduling_from_jobparams.test.ts +++ b/x-pack/platform/plugins/private/reporting/server/routes/internal/schedule/integration_tests/scheduling_from_jobparams.test.ts @@ -203,6 +203,28 @@ describe(`POST ${INTERNAL_ROUTES.SCHEDULE_PREFIX}`, () => { ); }); + it('returns 400 on invalid rrule.dtstart date', async () => { + registerScheduleRoutesInternal(reportingCore, mockLogger); + + await server.start(); + + await supertest(httpSetup.server.listener) + .post(`${INTERNAL_ROUTES.SCHEDULE_PREFIX}/printablePdfV2`) + .send({ + jobParams: rison.encode({ browserTimezone: 'America/Amsterdam', title: `abc` }), + schedule: { rrule: { dtstart: '2025-06-23T14:1719.765Z', freq: 1, interval: 2 } }, + }) + .expect(400) + .then(({ body }) => + expect(body.message).toMatchInlineSnapshot(` + "[request body.schedule.rrule]: types that failed validation: + - [request body.schedule.rrule.0.dtstart]: Invalid date: 2025-06-23T14:1719.765Z + - [request body.schedule.rrule.1.freq]: expected value to equal [2] + - [request body.schedule.rrule.2.freq]: expected value to equal [3]" + `) + ); + }); + it('returns 400 on invalid notification list', async () => { registerScheduleRoutesInternal(reportingCore, mockLogger); @@ -333,7 +355,7 @@ describe(`POST ${INTERNAL_ROUTES.SCHEDULE_PREFIX}`, () => { bcc: ['single@email.com'], }, }, - schedule: { rrule: { freq: 1, interval: 2 } }, + schedule: { rrule: { dtstart: '2025-06-23T14:17:19.765Z', freq: 1, interval: 2 } }, }) .expect(200) .then(({ body }) => { @@ -351,7 +373,7 @@ describe(`POST ${INTERNAL_ROUTES.SCHEDULE_PREFIX}`, () => { title: 'abc', version: '7.14.0', }, - schedule: { rrule: { freq: 1, interval: 2 } }, + schedule: { rrule: { dtstart: '2025-06-23T14:17:19.765Z', freq: 1, interval: 2 } }, }, }); }); diff --git a/x-pack/platform/plugins/shared/task_manager/server/lib/get_first_run_at.test.ts b/x-pack/platform/plugins/shared/task_manager/server/lib/get_first_run_at.test.ts index e030aa9c61e8a..a51c7e73a5820 100644 --- a/x-pack/platform/plugins/shared/task_manager/server/lib/get_first_run_at.test.ts +++ b/x-pack/platform/plugins/shared/task_manager/server/lib/get_first_run_at.test.ts @@ -105,6 +105,95 @@ describe('getFirstRunAt', () => { expect(firstRunAtDate).toEqual(new Date('2025-04-16T12:15:00Z')); }); + test('should return the calculated runAt from fixed dtstart when an rrule with fixed time and dtstart is provided', () => { + const taskInstance = { + id: 'id', + params: {}, + state: {}, + taskType: 'report', + schedule: { + rrule: { + dtstart: '2025-06-15T13:01:02Z', + freq: 3, + interval: 1, + tzid: 'UTC', + byhour: [12], + byminute: [15], + }, + }, + }; + const firstRunAt = getFirstRunAt({ taskInstance, logger }); + const firstRunAtDate = new Date(firstRunAt); + // The next day from 2025-06-15 is 2025-06-16 + // The time is set to 12:15 + expect(firstRunAtDate).toEqual(new Date('2025-06-16T12:15:02.000Z')); + }); + + test('should return the calculated runAt from now if using fixed dtstart calculates runAt in the past', () => { + const taskInstance = { + id: 'id', + params: {}, + state: {}, + taskType: 'report', + schedule: { + rrule: { + dtstart: '2025-03-10T13:01:02Z', + freq: 3, + interval: 1, + tzid: 'UTC', + byhour: [12], + byminute: [15], + }, + }, + }; + const firstRunAt = getFirstRunAt({ taskInstance, logger }); + const firstRunAtDate = new Date(firstRunAt); + // The next day from 2025-03-10 is 2025-03-11 which is in the past so the first runAt is set + // based on now which is fixed to '2025-04-15T13:01:02Z' + // The time is set to 12:15 + expect(firstRunAtDate).toEqual(new Date('2025-04-16T12:15:02Z')); + }); + + test('should return the dtstart as the calculated runAt when dtstart is provided with no other fields', () => { + const taskInstance = { + id: 'id', + params: {}, + state: {}, + taskType: 'report', + schedule: { + rrule: { + dtstart: '2025-06-15T13:01:02Z', + freq: 3, + interval: 1, + tzid: 'UTC', + }, + }, + }; + const firstRunAt = getFirstRunAt({ taskInstance, logger }); + const firstRunAtDate = new Date(firstRunAt); + expect(firstRunAtDate).toEqual(new Date('2025-06-15T13:01:02.000Z')); + }); + + test('should return the now as the calculated runAt when dtstart is provided but is in the past', () => { + const taskInstance = { + id: 'id', + params: {}, + state: {}, + taskType: 'report', + schedule: { + rrule: { + dtstart: '2025-03-10T13:01:02Z', + freq: 3, + interval: 1, + tzid: 'UTC', + }, + }, + }; + const firstRunAt = getFirstRunAt({ taskInstance, logger }); + const firstRunAtDate = new Date(firstRunAt); + expect(firstRunAtDate).toEqual(new Date('2025-04-15T13:01:02.000Z')); + }); + test('should return the calculated runAt when an rrule with only byhour is provided', () => { const taskInstance = { id: 'id', diff --git a/x-pack/platform/plugins/shared/task_manager/server/lib/get_first_run_at.ts b/x-pack/platform/plugins/shared/task_manager/server/lib/get_first_run_at.ts index 8d436a2ba1c58..2bccdd8491f61 100644 --- a/x-pack/platform/plugins/shared/task_manager/server/lib/get_first_run_at.ts +++ b/x-pack/platform/plugins/shared/task_manager/server/lib/get_first_run_at.ts @@ -24,11 +24,19 @@ export function getFirstRunAt({ if (taskInstance.schedule?.rrule && rruleHasFixedTime(taskInstance.schedule.rrule)) { try { - const rrule = new RRule({ - ...taskInstance.schedule.rrule, - bysecond: [0], - dtstart: now, - }); + const rrule = taskInstance.schedule.rrule.dtstart + ? new RRule({ + ...taskInstance.schedule.rrule, + // when "dtstart" is provided, we use as-is with no overrides + dtstart: new Date(taskInstance.schedule.rrule.dtstart), + }) + : new RRule({ + ...taskInstance.schedule.rrule, + // when "dtstart" is not provided, we use the current time as the start but + // override the seconds to 0 to ensure the first run is at the start of the minute + dtstart: now, + bysecond: [0], + }); return rrule.after(now)?.toISOString() || nowString; } catch (e) { logger.error(`runAt for the rrule with fixed time could not be calculated: ${e}`); diff --git a/x-pack/platform/plugins/shared/task_manager/server/lib/get_next_run_at.test.ts b/x-pack/platform/plugins/shared/task_manager/server/lib/get_next_run_at.test.ts index 0230320712168..e8a28731e0644 100644 --- a/x-pack/platform/plugins/shared/task_manager/server/lib/get_next_run_at.test.ts +++ b/x-pack/platform/plugins/shared/task_manager/server/lib/get_next_run_at.test.ts @@ -120,6 +120,37 @@ describe('getNextRunAt', () => { jest.useRealTimers(); }); + test('should use now even if dtstart defined in rrule with a fixed time when it is given to calculate the next runAt', () => { + jest.useFakeTimers(); + const now = new Date('2025-04-30T10:00:00.000Z'); + jest.setSystemTime(now); + const testStart = new Date(now.getTime() - 500); + const testRunAt = new Date(now.getTime() - 1000); + const nextRunAt = getNextRunAt( + taskManagerMock.createTask({ + schedule: { + rrule: { + dtstart: '2025-01-15T13:01:02Z', + freq: 3, // Daily + interval: 1, + tzid: 'UTC', + byhour: [12], + byminute: [15], + }, + }, + runAt: testRunAt, + startedAt: testStart, + }), + 0, + mockLogger + ); + + const expectedNextRunAt = new Date('2025-04-30T12:15:59.500Z'); + expect(nextRunAt).toEqual(expectedNextRunAt); + + jest.clearAllTimers(); + }); + test('should use the rrule with a basic interval time when it is given to calculate the next runAt', () => { const now = new Date(); const testStart = now; diff --git a/x-pack/platform/plugins/shared/task_manager/server/task.ts b/x-pack/platform/plugins/shared/task_manager/server/task.ts index a294373e83409..e4fa8af453b1d 100644 --- a/x-pack/platform/plugins/shared/task_manager/server/task.ts +++ b/x-pack/platform/plugins/shared/task_manager/server/task.ts @@ -267,6 +267,7 @@ export interface RruleSchedule { } interface RruleCommon { + dtstart?: string; freq: Frequency; interval: number; tzid: string; diff --git a/x-pack/platform/test/plugin_api_integration/plugins/sample_task_plugin/server/init_routes.ts b/x-pack/platform/test/plugin_api_integration/plugins/sample_task_plugin/server/init_routes.ts index 33e7200f2d210..3050d112035e5 100644 --- a/x-pack/platform/test/plugin_api_integration/plugins/sample_task_plugin/server/init_routes.ts +++ b/x-pack/platform/test/plugin_api_integration/plugins/sample_task_plugin/server/init_routes.ts @@ -46,6 +46,7 @@ const taskSchema = schema.object({ }), schema.object({ rrule: schema.object({ + dtstart: schema.maybe(schema.string()), freq: schema.number(), interval: schema.number(), tzid: schema.string({ defaultValue: 'UTC' }), diff --git a/x-pack/platform/test/plugin_api_integration/test_suites/task_manager/task_management.ts b/x-pack/platform/test/plugin_api_integration/test_suites/task_manager/task_management.ts index ec49129acd6b3..c617ea348820e 100644 --- a/x-pack/platform/test/plugin_api_integration/test_suites/task_manager/task_management.ts +++ b/x-pack/platform/test/plugin_api_integration/test_suites/task_manager/task_management.ts @@ -297,6 +297,52 @@ export default function ({ getService }: FtrProviderContext) { expect((await historyDocs()).length).to.eql(0); }); + it('should schedule a task with rrule with fixed time and dtstart', async () => { + const now = new Date(); + const todayDay = now.getUTCDate(); + const todayMonth = now.getUTCMonth(); + // set a start date for 2 days from now + const startDate = moment(now).add(2, 'days').toDate(); + const dailyTask = await scheduleTask({ + id: 'sample-recurring-task-id', + taskType: 'sampleRecurringTask', + schedule: { + rrule: { + dtstart: startDate.toISOString(), + freq: Frequency.DAILY, + tzid: 'UTC', + interval: 1, + byhour: [15], + byminute: [27], + }, + }, + params: {}, + }); + + await retry.try(async () => { + const task = await currentTask(dailyTask.id); + expect(task.status).to.be('idle'); + const runAt = new Date(task.runAt); + + const runAtDay = runAt.getUTCDate(); + const runAtMonth = runAt.getUTCMonth(); + if (todayMonth === runAtMonth) { + expect(runAtDay >= todayDay + 2).to.be(true); + } else if (todayMonth < runAtMonth) { + log.info(`todayMonth: ${todayMonth}, runAtMonth: ${runAtMonth}`); + } else { + throw new Error( + `Unexpected result: todayMonth:[${todayMonth}] > runAtMonth:[${runAtMonth}]` + ); + } + expect(runAt.getUTCHours()).to.be(15); + expect(runAt.getUTCMinutes()).to.be(27); + }); + + // should not run immediately as the task is scheduled to run at 15:27 UTC + expect((await historyDocs()).length).to.eql(0); + }); + it('should not schedule a task with invalid rrule config', async () => { await supertest .post('/api/sample_tasks/schedule') diff --git a/x-pack/platform/test/reporting_api_integration/reporting_and_security/security_roles_privileges.ts b/x-pack/platform/test/reporting_api_integration/reporting_and_security/security_roles_privileges.ts index 223da44002608..c51536ca3c5a6 100644 --- a/x-pack/platform/test/reporting_api_integration/reporting_and_security/security_roles_privileges.ts +++ b/x-pack/platform/test/reporting_api_integration/reporting_and_security/security_roles_privileges.ts @@ -212,13 +212,34 @@ export default function ({ getService }: FtrProviderContext) { locatorParams: [{ id: 'canvas', version: '7.14.0', params: {} }], objectType: 'dashboard', version: '7.14.0', - } + }, + { rrule: { freq: 1, interval: 1, tzid: 'UTC' } }, + '2025-06-01T13:00:00.000Z' ); expect(res.status).to.eql(200); const soResult = await reportingAPI.getScheduledReports(res.body.job.id); expect(soResult.status).to.eql(200); expect(soResult.body._source.scheduled_report.title).to.eql('test PDF allowed'); + expect(soResult.body._source.scheduled_report.createdBy).to.eql('reporting_user'); + expect(soResult.body._source.scheduled_report.enabled).to.eql(true); + expect(soResult.body._source.scheduled_report.jobType).to.eql('printable_pdf_v2'); + expect(soResult.body._source.scheduled_report.meta).to.eql({ + isDeprecated: false, + layout: 'preserve_layout', + objectType: 'dashboard', + }); + expect(soResult.body._source.scheduled_report.payload).to.eql( + '{"browserTimezone":"UTC","layout":{"id":"preserve_layout"},"objectType":"dashboard","title":"test PDF allowed","version":"7.14.0","locatorParams":[{"id":"canvas","params":{},"version":"7.14.0"}],"isDeprecated":false}' + ); + expect(soResult.body._source.scheduled_report.schedule).to.eql({ + rrule: { + dtstart: '2025-06-01T13:00:00.000Z', + freq: 1, + interval: 1, + tzid: 'UTC', + }, + }); scheduledReportIds.push(res.body.job.id); const taskResult = await reportingAPI.getTask(res.body.job.id); @@ -263,6 +284,24 @@ export default function ({ getService }: FtrProviderContext) { const soResult = await reportingAPI.getScheduledReports(res.body.job.id); expect(soResult.status).to.eql(200); expect(soResult.body._source.scheduled_report.title).to.eql('test PDF allowed'); + expect(soResult.body._source.scheduled_report.createdBy).to.eql('reporting_user'); + expect(soResult.body._source.scheduled_report.enabled).to.eql(true); + expect(soResult.body._source.scheduled_report.jobType).to.eql('printable_pdf_v2'); + expect(soResult.body._source.scheduled_report.meta).to.eql({ + isDeprecated: false, + layout: 'preserve_layout', + objectType: 'visualization', + }); + expect(soResult.body._source.scheduled_report.payload).to.eql( + '{"browserTimezone":"UTC","layout":{"id":"preserve_layout"},"objectType":"visualization","title":"test PDF allowed","version":"7.14.0","locatorParams":[{"id":"canvas","params":{},"version":"7.14.0"}],"isDeprecated":false}' + ); + expect(soResult.body._source.scheduled_report.schedule).to.eql({ + rrule: { + freq: 1, + interval: 1, + tzid: 'UTC', + }, + }); scheduledReportIds.push(res.body.job.id); const taskResult = await reportingAPI.getTask(res.body.job.id); diff --git a/x-pack/platform/test/reporting_api_integration/services/scenarios.ts b/x-pack/platform/test/reporting_api_integration/services/scenarios.ts index 89cc22ec2865e..e932506618df0 100644 --- a/x-pack/platform/test/reporting_api_integration/services/scenarios.ts +++ b/x-pack/platform/test/reporting_api_integration/services/scenarios.ts @@ -203,14 +203,18 @@ export function createScenarios({ getService }: Pick { const jobParams = rison.encode(job); + const scheduleToUse = startedAt + ? { rrule: { ...schedule.rrule, dtstart: startedAt } } + : schedule; return await supertestWithoutAuth .post(`/internal/reporting/schedule/printablePdfV2`) .auth(username, password) .set('kbn-xsrf', 'xxx') - .send({ jobParams, schedule }); + .send({ jobParams, schedule: scheduleToUse }); }; const generatePng = async ( username: string, @@ -230,14 +234,18 @@ export function createScenarios({ getService }: Pick { const jobParams = rison.encode(job); + const scheduleToUse = startedAt + ? { rrule: { ...schedule.rrule, dtstart: startedAt } } + : schedule; return await supertestWithoutAuth .post(`/internal/reporting/schedule/pngV2`) .auth(username, password) .set('kbn-xsrf', 'xxx') - .send({ jobParams, schedule }); + .send({ jobParams, schedule: scheduleToUse }); }; const generateCsv = async ( job: JobParamsCSV, @@ -257,15 +265,18 @@ export function createScenarios({ getService }: Pick { const jobParams = rison.encode(job); - + const scheduleToUse = startedAt + ? { rrule: { ...schedule.rrule, dtstart: startedAt } } + : schedule; return await supertestWithoutAuth .post(`/internal/reporting/schedule/csv_searchsource`) .auth(username, password) .set('kbn-xsrf', 'xxx') - .send({ jobParams, schedule }); + .send({ jobParams, schedule: scheduleToUse }); }; const listScheduledReports = async (