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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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