Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.x] [ML] Daylight saving time calendar events (#193605) #195262

Merged
merged 1 commit into from
Oct 7, 2024
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
3 changes: 3 additions & 0 deletions x-pack/plugins/ml/common/constants/locator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,11 @@ export const ML_PAGES = {
ANOMALY_DETECTION_MODULES_VIEW_OR_CREATE: 'modules/check_view_or_create',
SETTINGS: 'settings',
CALENDARS_MANAGE: 'settings/calendars_list',
CALENDARS_DST_MANAGE: 'settings/calendars_dst_list',
CALENDARS_NEW: 'settings/calendars_list/new_calendar',
CALENDARS_DST_NEW: 'settings/calendars_dst_list/new_calendar',
CALENDARS_EDIT: 'settings/calendars_list/edit_calendar',
CALENDARS_DST_EDIT: 'settings/calendars_dst_list/edit_calendar',
FILTER_LISTS_MANAGE: 'settings/filter_lists',
FILTER_LISTS_NEW: 'settings/filter_lists/new_filter_list',
FILTER_LISTS_EDIT: 'settings/filter_lists/edit_filter_list',
Expand Down
18 changes: 13 additions & 5 deletions x-pack/plugins/ml/common/types/calendars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,24 @@
* 2.0.
*/

export type CalendarId = string;
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';

export interface Calendar {
calendar_id: CalendarId;
export type MlCalendarId = string;

export interface MlCalendar {
calendar_id: MlCalendarId;
description: string;
events: any[];
job_ids: string[];
total_job_count?: number;
}

export interface UpdateCalendar extends Calendar {
calendarId: CalendarId;
export interface UpdateCalendar extends MlCalendar {
calendarId: MlCalendarId;
}

export type MlCalendarEvent = estypes.MlCalendarEvent & {
force_time_shift?: number;
skip_result?: boolean;
skip_model_update?: boolean;
};
11 changes: 11 additions & 0 deletions x-pack/plugins/ml/common/types/locator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ export type MlGenericUrlState = MLPageState<
| typeof ML_PAGES.DATA_FRAME_ANALYTICS_CREATE_JOB
| typeof ML_PAGES.OVERVIEW
| typeof ML_PAGES.CALENDARS_MANAGE
| typeof ML_PAGES.CALENDARS_DST_MANAGE
| typeof ML_PAGES.CALENDARS_NEW
| typeof ML_PAGES.CALENDARS_DST_NEW
| typeof ML_PAGES.FILTER_LISTS_MANAGE
| typeof ML_PAGES.FILTER_LISTS_NEW
| typeof ML_PAGES.SETTINGS
Expand Down Expand Up @@ -247,6 +249,14 @@ export type CalendarEditUrlState = MLPageState<
}
>;

export type CalendarDstEditUrlState = MLPageState<
typeof ML_PAGES.CALENDARS_DST_EDIT,
{
calendarId: string;
globalState?: MlCommonGlobalState;
}
>;

export type FilterEditUrlState = MLPageState<
typeof ML_PAGES.FILTER_LISTS_EDIT,
{
Expand Down Expand Up @@ -277,6 +287,7 @@ export type MlLocatorState =
| DataFrameAnalyticsUrlState
| DataFrameAnalyticsExplorationUrlState
| CalendarEditUrlState
| CalendarDstEditUrlState
| FilterEditUrlState
| MlGenericUrlState
| NotificationsUrlState
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import type { CREATED_BY_LABEL } from '../../../../../../common/constants/new_jo
import { JOB_TYPE, SHARED_RESULTS_INDEX_NAME } from '../../../../../../common/constants/new_job';
import { collectAggs } from './util/general';
import { filterRuntimeMappings } from './util/filter_runtime_mappings';
import type { Calendar } from '../../../../../../common/types/calendars';
import type { MlCalendar } from '../../../../../../common/types/calendars';
import { mlCalendarService } from '../../../../services/calendar_service';
import { getDatafeedAggregations } from '../../../../../../common/util/datafeed_utils';
import { getFirstKeyInObject } from '../../../../../../common/util/object_utils';
Expand All @@ -58,7 +58,7 @@ export class JobCreator {
protected _indexPatternTitle: IndexPatternTitle = '';
protected _indexPatternDisplayName: string = '';
protected _job_config: Job;
protected _calendars: Calendar[];
protected _calendars: MlCalendar[];
protected _datafeed_config: Datafeed;
protected _detectors: Detector[];
protected _influencers: string[];
Expand Down Expand Up @@ -271,11 +271,11 @@ export class JobCreator {
this._job_config.groups = groups;
}

public get calendars(): Calendar[] {
public get calendars(): MlCalendar[] {
return this._calendars;
}

public set calendars(calendars: Calendar[]) {
public set calendars(calendars: MlCalendar[]) {
this._calendars = calendars;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,16 @@ export const AdditionalSection: FC<Props> = ({ additionalExpanded, setAdditional
<CustomUrlsSelection />
</EuiFlexItem>
</EuiFlexGroup>

<EuiSpacer />

<EuiFlexGroup gutterSize="xl" style={{ marginLeft: '0px', marginRight: '0px' }}>
<EuiFlexItem>
<CalendarsSelection />
</EuiFlexItem>
<EuiFlexItem />
<EuiFlexItem>
<CalendarsSelection isDst={true} />
</EuiFlexItem>
</EuiFlexGroup>
</section>
</EuiAccordion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,24 @@ import {
EuiToolTip,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import {
filterCalendarsForDst,
separateCalendarsByType,
} from '../../../../../../../../../settings/calendars/dst_utils';
import { JobCreatorContext } from '../../../../../job_creator_context';
import { Description } from './description';
import { PLUGIN_ID } from '../../../../../../../../../../../common/constants/app';
import type { Calendar } from '../../../../../../../../../../../common/types/calendars';
import type { MlCalendar } from '../../../../../../../../../../../common/types/calendars';
import { useMlApi, useMlKibana } from '../../../../../../../../../contexts/kibana';
import { GLOBAL_CALENDAR } from '../../../../../../../../../../../common/constants/calendars';
import { ML_PAGES } from '../../../../../../../../../../../common/constants/locator';
import { DescriptionDst } from './description_dst';

interface Props {
isDst?: boolean;
}

export const CalendarsSelection: FC = () => {
export const CalendarsSelection: FC<Props> = ({ isDst = false }) => {
const {
services: {
application: { getUrlForApp },
Expand All @@ -37,19 +46,22 @@ export const CalendarsSelection: FC = () => {
const mlApi = useMlApi();

const { jobCreator, jobCreatorUpdate } = useContext(JobCreatorContext);
const [selectedCalendars, setSelectedCalendars] = useState<Calendar[]>(jobCreator.calendars);
const [selectedOptions, setSelectedOptions] = useState<Array<EuiComboBoxOptionOption<Calendar>>>(
[]
const [selectedCalendars, setSelectedCalendars] = useState<MlCalendar[]>(
filterCalendarsForDst(jobCreator.calendars, isDst)
);
const [options, setOptions] = useState<Array<EuiComboBoxOptionOption<Calendar>>>([]);
const [selectedOptions, setSelectedOptions] = useState<
Array<EuiComboBoxOptionOption<MlCalendar>>
>([]);
const [options, setOptions] = useState<Array<EuiComboBoxOptionOption<MlCalendar>>>([]);
const [isLoading, setIsLoading] = useState(false);

async function loadCalendars() {
setIsLoading(true);
const calendars = (await mlApi.calendars()).filter(
const { calendars, calendarsDst } = separateCalendarsByType(await mlApi.calendars());
const filteredCalendars = (isDst ? calendarsDst : calendars).filter(
(c) => c.job_ids.includes(GLOBAL_CALENDAR) === false
);
setOptions(calendars.map((c) => ({ label: c.calendar_id, value: c })));
setOptions(filteredCalendars.map((c) => ({ label: c.calendar_id, value: c })));
setSelectedOptions(selectedCalendars.map((c) => ({ label: c.calendar_id, value: c })));
setIsLoading(false);
}
Expand All @@ -60,12 +72,14 @@ export const CalendarsSelection: FC = () => {
}, []);

useEffect(() => {
jobCreator.calendars = selectedCalendars;
const { calendars, calendarsDst } = separateCalendarsByType(jobCreator.calendars);
const otherCalendars = isDst ? calendars : calendarsDst;
jobCreator.calendars = [...selectedCalendars, ...otherCalendars];
jobCreatorUpdate();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedCalendars.join()]);

const comboBoxProps: EuiComboBoxProps<Calendar> = {
const comboBoxProps: EuiComboBoxProps<MlCalendar> = {
async: true,
options,
selectedOptions,
Expand All @@ -77,11 +91,13 @@ export const CalendarsSelection: FC = () => {
};

const manageCalendarsHref = getUrlForApp(PLUGIN_ID, {
path: ML_PAGES.CALENDARS_MANAGE,
path: isDst ? ML_PAGES.CALENDARS_DST_MANAGE : ML_PAGES.CALENDARS_MANAGE,
});

const Desc = isDst ? DescriptionDst : Description;

return (
<Description>
<Desc>
<EuiFlexGroup gutterSize="xs" alignItems="center">
<EuiFlexItem>
<EuiComboBox {...comboBoxProps} data-test-subj="mlJobWizardComboBoxCalendars" />
Expand Down Expand Up @@ -119,6 +135,6 @@ export const CalendarsSelection: FC = () => {
/>
</EuiLink>
</EuiText>
</Description>
</Desc>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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 type { FC, PropsWithChildren } from 'react';
import React, { memo } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiDescribedFormGroup, EuiFormRow, EuiLink } from '@elastic/eui';
import { useMlKibana } from '../../../../../../../../../contexts/kibana';

export const DescriptionDst: FC<PropsWithChildren<unknown>> = memo(({ children }) => {
const {
services: { docLinks },
} = useMlKibana();
const docsUrl = docLinks.links.ml.calendars;
const title = i18n.translate(
'xpack.ml.newJob.wizard.jobDetailsStep.additionalSection.calendarsDstSelection.title',
{
defaultMessage: 'DST Calendars',
}
);
return (
<EuiDescribedFormGroup
title={<h3>{title}</h3>}
description={
<FormattedMessage
id="xpack.ml.newJob.wizard.jobDetailsStep.additionalSection.calendarsDstSelection.description"
defaultMessage="A list of scheduled events you want to ignore, taking into account daylight saving time shifts. {learnMoreLink}"
values={{
learnMoreLink: (
<EuiLink href={docsUrl} target="_blank">
<FormattedMessage
id="xpack.ml.newJob.wizard.jobDetailsStep.additionalSection.calendarsDstSelection.learnMoreLinkText"
defaultMessage="Learn more"
/>
</EuiLink>
),
}}
/>
}
>
<EuiFormRow>
<>{children}</>
</EuiFormRow>
</EuiDescribedFormGroup>
);
});
9 changes: 9 additions & 0 deletions x-pack/plugins/ml/public/application/routing/breadcrumbs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,14 @@ export const CALENDAR_MANAGEMENT_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
deepLinkId: 'ml:calendarSettings',
});

export const CALENDAR_DST_MANAGEMENT_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
text: i18n.translate('xpack.ml.settings.breadcrumbs.calendarManagementLabel', {
defaultMessage: 'Calendar DST management',
}),
href: '/settings/calendars_dst_list',
deepLinkId: 'ml:calendarSettings',
});

export const FILTER_LISTS_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
text: i18n.translate('xpack.ml.settings.breadcrumbs.filterListsLabel', {
defaultMessage: 'Filter lists',
Expand Down Expand Up @@ -160,6 +168,7 @@ const breadcrumbs = {
CHANGE_POINT_DETECTION,
CREATE_JOB_BREADCRUMB,
CALENDAR_MANAGEMENT_BREADCRUMB,
CALENDAR_DST_MANAGEMENT_BREADCRUMB,
FILTER_LISTS_BREADCRUMB,
SUPPLIED_CONFIGURATIONS,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const calendarListRouteFactory = (
title: i18n.translate('xpack.ml.settings.calendarList.docTitle', {
defaultMessage: 'Calendars',
}),
render: (props, deps) => <PageWrapper {...props} deps={deps} />,
render: (props, deps) => <PageWrapper {...props} deps={deps} isDst={false} />,
breadcrumbs: [
getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
getBreadcrumbWithUrlForApp('ANOMALY_DETECTION_BREADCRUMB', navigateToPath, basePath),
Expand All @@ -40,7 +40,24 @@ export const calendarListRouteFactory = (
],
});

const PageWrapper: FC<PageProps> = () => {
export const calendarDstListRouteFactory = (
navigateToPath: NavigateToPath,
basePath: string
): MlRoute => ({
path: createPath(ML_PAGES.CALENDARS_DST_MANAGE),
title: i18n.translate('xpack.ml.settings.calendarList.docTitle', {
defaultMessage: 'Calendars',
}),
render: (props, deps) => <PageWrapper {...props} deps={deps} isDst={true} />,
breadcrumbs: [
getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
getBreadcrumbWithUrlForApp('ANOMALY_DETECTION_BREADCRUMB', navigateToPath, basePath),
getBreadcrumbWithUrlForApp('SETTINGS_BREADCRUMB', navigateToPath, basePath),
getBreadcrumbWithUrlForApp('CALENDAR_DST_MANAGEMENT_BREADCRUMB'),
],
});

const PageWrapper: FC<PageProps & { isDst: boolean }> = ({ isDst }) => {
const { context } = useRouteResolver('full', ['canGetCalendars'], { getMlNodeCount });

useTimefilter({ timeRangeSelector: false, autoRefreshSelector: false });
Expand All @@ -52,7 +69,7 @@ const PageWrapper: FC<PageProps> = () => {

return (
<PageLoader context={context}>
<CalendarsList {...{ canCreateCalendar, canDeleteCalendar }} />
<CalendarsList {...{ canCreateCalendar, canDeleteCalendar, isDst }} />
</PageLoader>
);
};
Loading