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

Adding functionality to associate existing detector with a visualization #484

Merged
Merged
Show file tree
Hide file tree
Changes from 4 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 @@ -6,7 +6,7 @@ import React, { useState } from 'react';
import { get } from 'lodash';
import AssociatedDetectors from '../AssociatedDetectors/containers/AssociatedDetectors';
import { getEmbeddable } from '../../../../public/services';
import AddAnomalyDetector from '../CreateAnomalyDetector/AddAnomalyDetector';
import AddAnomalyDetector from '../CreateAnomalyDetector';

const AnywhereParentFlyout = ({ startingFlyout, ...props }) => {
const embeddable = getEmbeddable().getEmbeddableFactory;
Expand All @@ -15,11 +15,12 @@ const AnywhereParentFlyout = ({ startingFlyout, ...props }) => {
];

const [mode, setMode] = useState(startingFlyout);
const [selectedDetectorId, setSelectedDetectorId] = useState();
const [selectedDetector, setSelectedDetector] = useState(undefined);

const AnywhereFlyout = {
create: AddAnomalyDetector,
associated: AssociatedDetectors,
existing: AddAnomalyDetector,
}[mode];

return (
Expand All @@ -29,8 +30,8 @@ const AnywhereParentFlyout = ({ startingFlyout, ...props }) => {
setMode,
mode,
indices,
selectedDetectorId,
setSelectedDetectorId,
selectedDetector,
setSelectedDetector,
}}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,11 +239,6 @@ function AssociatedDetectors({ embeddable, closeFlyout, setMode }) {
dispatch(getDetectorList(GET_ALL_DETECTORS_QUERY_PARAMS));
};

// TODO: this part is incomplete because it is pending on a different PR that will have all the associate existing changes
const openAssociateDetectorFlyout = async () => {
console.log('inside create anomaly detector');
};

const handleUnlinkDetectorAction = (detector: DetectorListItem) => {
setDetectorToUnlink(detector);
setConfirmModalState({
Expand Down Expand Up @@ -326,7 +321,7 @@ function AssociatedDetectors({ embeddable, closeFlyout, setMode }) {
fill
iconType="link"
onClick={() => {
openAssociateDetectorFlyout();
setMode('existing');
}}
>
Associate a detector
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,19 +80,34 @@ import { FeatureAccordion } from '../../../../public/pages/ConfigureModel/compon
import {
AD_DOCS_LINK,
AD_HIGH_CARDINALITY_LINK,
ALERTING_PLUGIN_NAME,
DEFAULT_SHINGLE_SIZE,
MAX_FEATURE_NUM,
} from '../../../../public/utils/constants';
import { getNotifications } from '../../../../public/services';
import { getNotifications, getUiActions } from '../../../../public/services';
import { prettifyErrorMessage } from '../../../../server/utils/helpers';
import {
ORIGIN_PLUGIN_VIS_LAYER,
OVERLAY_ANOMALIES,
VIS_LAYER_PLUGIN_TYPE,
} from '../../../../public/expressions/constants';
import { formikToDetectorName, visFeatureListToFormik } from './helpers';
import { AssociateExisting } from './AssociateExisting';
import { mountReactNode } from '../../../../../../src/core/public/utils';
import { CoreServicesContext } from '../../../../public/components/CoreServices/CoreServices';
import { CoreStart } from '../../../../../../src/core/public';

function AddAnomalyDetector({ embeddable, closeFlyout, mode, setMode }) {
function AddAnomalyDetector({
embeddable,
closeFlyout,
mode,
setMode,
selectedDetector,
setSelectedDetector,
}) {
const dispatch = useDispatch();
const core = React.useContext(CoreServicesContext) as CoreStart;
amitgalitz marked this conversation as resolved.
Show resolved Hide resolved

const [queryText, setQueryText] = useState('');
useEffect(() => {
const getInitialIndices = async () => {
Expand Down Expand Up @@ -148,21 +163,43 @@ function AddAnomalyDetector({ embeddable, closeFlyout, mode, setMode }) {
}
};

const handleSubmit = (formikProps) => {
const getAugmentVisSavedObject = (detectorId: string) => {
const fn = {
type: VisLayerTypes.PointInTimeEvents,
name: OVERLAY_ANOMALIES,
args: {
detectorId: detectorId,
},
} as VisLayerExpressionFn;

const pluginResource = {
type: VIS_LAYER_PLUGIN_TYPE,
id: detectorId,
} as ISavedPluginResource;

return {
title: embeddable.vis.title,
originPlugin: ORIGIN_PLUGIN_VIS_LAYER,
pluginResource: pluginResource,
visId: embeddable.vis.id,
visLayerExpressionFn: fn,
} as ISavedAugmentVis;
ohltyler marked this conversation as resolved.
Show resolved Hide resolved
};

//DONE 1. if detector is created succesfully, started succesfully and associated succesfully and alerting exists -> show end message with alerting button
//DONE 2. If detector is created succesfully, started succesfully and associated succesfully and alerting doesn't exist -> show end message with OUT alerting button
//DONE 3. If detector is created succesfully, started succesfully and fails association -> show one toast with detector created and started succesfully and one toast with failed association
//DONE 4. If detector is created succesfully, fails starting and fails association -> show one toast with detector created succesfully, one toast with failed association
//DONE 5. If detector is created successfully, fails starting and fails associating -> show one toast with detector created succesfully, one toast with fail starting, one toast with failed association
//DONE 6. If detector fails creating -> show one toast with detector failed creating
const handleSubmit = async (formikProps) => {
formikProps.setSubmitting(true);
try {
const detectorToCreate = formikToDetector(formikProps.values);
dispatch(createDetector(detectorToCreate))
.then(async (response) => {
notifications.toasts.addSuccess(
`Detector created: ${formikProps.values.name}`
);
dispatch(startDetector(response.response.id))
.then((startDetectorResponse) => {
notifications.toasts.addSuccess(
`Successfully started the real-time detector`
);
})
.then((startDetectorResponse) => {})
.catch((err: any) => {
notifications.toasts.addDanger(
prettifyErrorMessage(
Expand All @@ -174,34 +211,54 @@ function AddAnomalyDetector({ embeddable, closeFlyout, mode, setMode }) {
);
});

const fn = {
type: VisLayerTypes.PointInTimeEvents,
name: OVERLAY_ANOMALIES,
args: {
detectorId: response.response.id,
},
} as VisLayerExpressionFn;

const pluginResource = {
type: VIS_LAYER_PLUGIN_TYPE,
id: response.response.id,
} as ISavedPluginResource;

const savedObjectToCreate = {
title: embeddable.vis.title,
originPlugin: ORIGIN_PLUGIN_VIS_LAYER,
pluginResource: pluginResource,
visId: embeddable.vis.id,
savedObjectType: 'visualization',
visLayerExpressionFn: fn,
} as ISavedAugmentVis;

// TODO: catch saved object failure
const savedObject = await createAugmentVisSavedObject(
savedObjectToCreate
);

const saveObjectResponse = await savedObject.save({});
const detectorId = response.response.id;

const augmentVisSavedObjectToCreate: ISavedAugmentVis =
getAugmentVisSavedObject(detectorId);

createAugmentVisSavedObject(augmentVisSavedObjectToCreate)
.then((savedObject: any) => {
savedObject
.save({})
.then((response: any) => {
const shingleSize = get(
formikProps.values,
'shingleSize',
DEFAULT_SHINGLE_SIZE
);
const detectorId = get(savedObject, 'pluginResource.id', '');
notifications.toasts.addSuccess({
title: `The ${formikProps.values.name} is associated with the ${title} visualization`,
text: mountReactNode(
getEverythingSuccesfulButton(detectorId, shingleSize)
),
});
closeFlyout();
})
.catch((error) => {
console.error(
`Error associating selected detector4: ${error}`
amitgalitz marked this conversation as resolved.
Show resolved Hide resolved
);
notifications.toasts.addDanger(
prettifyErrorMessage(
`Error associating selected detector in save process: ${error}`
)
);
notifications.toasts.addSuccess(
`Detector created: ${formikProps.values.name}`
);
});
})
.catch((error) => {
notifications.toasts.addDanger(
prettifyErrorMessage(
`Error associating selected detector3 in crate process: ${error}`
amitgalitz marked this conversation as resolved.
Show resolved Hide resolved
)
);
notifications.toasts.addSuccess(
`Detector created: ${formikProps.values.name}`
);
});
})
.catch((err: any) => {
dispatch(getDetectorCount()).then((response: any) => {
Expand Down Expand Up @@ -231,6 +288,74 @@ function AddAnomalyDetector({ embeddable, closeFlyout, mode, setMode }) {
}
};

const getEverythingSuccesfulButton = (detectorId, shingleSize) => {
amitgalitz marked this conversation as resolved.
Show resolved Hide resolved
return (
<EuiText>
<p>
`Attempting to initialize the detector with historical data. This
initializing process takes approximately 1 minute if you have data in
each of the last ${32 + shingleSize} consecutive intervals.`
</p>
{alertingExists(detectorId) ? (
<EuiFlexItem>
<EuiButton onClick={() => openAlerting(detectorId)}>
Set Up Alerts
</EuiButton>{' '}
<p>set up alerts</p>
</EuiFlexItem>
) : null}
</EuiText>
);
};

const alertingExists = (detectorId) => {
try {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have a setup alert button on existing AD detector overview page, is this how we currently check if alerting exist? also what does ALERTING_TRIGGER_AD_ID mean here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We actually don't have this logic on the setup alert button in AD overview page. If there is no alerting plugin, the button still shows and it just sends to a broken page.
ALERTING_TRIGGER_AD_ID is the trigger that David set up on the alerting code to open up the relevant flyout. If the alerting trigger doesn't exist then it means there is no alerting plugin or potentially something else going on but regardless it means the flyout for creating a monitor on top of a detector wont be opened

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We actually don't have this logic on the setup alert button in AD overview page. If there is no alerting plugin, the button still shows and it just sends to a broken page.

I believe we used to have a check here. Regardless, this is bad UX. Can you open an issue on that. We can probably keep it broad, like "clean up how to detect alerting plugin is installed"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened an issue #493

const uiActionService = getUiActions();
uiActionService.getTrigger('ALERTING_TRIGGER_AD_ID');
return true;
} catch (e) {
console.error('No allerting trigger exists', e);
amitgalitz marked this conversation as resolved.
Show resolved Hide resolved
return false;
}
};

const openAlerting = (detectorId) => {
const uiActionService = getUiActions();
uiActionService
.getTrigger('ALERTING_TRIGGER_AD_ID')
.exec({ embeddable, detectorId });
};

const handleAssociate = async (detector: DetectorListItem) => {
const augmentVisSavedObjectToCreate: ISavedAugmentVis =
getAugmentVisSavedObject(detector.id);

createAugmentVisSavedObject(augmentVisSavedObjectToCreate)
.then((savedObject: any) => {
savedObject
.save({})
.then((response: any) => {
notifications.toasts.addSuccess({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here's a way to make the toast wider:

  1. Add the following style in the styles.scss file
.euiGlobalToastList {
  width: 500px;
}

.successToast {
  width: 450px;
  position: relative;
  right: 25px;
}
  1. add this line inside the notifictions.toasts.addSuccess...
    className: "successToast",

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2023-05-25 at 3 31 22 PM

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, made the fix

title: `The ${detector.name} is associated with the ${title} visualization`,
text: "The detector's anomalies do not appear on the visualization. Refresh your dashboard to update the visualization",
});
closeFlyout();
})
.catch((error) => {
notifications.toasts.addDanger(
prettifyErrorMessage(
`Error associating selected detector: ${error}`
)
);
});
})
.catch((error) => {
notifications.toasts.addDanger(
prettifyErrorMessage(`Error associating selected detector: ${error}`)
);
});
};

const validateVisDetectorName = async (detectorName: string) => {
if (isEmpty(detectorName)) {
return 'Detector name cannot be empty';
Expand Down Expand Up @@ -325,6 +450,13 @@ function AddAnomalyDetector({ embeddable, closeFlyout, mode, setMode }) {
))}
</EuiFormFieldset>
<EuiSpacer size="m" />
{mode === 'existing' && (
<AssociateExisting
embeddableVisId={embeddable.vis.id}
selectedDetector={selectedDetector}
setSelectedDetector={setSelectedDetector}
></AssociateExisting>
)}
{mode === 'create' && (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the conditional on line 830, we can do the same here for these 2 exclusive scenarios.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mode can also be 'associated'

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the difference between existing and associated? I'm thinking if we can have these in constants with details about each scenario it would be helpful.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

existing is the one with the button to associate an existing detector flyout and associated is the flyout with the list of all currently associated detectors with ability to unlink. I can add some comments and constants, will do that in my final commit after fixing the dependency issue

<div className="create-new">
<EuiText size="xs">
Expand Down Expand Up @@ -695,16 +827,27 @@ function AddAnomalyDetector({ embeddable, closeFlyout, mode, setMode }) {
<EuiButtonEmpty onClick={closeFlyout}>Cancel</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
fill={true}
data-test-subj="adAnywhereCreateDetectorButton"
isLoading={formikProps.isSubmitting}
onClick={() => {
handleValidationAndSubmit(formikProps);
}}
>
Create Detector
</EuiButton>
{mode === 'existing' ? (
<EuiButton
fill={true}
data-test-subj="adAnywhereCreateDetectorButton"
isLoading={formikProps.isSubmitting}
onClick={() => handleAssociate(selectedDetector)}
>
associate detector
amitgalitz marked this conversation as resolved.
Show resolved Hide resolved
</EuiButton>
) : (
<EuiButton
fill={true}
data-test-subj="adAnywhereCreateDetectorButton"
isLoading={formikProps.isSubmitting}
onClick={() => {
handleValidationAndSubmit(formikProps);
}}
>
create detector
amitgalitz marked this conversation as resolved.
Show resolved Hide resolved
</EuiButton>
)}
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlyoutFooter>
Expand Down
Loading