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

Advanced watch cleanup #34955

Merged
1 change: 0 additions & 1 deletion x-pack/plugins/watcher/common/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,3 @@ export { WATCH_HISTORY } from './watch_history';
export { WATCH_STATES } from './watch_states';
export { WATCH_TYPES } from './watch_types';
export { ERROR_CODES } from './error_codes';
export { WATCH_TABS, WATCH_TAB_ID_EDIT, WATCH_TAB_ID_SIMULATE } from './watch_tabs';
30 changes: 0 additions & 30 deletions x-pack/plugins/watcher/common/constants/watch_tabs.ts

This file was deleted.

12 changes: 6 additions & 6 deletions x-pack/plugins/watcher/common/types/watch_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ export interface ExecutedWatchResults {
startTime: Date;
watchStatus: {
state: string;
actionStatuses: Array<{ state: string; lastExecutionReason: string }>;
actionStatuses: Array<{ state: string; lastExecutionReason: string; id: string }>;
};
}

export interface ExecutedWatchDetails {
triggerData: {
triggeredTime: Date;
scheduledTime: Date;
};
scheduledTimeValue: string | undefined;
scheduledTimeUnit: string;
triggeredTimeValue: string | undefined;
triggeredTimeUnit: string;
ignoreCondition: boolean;
alternativeInput: any;
actionModes: {
Expand All @@ -42,7 +42,7 @@ export interface BaseWatch {
upstreamJson: any;
resetActions: () => void;
createAction: (type: string, actionProps: {}) => void;
validate: () => { warning: { message: string } };
validate: () => { warning: { message: string; title?: string } };
actions: [
{
id: string;
Expand Down
28 changes: 18 additions & 10 deletions x-pack/plugins/watcher/public/components/confirm_watches_modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,23 @@ export const ConfirmWatchesModal = ({
modalOptions,
callback,
}: {
modalOptions: { message: string } | null;
modalOptions: {
title: string;
message: string;
buttonLabel?: string;
buttonType?: 'primary' | 'danger';
} | null;
callback: (isConfirmed?: boolean) => void;
}) => {
if (!modalOptions) {
return null;
}
const { title, message, buttonType, buttonLabel } = modalOptions;
return (
<EuiOverlayMask>
<EuiConfirmModal
buttonColor="danger"
title={i18n.translate('xpack.watcher.sections.watchEdit.json.saveConfirmModal.title', {
defaultMessage: 'Confirm save',
})}
buttonColor={buttonType ? buttonType : 'primary'}
title={title}
onCancel={() => callback()}
onConfirm={() => {
callback(true);
Expand All @@ -32,12 +36,16 @@ export const ConfirmWatchesModal = ({
'xpack.watcher.sections.watchEdit.json.saveConfirmModal.cancelButtonLabel',
{ defaultMessage: 'Cancel' }
)}
confirmButtonText={i18n.translate(
'xpack.watcher.sections.watchEdit.json.saveConfirmModal.saveButtonLabel',
{ defaultMessage: 'Save' }
)}
confirmButtonText={
buttonLabel
? buttonLabel
: i18n.translate(
'xpack.watcher.sections.watchEdit.json.saveConfirmModal.saveButtonLabel',
{ defaultMessage: 'Save watch' }
)
}
>
{modalOptions.message}
{message}
</EuiConfirmModal>
</EuiOverlayMask>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,11 @@ export const documentationLinks = {
putWatchApi: makeDocumentationLink(
'{baseUrl}guide/en/elasticsearch/reference/{urlVersion}/watcher-api-put-watch.html'
),
executeWatchApi: makeDocumentationLink(
'{baseUrl}guide/en/elasticsearch/reference/{urlVersion}/watcher-api-execute-watch.html#watcher-api-execute-watch-action-mode'
),
watchNotificationSettings: makeDocumentationLink(
'{baseUrl}guide/en/elasticsearch/reference/{urlVersion}/notification-settings.html#slack-notification-settings'
),
},
};
26 changes: 23 additions & 3 deletions x-pack/plugins/watcher/public/models/action/slack_action.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';
import { get, isArray } from 'lodash';
import { BaseAction } from './base_action';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiCode, EuiLink } from '@elastic/eui';
import { documentationLinks } from '../../lib/documentation_links';

export class SlackAction extends BaseAction {
constructor(props = {}) {
Expand All @@ -22,10 +26,26 @@ export class SlackAction extends BaseAction {

if (!this.to.length) {
errors.push({
message: i18n.translate('xpack.watcher.sections.watchEdit.json.warningPossibleInvalidSlackAction.description', {
message: <FormattedMessage
id="xpack.watcher.sections.watchEdit.json.warningPossibleInvalidSlackAction.description"
// eslint-disable-next-line max-len
defaultMessage: 'This watch has a Slack action without a "to" property. This watch will only be valid if you specified the "to" property in the Slack "message_default" setting in Elasticsearch.'
})
defaultMessage="If the “to” property for {ymlValue} is already set in your elasticsearch.yml file, you're all set. Otherwise, you can include it here in the watch JSON. {docsLink}"
values={{
ymlValue: (
<EuiCode>message_defaults.to</EuiCode>
),
docsLink: (
<EuiLink href={documentationLinks.watcher.watchNotificationSettings} target="_blank">
{i18n.translate(
'xpack.watcher.sections.watchEdit.json.warningPossibleInvalidSlackAction.documentationLink',
{
defaultMessage: 'Learn more.',
}
)}
</EuiLink>
),
}}
/>
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,47 @@

import { TIME_UNITS } from '../../../common/constants';
import moment from 'moment';
import { i18n } from '@kbn/i18n';

export class ExecuteDetails {
constructor(props = {}) {
this.triggeredTimeValue = props.triggeredTimeValue;
this.triggeredTimeUnit = props.triggeredTimeUnit;
this.scheduledTimeValue = props.scheduledTimeValue;
this.scheduledTimeUnit = props.scheduledTimeUnit;
this.scheduledTime = props.scheduledTime;
this.ignoreCondition = props.ignoreCondition;
this.alternativeInput = props.alternativeInput;
this.alternativeInput = props.alternativeInput || '';
this.actionModes = props.actionModes;
this.recordExecution = props.recordExecution;
}

validate() {
const errors = {
json: [],
};
if (this.alternativeInput || this.alternativeInput !== '') {
try {
const parsedJson = JSON.parse(this.alternativeInput);
if (parsedJson && typeof parsedJson !== 'object') {
errors.json.push(i18n.translate(
'xpack.watcher.sections.watchEdit.simulate.form.alternativeInputFieldError',
{
defaultMessage: 'Invalid JSON',
}
));
}
} catch (e) {
errors.json.push(i18n.translate(
'xpack.watcher.sections.watchEdit.simulate.form.alternativeInputFieldError',
{
defaultMessage: 'Invalid JSON',
}
));
}
}
return errors;
}

formatTime(timeUnit, value) {
let timeValue = moment();
switch (timeUnit) {
Expand All @@ -42,18 +69,17 @@ export class ExecuteDetails {
get upstreamJson() {
const hasTriggerTime = this.triggeredTimeValue !== '';
const hasScheduleTime = this.scheduledTimeValue !== '';
const formattedTriggerTime = hasTriggerTime ? this.formatTime(this.triggeredTimeUnit, this.triggeredTimeValue) : undefined;
const formattedScheduleTime = hasScheduleTime ? this.formatTime(this.scheduledTimeUnit, this.scheduledTimeValue) : undefined;
const triggerData = {
triggeredTime: formattedTriggerTime,
scheduledTime: formattedScheduleTime,
};
const triggeredTime = hasTriggerTime ? this.formatTime(this.triggeredTimeUnit, this.triggeredTimeValue) : undefined;
const scheduledTime = hasScheduleTime ? this.formatTime(this.scheduledTimeUnit, this.scheduledTimeValue) : undefined;
return {
triggerData,
triggerData: {
triggeredTime,
scheduledTime,
},
ignoreCondition: this.ignoreCondition,
alternativeInput: this.alternativeInput,
alternativeInput: this.alternativeInput !== '' ? JSON.parse(this.alternativeInput) : undefined,
actionModes: this.actionModes,
recordExecution: this.recordExecution
recordExecution: this.recordExecution,
};
}
}
3 changes: 1 addition & 2 deletions x-pack/plugins/watcher/public/models/watch/base_watch.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,9 @@ export class BaseWatch {
* @param {array} props.actions Action definitions
*/
constructor(props = {}) {
this.id = get(props, 'id', '');
this.id = get(props, 'id');
this.type = get(props, 'type');
this.isNew = get(props, 'isNew', true);

this.name = get(props, 'name');
this.isSystemWatch = Boolean(get(props, 'isSystemWatch'));
this.watchStatus = WatchStatus.fromUpstreamJson(get(props, 'watchStatus'));
Expand Down
67 changes: 64 additions & 3 deletions x-pack/plugins/watcher/public/models/watch/json_watch.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import { get } from 'lodash';
import { BaseWatch } from './base_watch';
import { WATCH_TYPES } from 'plugins/watcher/../common/constants';
import { ACTION_TYPES, WATCH_TYPES } from 'plugins/watcher/../common/constants';
import defaultWatchJson from './default_watch.json';
import { i18n } from '@kbn/i18n';

Expand All @@ -17,16 +17,77 @@ export class JsonWatch extends BaseWatch {
constructor(props = {}) {
props.type = WATCH_TYPES.JSON;
super(props);
const existingWatch = get(props, 'watch');
this.watch = existingWatch ? existingWatch : defaultWatchJson;
this.watchString = get(props, 'watchString', JSON.stringify(existingWatch ? existingWatch : defaultWatchJson, null, 2));
}

this.watch = get(props, 'watch', defaultWatchJson);
validate() {
const validationResult = super.validate();
const idRegex = /^[A-Za-z0-9\-\_]+$/;
const errors = {
id: [],
json: [],
};
validationResult.errors = errors;
// Watch id validation
if (!this.id) {
errors.id.push(
i18n.translate('xpack.watcher.sections.watchEdit.json.error.requiredIdText', {
defaultMessage: 'ID is required',
})
);
} else if (!idRegex.test(this.id)) {
errors.id.push(i18n.translate('xpack.watcher.sections.watchEdit.json.error.invalidIdText', {
defaultMessage: 'ID can only contain letters, underscores, dashes, and numbers.',
}));
}
// JSON validation
if (!this.watchString || this.watchString === '') {
errors.json.push(i18n.translate('xpack.watcher.sections.watchEdit.json.error.requiredJsonText', {
defaultMessage: 'JSON is required',
}));
} else {
try {
const parsedJson = JSON.parse(this.watchString);
if (parsedJson && typeof parsedJson === 'object') {
const { actions } = parsedJson;
if (actions) {
// Validate if the action(s) provided is one of the supported actions
const invalidActions = Object.keys(actions).find(actionKey => {
const actionKeys = Object.keys(actions[actionKey]);
let type;
Object.keys(ACTION_TYPES).forEach(actionTypeKey => {
if (actionKeys.includes(ACTION_TYPES[actionTypeKey]) && !actionKeys.includes(ACTION_TYPES.UNKNOWN)) {
type = ACTION_TYPES[actionTypeKey];
}
});
return !type;
});
if (invalidActions) {
errors.json.push(i18n.translate('xpack.watcher.sections.watchEdit.json.error.invalidActionType', {
defaultMessage: 'Unknown action type provided for action "{action}".',
values: {
action: invalidActions,
},
}));
}
}
}
} catch (e) {
errors.json.push(i18n.translate('xpack.watcher.sections.watchEdit.json.error.invalidJsonText', {
defaultMessage: 'Invalid JSON',
}));
}
}
return validationResult;
}

get upstreamJson() {
const result = super.upstreamJson;
Object.assign(result, {
watch: this.watch
});

return result;
}

Expand Down
Loading