Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
277ef56
initial commit
js-jankisalvi Apr 23, 2025
5d355a3
unit tests
js-jankisalvi Apr 25, 2025
6ec0681
Merge branch 'main' into recreate-maintenance-window-events
js-jankisalvi Apr 25, 2025
a652beb
don't update expiration date when no new events
js-jankisalvi Apr 29, 2025
e7c3c4c
Merge branch 'main' into recreate-maintenance-window-events
js-jankisalvi Apr 29, 2025
c5333af
add to registered task types test
js-jankisalvi Apr 29, 2025
2e804f9
Merge branch 'recreate-maintenance-window-events' of https://github.c…
js-jankisalvi Apr 29, 2025
e07b290
Merge branch 'main' into recreate-maintenance-window-events
js-jankisalvi May 2, 2025
1193b7d
feedback addressed 1
js-jankisalvi May 6, 2025
a3f3a33
Merge branch 'main' into recreate-maintenance-window-events
js-jankisalvi May 6, 2025
2ed9c94
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine May 6, 2025
1bec795
Merge branch 'main' into recreate-maintenance-window-events
js-jankisalvi May 19, 2025
b039796
Feedback 2
js-jankisalvi May 19, 2025
e5de478
Merge branch 'main' into recreate-maintenance-window-events
js-jankisalvi May 27, 2025
efba636
Merge branch 'main' into recreate-maintenance-window-events
js-jankisalvi May 30, 2025
df8de37
New feedback 1
js-jankisalvi Jun 3, 2025
7816859
Merge remote-tracking branch 'upstream' into recreate-maintenance-win…
js-jankisalvi Jun 3, 2025
3ea7753
[CI] Auto-commit changed files from 'node scripts/capture_oas_snapsho…
kibanamachine Jun 3, 2025
c6aee26
[CI] Auto-commit changed files from 'make api-docs'
kibanamachine Jun 3, 2025
87ab475
added functional test
js-jankisalvi Jun 6, 2025
70d2639
Merge branch 'main' into recreate-maintenance-window-events
js-jankisalvi Jun 6, 2025
9b2d19e
fix type
js-jankisalvi Jun 9, 2025
7a808d4
Merge branch 'main' into recreate-maintenance-window-events
js-jankisalvi Jun 9, 2025
c59b0be
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Jun 9, 2025
f13a003
lint fix for test file
js-jankisalvi Jun 9, 2025
d2be24d
Merge remote-tracking branch 'upstream' into recreate-maintenance-win…
js-jankisalvi Jun 10, 2025
01e94e4
Merge remote-tracking branch 'upstream' into recreate-maintenance-win…
js-jankisalvi Jun 13, 2025
930aa8b
update cancel task and add unit tests
js-jankisalvi Jun 16, 2025
b3bf14d
Merge branch 'main' into recreate-maintenance-window-events
js-jankisalvi Jun 16, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,28 @@ describe('generateMaintenanceWindowEvents', () => {
expect(result[result.length - 1].gte).toEqual('2023-03-30T00:00:00.000Z');
expect(result[result.length - 1].lte).toEqual('2023-03-30T01:00:00.000Z');
});

it('should generate events starting with start date', () => {
const result = generateMaintenanceWindowEvents({
duration: 1 * 60 * 60 * 1000,
expirationDate: moment(new Date('2023-02-27T00:00:00.000Z'))
.tz('UTC')
.add(5, 'weeks')
.toISOString(),
rRule: {
tzid: 'UTC',
freq: Frequency.WEEKLY,
interval: 1,
byweekday: ['WE', 'TU', 'TH'],
dtstart: '2023-01-27T00:00:00.000Z',
},
startDate: '2023-03-01T00:00:00.000Z',
});

expect(result[0].lte).toEqual('2023-03-01T01:00:00.000Z'); // events started after start date
expect(result[0].gte).toEqual('2023-03-01T00:00:00.000Z');

expect(result[result.length - 1].lte).toEqual('2023-03-30T01:00:00.000Z'); // events ended before expiration date
expect(result[result.length - 1].gte).toEqual('2023-03-30T00:00:00.000Z');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,32 @@ export interface GenerateMaintenanceWindowEventsParams {
rRule: RRuleParams;
expirationDate: string;
duration: number;
startDate?: string;
}

export const generateMaintenanceWindowEvents = ({
rRule,
expirationDate,
duration,
startDate,
}: GenerateMaintenanceWindowEventsParams) => {
const { dtstart, until, wkst, byweekday, ...rest } = rRule;

const startDate = new Date(dtstart);
const rRuleStartDate = new Date(dtstart);
const endDate = new Date(expirationDate);

const rRuleOptions = {
...rest,
dtstart: startDate,
dtstart: rRuleStartDate,
until: until ? new Date(until) : null,
wkst: wkst ? Weekday[wkst] : null,
byweekday: byweekday ?? null,
};

try {
const recurrenceRule = new RRule(rRuleOptions);
const occurrenceDates = recurrenceRule.between(startDate, endDate);
const eventStartDate = startDate ? new Date(startDate) : rRuleStartDate;
const occurrenceDates = recurrenceRule.between(eventStartDate, endDate);

return occurrenceDates.map((date) => {
return {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { Frequency } from '@kbn/rrule';
import { getMaintenanceWindowExpirationDate } from './get_maintenance_window_expiration_date';

describe('getMaintenanceWindowExpirationDate', () => {
beforeAll(() => {
jest.useFakeTimers().setSystemTime(new Date('2023-03-25T00:30:00.000Z'));
});

afterAll(() => {
jest.clearAllTimers();
jest.useRealTimers();
});

it('should return +1 year expiration date', () => {
const result = getMaintenanceWindowExpirationDate({
rRule: {
tzid: 'UTC',
freq: Frequency.WEEKLY,
byweekday: ['MO', 'FR'],
interval: 1,
dtstart: '2023-03-25T01:00:00.000Z',
},
duration: 1 * 60 * 60 * 1000,
});
expect(result).toEqual('2024-03-25T00:30:00.000Z');
});

it('should return expiration date based on duration', () => {
const result = getMaintenanceWindowExpirationDate({
rRule: {
tzid: 'UTC',
dtstart: '2023-03-25T09:00:00.000Z',
},
duration: 1 * 60 * 60 * 1000,
});
expect(result).toEqual('2023-03-25T10:00:00.000Z');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import moment from 'moment';
import type { RRuleParams } from '../../../../common';

// Returns a date in ISO format one year in the future if the rule is recurring or until the end of the MW if it is not recurring.
export const getMaintenanceWindowExpirationDate = ({
rRule,
duration,
}: {
rRule: RRuleParams;
duration: number;
}): string => {
let expirationDate;
if (rRule.interval || rRule.freq) {
expirationDate = moment().utc().add(1, 'year').toISOString();
} else {
expirationDate = moment(rRule.dtstart).utc().add(duration, 'ms').toISOString();
}

return expirationDate;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { MaintenanceWindowStatus } from '../../../../common';

export const getMaintenanceWindowStatus = () => {
return {
[MaintenanceWindowStatus.Running]: '(maintenance-window.attributes.events: "now")',
[MaintenanceWindowStatus.Upcoming]:
'(not maintenance-window.attributes.events: "now" and maintenance-window.attributes.events > "now")',
[MaintenanceWindowStatus.Finished]:
'(not maintenance-window.attributes.events >= "now" and maintenance-window.attributes.expirationDate >"now")',
[MaintenanceWindowStatus.Archived]: '(maintenance-window.attributes.expirationDate < "now")',
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ export {
getMaintenanceWindowDateAndStatus,
findRecentEventWithStatus,
} from './get_maintenance_window_date_and_status';

export { getMaintenanceWindowExpirationDate } from './get_maintenance_window_expiration_date';
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
* 2.0.
*/

import moment from 'moment';
import Boom from '@hapi/boom';
import { SavedObjectsUtils } from '@kbn/core/server';
import type { Filter } from '@kbn/es-query';
Expand All @@ -22,6 +21,7 @@ import {
} from '../../transforms';
import { createMaintenanceWindowSo } from '../../../../data/maintenance_window';
import { createMaintenanceWindowParamsSchema } from './schemas';
import { getMaintenanceWindowExpirationDate } from '../../lib';

export async function createMaintenanceWindow(
context: MaintenanceWindowClientContext,
Expand Down Expand Up @@ -65,7 +65,12 @@ export async function createMaintenanceWindow(
}

const id = SavedObjectsUtils.generateId();
const expirationDate = moment().utc().add(1, 'year').toISOString();

const expirationDate = getMaintenanceWindowExpirationDate({
rRule,
duration,
});

const modificationMetadata = await getModificationMetadata();

const events = generateMaintenanceWindowEvents({ rRule, expirationDate, duration });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import Boom from '@hapi/boom';
import type { KueryNode } from '@kbn/es-query';
import { fromKueryExpression } from '@kbn/es-query';
import type { MaintenanceWindowClientContext } from '../../../../../common';
import { MaintenanceWindowStatus } from '../../../../../common';
import { transformMaintenanceWindowAttributesToMaintenanceWindow } from '../../transforms';
import { findMaintenanceWindowSo } from '../../../../data/maintenance_window';
import type {
Expand All @@ -18,23 +17,16 @@ import type {
MaintenanceWindowsStatus,
} from './types';
import { findMaintenanceWindowsParamsSchema } from './schemas';
import { getMaintenanceWindowStatus } from '../../lib/get_maintenance_window_status';

export const getStatusFilter = (
status?: MaintenanceWindowsStatus[]
): KueryNode | string | undefined => {
if (!status || status.length === 0) return undefined;

const statusToQueryMapping = {
[MaintenanceWindowStatus.Running]: '(maintenance-window.attributes.events: "now")',
[MaintenanceWindowStatus.Upcoming]:
'(not maintenance-window.attributes.events: "now" and maintenance-window.attributes.events > "now")',
[MaintenanceWindowStatus.Finished]:
'(not maintenance-window.attributes.events >= "now" and maintenance-window.attributes.expirationDate >"now")',
[MaintenanceWindowStatus.Archived]: '(maintenance-window.attributes.expirationDate < "now")',
};
const mwStatusQuery = getMaintenanceWindowStatus();

const fullQuery = status
.map((value) => statusToQueryMapping[value])
.map((value) => mwStatusQuery[value])
.filter(Boolean)
.join(' or ');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import {
generateMaintenanceWindowEvents,
shouldRegenerateEvents,
mergeEvents,
} from '../../lib/generate_maintenance_window_events';
getMaintenanceWindowExpirationDate,
} from '../../lib';
import { retryIfConflicts } from '../../../../lib/retry_if_conflicts';
import {
transformMaintenanceWindowAttributesToMaintenanceWindow,
Expand Down Expand Up @@ -98,7 +99,11 @@ async function updateWithOCC(
throw Boom.badRequest('Cannot edit archived maintenance windows');
}

const expirationDate = moment.utc().add(1, 'year').toISOString();
const expirationDate = getMaintenanceWindowExpirationDate({
rRule: maintenanceWindow.rRule,
duration: maintenanceWindow.duration,
});

const modificationMetadata = await getModificationMetadata();

let events = generateMaintenanceWindowEvents({
Expand Down
Loading