Skip to content

Commit

Permalink
[FEATURE] Add composite monitor type opensearch-project#573
Browse files Browse the repository at this point in the history
Signed-off-by: Jovan Cvetkovic <[email protected]>
  • Loading branch information
jovancvetkovic3006 committed Jun 15, 2023
1 parent c068abd commit 11b5b0d
Show file tree
Hide file tree
Showing 25 changed files with 1,524 additions and 150 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React, { Fragment } from 'react';
import { EuiSpacer, EuiText } from '@elastic/eui';
import MonitorsList from './components/MonitorsList';

const AssociateMonitors = ({ monitors, options, history }) => {
const onUpdate = () => {};

return (
<Fragment>
<EuiText size={'m'} style={{ paddingBottom: '0px', marginBottom: '0px' }}>
<h4>Associate monitors</h4>
</EuiText>
<EuiText color={'subdued'} size={'xs'}>
Associate two or more monitors to run as part of this flow.
</EuiText>

<EuiSpacer size="m" />

<MonitorsList monitors={monitors} options={options} history={history} />
</Fragment>
);
};

export default AssociateMonitors;
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React, { Fragment, useState, useEffect } from 'react';
import * as _ from 'lodash';
import {
EuiButton,
EuiButtonIcon,
EuiFlexGroup,
EuiFlexItem,
EuiSpacer,
EuiText,
EuiToolTip,
} from '@elastic/eui';
import {
FormikComboBox,
FormikInputWrapper,
FormikFormRow,
} from '../../../../../components/FormControls';

const MonitorsList = ({ monitors = [], options = [], history }) => {
const [selectedOptions, setSelectedOptions] = useState({});
const [monitorOptions, setMonitorOptions] = useState([]);
const [monitorFields, setMonitorFields] = useState<number[]>(
_.reduce(
monitors.length ? monitors : [0, 1],
(result, value, key) => {
result.push(key);
return result;
},
[]
)
);

useEffect(() => {
const newOptions = [...options].map((monitor) => ({
label: monitor.monitor_name,
value: monitor.monitor_id,
}));
setMonitorOptions(newOptions);

let newSelected = monitors.length ? monitors : [];
setSelectedOptions(Object.assign({}, newSelected));
}, []);

const onChange = (options, monitorIdx, form) => {
let newSelected = {
...selectedOptions,
};
if (options[0]) {
newSelected[monitorIdx] = options[0];
} else {
delete newSelected[monitorIdx];
}
setSelectedOptions(newSelected);

updateMonitorOptions(newSelected);

onBlur(monitorIdx, form);
};

const updateMonitorOptions = (selected) => {
const newMonitorOptions = [...monitorOptions];
newMonitorOptions.forEach((mon) => {
mon.disabled = isSelected(selected, mon);
});
setMonitorOptions([...newMonitorOptions]);
};

const onBlur = (monitorIdx, form) => {
form.setFieldTouched('associatedMonitors', true);
form.setFieldTouched(`associatedMonitor_${monitorIdx}`, true);

form.setFieldValue('associatedMonitors', Object.values(selectedOptions));
form.setFieldError('associatedMonitors', validate());
};

const isSelected = (selected, monitor) => {
let isSelected = false;
for (const key in selected) {
if (selected.hasOwnProperty(key)) {
if (_.isEqual(selected[key], monitor)) {
isSelected = true;
break;
}
}
}
return isSelected;
};

const onAddMonitor = () => {
let nextIndex = Math.max(...monitorFields) + 1;
const newMonitorFields = [...monitorFields, nextIndex];
setMonitorFields(newMonitorFields);
};

const onRemoveMonitor = (monitorIdx, idx, form) => {
const newSelected = { ...selectedOptions };
delete newSelected[monitorIdx];
setSelectedOptions(newSelected);

const newMonitorFields = [...monitorFields];
newMonitorFields.splice(idx, 1);
setMonitorFields(newMonitorFields);

updateMonitorOptions(newSelected);

onBlur(monitorIdx, form);
};

const isValid = () => Object.keys(selectedOptions).length > 1;
const validate = () => {
if (!isValid()) return 'Required.';
};

return (
<FormikInputWrapper
name={'associatedMonitors'}
fieldProps={{
validate: () => validate(),
}}
render={({ field, form }) => (
<FormikFormRow
name={'associatedMonitors'}
form={form}
rowProps={{
label: 'Monitor',
isInvalid: () => form.touched['associatedMonitors'] && !isValid(),
error: () => validate(),
}}
>
<Fragment>
{monitorFields.map((monitorIdx, idx) => (
<EuiFlexGroup
key={`monitors_list_${monitorIdx}`}
style={{ width: '400px', position: 'relative' }}
>
<EuiFlexItem grow={true}>
<FormikComboBox
name={`associatedMonitor_${monitorIdx}`}
inputProps={{
isInvalid:
form.touched[`associatedMonitor_${monitorIdx}`] &&
form.errors['associatedMonitors'] &&
!selectedOptions[monitorIdx],
placeholder: 'Select a monitor',
onChange: (options, field, form) => onChange(options, monitorIdx, form),
onBlur: (e, field, form) => onBlur(monitorIdx, form),
options: monitorOptions,
singleSelection: { asPlainText: true },
selectedOptions: selectedOptions[monitorIdx]
? [selectedOptions[monitorIdx]]
: undefined,
'data-test-subj': `monitors_list_${monitorIdx}`,
fullWidth: true,
}}
/>
</EuiFlexItem>
{selectedOptions[monitorIdx] && (
<EuiFlexItem
grow={false}
style={{
margin: 0,
padding: 0,
width: '20px',
height: '20px',
position: 'absolute',
right: '-20px',
bottom: '24px',
}}
>
<EuiToolTip title={'View monitor'}>
<EuiButtonIcon
aria-label={'View monitor'}
iconType={'inspect'}
color="text"
target={'_blank'}
href={`alerting#/monitors/${selectedOptions[monitorIdx].value}?alertState=ALL&from=0&monitorIds=${selectedOptions[monitorIdx].value}&search=&severityLevel=ALL&size=20&sortDirection=desc&sortField=start_time`}
/>
</EuiToolTip>
</EuiFlexItem>
)}
{monitorFields.length > 2 && (
<EuiFlexItem
grow={false}
style={{
margin: 0,
padding: 0,
width: '20px',
height: '20px',
position: 'absolute',
right: '-50px',
bottom: '24px',
}}
>
<EuiToolTip title={'Remove monitor'}>
<EuiButtonIcon
aria-label={'Delete selection'}
iconType={'trash'}
color="danger"
onClick={() => onRemoveMonitor(monitorIdx, idx, form)}
/>
</EuiToolTip>
</EuiFlexItem>
)}
</EuiFlexGroup>
))}
<EuiSpacer size={'m'} />
<EuiButton
onClick={() => onAddMonitor()}
disabled={
monitorFields.length >= 10 ||
monitorOptions.length <= Object.keys(selectedOptions).length
}
>
Associate another monitor
</EuiButton>
<EuiText color={'subdued'} size={'xs'}>
You can associate up to {10 - monitorFields.length} more monitors.
</EuiText>
</Fragment>
</FormikFormRow>
)}
/>
);
};

export default MonitorsList;
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import { OPERATORS_MAP } from '../../MonitorExpressions/expressions/utils/constants';

export const DOC_LEVEL_INPUT_FIELD = 'doc_level_input';
export const COMPOSITE_INPUT_FIELD = 'composite_input';

/**
* A list of the operators currently supported for defining queries through the UI.
Expand Down
31 changes: 29 additions & 2 deletions public/pages/CreateMonitor/components/MonitorType/MonitorType.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ const onChangeDefinition = (e, form) => {
form.setFieldValue('searchType', FORMIK_INITIAL_VALUES.searchType);
form.setFieldValue('triggerDefinitions', FORMIK_INITIAL_TRIGGER_VALUES.triggerConditions);
switch (type) {
case MONITOR_TYPE.COMPOSITE_LEVEL:
form.setFieldValue('searchType', SEARCH_TYPE.GRAPH);
break;
case MONITOR_TYPE.CLUSTER_METRICS:
form.setFieldValue('searchType', SEARCH_TYPE.CLUSTER_METRICS);
break;
Expand Down Expand Up @@ -56,9 +59,17 @@ const clusterMetricsDescription = (
</EuiText>
);

const documentLevelDescription = ( // TODO DRAFT: confirm wording
const documentLevelDescription = // TODO DRAFT: confirm wording
(
<EuiText color={'subdued'} size={'xs'} style={{ paddingBottom: '10px', paddingTop: '0px' }}>
Per document monitors allow you to run queries on new documents as they're indexed.
</EuiText>
);

const compositeLevelDescription = (
<EuiText color={'subdued'} size={'xs'} style={{ paddingBottom: '10px', paddingTop: '0px' }}>
Per document monitors allow you to run queries on new documents as they're indexed.
Composite monitors allow you to monitor the states of existing monitors and to reduce alert
noise.
</EuiText>
);

Expand Down Expand Up @@ -128,6 +139,22 @@ const MonitorType = ({ values }) => (
}}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<FormikCheckableCard
name="monitorTypeCompositeLevel"
formRow
rowProps={{ hasEmptyLabelSpace: true }}
inputProps={{
id: 'compositeLevelMonitorRadioCard',
label: 'Composite monitor',
checked: values.monitor_type === MONITOR_TYPE.COMPOSITE_LEVEL,
value: MONITOR_TYPE.COMPOSITE_LEVEL,
onChange: (e, field, form) => onChangeDefinition(e, form),
children: compositeLevelDescription,
'data-test-subj': 'compositeLevelMonitorRadioCard',
}}
/>
</EuiFlexItem>
</EuiFlexGrid>
);

Expand Down
4 changes: 2 additions & 2 deletions public/pages/CreateMonitor/components/Schedule/Schedule.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Interval from './Frequencies/Interval';
const Schedule = ({ isAd }) => (
<Fragment>
<EuiText style={{ marginBottom: '0px' }}>
<h4>Schedule</h4>
<h4>Define workflow schedule</h4>
</EuiText>

{isAd ? (
Expand All @@ -31,7 +31,7 @@ const Schedule = ({ isAd }) => (
) : (
<div>
<Frequency />
<EuiSpacer size="s" />
<EuiSpacer size="m" />
<FrequencyPicker />
</div>
)}
Expand Down
Loading

0 comments on commit 11b5b0d

Please sign in to comment.