diff --git a/thirdeye-ui/src/app/components/alert-view/alert-drawer/alert-drawer.component.tsx b/thirdeye-ui/src/app/components/alert-view/alert-drawer/alert-drawer.component.tsx index 76a9792eae..199487425c 100644 --- a/thirdeye-ui/src/app/components/alert-view/alert-drawer/alert-drawer.component.tsx +++ b/thirdeye-ui/src/app/components/alert-view/alert-drawer/alert-drawer.component.tsx @@ -14,20 +14,25 @@ */ import { Box, + Button, Card, + ClickAwayListener, List, ListItem, ListItemText, + Popper, + PopperProps, Tooltip, Typography, } from "@material-ui/core"; import { ChevronRight } from "@material-ui/icons"; -import { Alert } from "@material-ui/lab"; +import { Alert as MUIAlert } from "@material-ui/lab"; import { useMutation } from "@tanstack/react-query"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate, useSearchParams } from "react-router-dom"; import { + JSONEditorV2, NotificationTypeV1, useNotificationProviderV1, } from "../../../platform/components"; @@ -36,11 +41,15 @@ import { getAlertInsight, rerunAnomalyDetectionForAlert, } from "../../../rest/alerts/alerts.rest"; +import { Alert } from "../../../rest/dto/alert.interfaces"; import { + AppRoute, + AppRouteRelative, createPathWithRecognizedQueryString, getAlertsAllPath, getAlertsCreateCopyPath, getAlertsUpdatePath, + getAlertsUpdatePathWithType, getAnomaliesCreatePath, } from "../../../utils/routes/routes.util"; import { Modal } from "../../modal/modal.component"; @@ -54,12 +63,14 @@ const AlertDrawer: React.FC = ({ alert, onChange, onDetectionRerunSuccess, + handleAlertResetClick, }) => { const { t } = useTranslation(); const navigate = useNavigate(); const [searchParams] = useSearchParams(); const { notify } = useNotificationProviderV1(); const [isModalOpen, setIsModalOpen] = useState(""); + const [isTooltipOpen, setIsTooltipOpen] = useState(""); const { mutateAsync, isLoading, isError } = useMutation({ mutationFn: async (alertId: number) => { @@ -73,6 +84,23 @@ const AlertDrawer: React.FC = ({ }, }); + const CustomPopper = (popperProps: PopperProps): JSX.Element => { + const { anchorEl, open, disablePortal, modifiers, ...other } = + popperProps; + + return ( + setIsTooltipOpen("")}> + + + ); + }; + const handleAlertStateToggle = (): void => { if (!alert || !alert) { return; @@ -91,12 +119,12 @@ const AlertDrawer: React.FC = ({ navigate(getAlertsCreateCopyPath(alert.id)); }; - const handleAlertEdit = (): void => { + const handleAlertEdit = (path: string): void => { if (!alert) { return; } - navigate(getAlertsUpdatePath(alert.id)); + navigate(getAlertsUpdatePathWithType(alert.id, path)); }; const handleCreateAlertAnomaly = (): void => { @@ -159,17 +187,26 @@ const AlertDrawer: React.FC = ({ { id: "alertWizard", label: t("label.alert-wizard"), - onClick: () => handleAlertEdit(), + onClick: () => + handleAlertEdit( + `${AppRoute.ALERTS_UPDATE}/${AppRouteRelative.ALERTS_UPDATE_SIMPLE}/${AppRouteRelative.ALERTS_CREATE_EASY_ALERT}` + ), }, { id: "advancedJsonEditor", label: t("label.advanced-json-editor"), - onClick: () => handleAlertEdit(), + onClick: () => + handleAlertEdit( + AppRoute.ALERTS_UPDATE_JSON_EDITOR_V2 + ), }, { id: "advancedOld", label: t("label.advanced-old"), - onClick: () => handleAlertEdit(), + onClick: () => + handleAlertEdit( + `${AppRoute.ALERTS_UPDATE}/${AppRouteRelative.ALERTS_CREATE_ADVANCED_V2}` + ), }, ], }, @@ -222,8 +259,19 @@ const AlertDrawer: React.FC = ({ onClick: () => setIsModalOpen("viewTaskStatusesForAlert"), }, + { + id: "resetAnomaliesForAlert", + label: t("label.reset-anomalies-for-alert"), + onClick: () => + setIsModalOpen("resetAnomaliesForAlert"), + }, ], }, + { + id: "viewDetectionConfiguration", + label: t("message.view-detection-configuration"), + onClick: () => setIsModalOpen("viewDetectionConfiguration"), + }, ], }, { @@ -245,8 +293,11 @@ const AlertDrawer: React.FC = ({ {optionsList.map((option) => ( = ({ { + subOption.onClick && + subOption.onClick(); + setIsTooltipOpen(""); + }} > = ({ "" ) } + onClose={() => setIsTooltipOpen("")} + onOpen={() => setIsTooltipOpen(option.id)} > - { + if (!option.subOptions) { + option.onClick && option.onClick(); + } + if (isTooltipOpen === option.id) { + setIsTooltipOpen(""); + } else { + setIsTooltipOpen(option.id); + } }} - /> + > + + {option.subOptions && } + - {option.subOptions && } ))} @@ -314,6 +387,43 @@ const AlertDrawer: React.FC = ({ /> + +

{t("message.reset-alert-information")}

+

+ {t("message.reset-alert-confirmation-prompt", { + alertName: alert?.name, + })} +

+
+ { + navigate(getAlertsUpdatePath(alert.id)); + }} + > + {t("label.edit")} + + } + isOpen={isModalOpen === "viewDetectionConfiguration"} + maxWidth="md" + setIsOpen={setIsModalOpen} + title={t("message.view-detection-configuration")} + > + + disableValidation + readOnly + actions={[]} + value={alert} + /> + = ({ onSubmit={handleRerunAlert} > {isError && ( - + {t("message.an-error-was-experienced-while-trying-to")} - + )}

{t( diff --git a/thirdeye-ui/src/app/components/alert-view/alert-drawer/alert-drawer.interfaces.ts b/thirdeye-ui/src/app/components/alert-view/alert-drawer/alert-drawer.interfaces.ts index 2ea5c5c413..fd84c2086a 100644 --- a/thirdeye-ui/src/app/components/alert-view/alert-drawer/alert-drawer.interfaces.ts +++ b/thirdeye-ui/src/app/components/alert-view/alert-drawer/alert-drawer.interfaces.ts @@ -32,4 +32,5 @@ export interface AlertDrawerProps { alert: Alert; onChange: (alert: Alert) => void; onDetectionRerunSuccess: () => void; + handleAlertResetClick: () => void; } diff --git a/thirdeye-ui/src/app/components/alert-view/alert-status/alert-status.component.tsx b/thirdeye-ui/src/app/components/alert-view/alert-status/alert-status.component.tsx index aabc2221ef..656a4a3aa2 100644 --- a/thirdeye-ui/src/app/components/alert-view/alert-status/alert-status.component.tsx +++ b/thirdeye-ui/src/app/components/alert-view/alert-status/alert-status.component.tsx @@ -17,12 +17,20 @@ import CheckCircleOutlineIcon from "@material-ui/icons/CheckCircleOutline"; import HighlightOffIcon from "@material-ui/icons/HighlightOff"; import React, { FunctionComponent } from "react"; import { useTranslation } from "react-i18next"; +import { SkeletonV1 } from "../../../platform/components/skeleton-v1/skeleton-v1.component"; import { AlertStatusProps } from "./alert-status.interfaces"; -export const AlertStatus: FunctionComponent = ({ alert }) => { +export const AlertStatus: FunctionComponent = ({ + alert, + isLoading, +}) => { const theme = useTheme(); const { t } = useTranslation(); + if (isLoading) { + return ; + } + return ( <> {alert?.active && ( diff --git a/thirdeye-ui/src/app/components/alert-view/alert-status/alert-status.interfaces.ts b/thirdeye-ui/src/app/components/alert-view/alert-status/alert-status.interfaces.ts index 255565329a..5ff3457702 100644 --- a/thirdeye-ui/src/app/components/alert-view/alert-status/alert-status.interfaces.ts +++ b/thirdeye-ui/src/app/components/alert-view/alert-status/alert-status.interfaces.ts @@ -16,4 +16,5 @@ import { Alert } from "../../../rest/dto/alert.interfaces"; export interface AlertStatusProps { alert: Alert; + isLoading?: boolean; } diff --git a/thirdeye-ui/src/app/components/alert-view/enumeration-items-table-v2/enumeration-items-table-v2.component.tsx b/thirdeye-ui/src/app/components/alert-view/enumeration-items-table-v2/enumeration-items-table-v2.component.tsx index 4b19d8a31f..67c084b416 100644 --- a/thirdeye-ui/src/app/components/alert-view/enumeration-items-table-v2/enumeration-items-table-v2.component.tsx +++ b/thirdeye-ui/src/app/components/alert-view/enumeration-items-table-v2/enumeration-items-table-v2.component.tsx @@ -17,15 +17,22 @@ import { Card, CardContent, Grid, + IconButton, InputAdornment, TextField, Typography, } from "@material-ui/core"; -import { Search } from "@material-ui/icons"; +import { Close, Search } from "@material-ui/icons"; import ArrowDownwardIcon from "@material-ui/icons/ArrowDownward"; import ArrowUpwardIcon from "@material-ui/icons/ArrowUpward"; -import { sortBy } from "lodash"; -import React, { FunctionComponent, useEffect, useMemo, useState } from "react"; +import { debounce, sortBy } from "lodash"; +import React, { + FunctionComponent, + useCallback, + useEffect, + useMemo, + useState, +} from "react"; import { useTranslation } from "react-i18next"; import { DataGridSortOrderV1 } from "../../../platform/components"; import { filterEnumerationItems } from "../../../utils/enumeration-items/enumeration-items.util"; @@ -64,6 +71,7 @@ export const EnumerationItemsTableV2: FunctionComponent { const classes = useEnumerationItemsTableV2Styles(); + const { t } = useTranslation(); const enumerationItemsWithAnomalies = useMemo(() => { const enumerationIdToObjects: { @@ -114,25 +122,38 @@ export const EnumerationItemsTableV2: FunctionComponent { - onSearchTermChange(term); - if (term === "") { + // Wrap handleSearchClick with useCallback to ensure stable reference + const handleSearchClick = useCallback( + (term: string): void => { + onSearchTermChange(term); + if (term === "") { + setFilteredEnumerationItemsWithAnomalies( + enumerationItemsWithAnomalies + ); + + return; + } + setFilteredEnumerationItemsWithAnomalies( - enumerationItemsWithAnomalies + filterEnumerationItems(enumerationItemsWithAnomalies, term) ); + }, + [enumerationItemsWithAnomalies, onSearchTermChange] + ); - return; - } + // Create debounced function using lodash debounce + const debouncedHandleSearchClick = useMemo( + () => debounce(handleSearchClick, 300), + [handleSearchClick] + ); - setFilteredEnumerationItemsWithAnomalies( - filterEnumerationItems( - enumerationItemsWithAnomalies, - searchTerm - ) - ); - }; + // Cleanup debounce on unmount + useEffect(() => { + return () => { + debouncedHandleSearchClick.cancel(); + }; + }, [debouncedHandleSearchClick]); const handleIsOpenChange = (isOpen: boolean, name: string): void => { let copied = [...expanded]; @@ -245,72 +266,49 @@ export const EnumerationItemsTableV2: FunctionComponent -

{ - e.preventDefault(); - handleSearchClick(searchTerm); - }} - > - - - - - - ), - }} - placeholder={t( - "label.search-entity", - { - entity: t( - "label.dimensions" - ), - } - )} - value={searchTerm} - onChange={(e) => - setSearchTerm(e.target.value) - } - /> - - {filteredEnumerationItemsWithAnomalies.length !== - enumerationsItems.length && ( - - - - - - )} + + + + + + ), + endAdornment: ( + + {searchTerm && ( + { + setSearchTerm( + "" + ); + debouncedHandleSearchClick( + "" + ); + }} + > + + + )} + + ), + }} + placeholder={t("label.search-entity", { + entity: t("label.dimensions"), + })} + value={searchTerm} + onChange={(e) => { + setSearchTerm(e.target.value); + debouncedHandleSearchClick( + e.target.value + ); + }} + /> - +
diff --git a/thirdeye-ui/src/app/components/alert-view/enumeration-items-table-v2/enumeration-items-table-v2.styles.ts b/thirdeye-ui/src/app/components/alert-view/enumeration-items-table-v2/enumeration-items-table-v2.styles.ts index e576232e24..07d9b1d900 100644 --- a/thirdeye-ui/src/app/components/alert-view/enumeration-items-table-v2/enumeration-items-table-v2.styles.ts +++ b/thirdeye-ui/src/app/components/alert-view/enumeration-items-table-v2/enumeration-items-table-v2.styles.ts @@ -33,5 +33,6 @@ export const useEnumerationItemsTableV2Styles = makeStyles((theme) => ({ sortContainer: { paddingLeft: theme.spacing(2), paddingRight: theme.spacing(2), + paddingBottom: theme.spacing(2), }, })); diff --git a/thirdeye-ui/src/app/locale/languages/en-us.json b/thirdeye-ui/src/app/locale/languages/en-us.json index e3743abe5e..1e2d8722fc 100644 --- a/thirdeye-ui/src/app/locale/languages/en-us.json +++ b/thirdeye-ui/src/app/locale/languages/en-us.json @@ -794,7 +794,7 @@ "list-of-all-dimensions-related-to-alert": "List of all dimensions related to this alert for the date range selected", "login-error": "Unable to login", "logout": "You have been logged out", - "looks-like-this-alert-was-just-created-this-page-a": "Looks like this alert was just created. This page automatically refresh when the initial anomalies job is complete", + "looks-like-this-alert-was-just-created-this-page-a": "Looks like this alert was just created. This page automatically refreshes when the initial anomalies job is complete", "manual-filter-for-dimensions-analysis": "Manual filter for dimensions analysis.", "mean-variance-rule-algorithm-description": "Estimate the standard deviation, and consider the standard deviation is caused by noise only.", "metric-from-dataset-moved-along-all-dimensions-no-specific-root": "{{metric}} from {{dataset}} moved along all dimensions. No specific root cause detected.", diff --git a/thirdeye-ui/src/app/pages/alerts-view-page-v2/alerts-view-page-v2.component.tsx b/thirdeye-ui/src/app/pages/alerts-view-page-v2/alerts-view-page-v2.component.tsx index 4b1082b4a2..866f9ef12a 100644 --- a/thirdeye-ui/src/app/pages/alerts-view-page-v2/alerts-view-page-v2.component.tsx +++ b/thirdeye-ui/src/app/pages/alerts-view-page-v2/alerts-view-page-v2.component.tsx @@ -120,6 +120,7 @@ export const AlertsViewPageV2: FunctionComponent = () => { alert: alertThatWasReset, status: resetAlertRequestStatus, errorMessages: resetAlertRequestErrors, + resetAlert, } = useResetAlert(); const getAlertQuery = useFetchQuery({ @@ -336,31 +337,33 @@ export const AlertsViewPageV2: FunctionComponent = () => { setExpanded(newExpanded); }; - const handleAlertChange = (updatedAlert: Alert): void => { + const [isUpdating, setIsUpdating] = useState(false); + + const handleAlertChange = async (updatedAlert: Alert): Promise => { if (!updatedAlert) { return; } - updateAlert(updatedAlert).then( - () => { - notify( - NotificationTypeV1.Success, - t("message.update-success", { entity: t("label.alert") }) - ); - - getAlertQuery.refetch(); - }, - (error) => { - notifyIfErrors( - ActionStatus.Error, - getErrorMessages(error), - notify, - t("message.update-error", { - entity: t("label.alert"), - }) - ); - } - ); + setIsUpdating(true); + try { + await updateAlert(updatedAlert); + notify( + NotificationTypeV1.Success, + t("message.update-success", { entity: t("label.alert") }) + ); + await getAlertQuery.refetch(); + } catch (error) { + notifyIfErrors( + ActionStatus.Error, + getErrorMessages(error as AxiosError), + notify, + t("message.update-error", { + entity: t("label.alert"), + }) + ); + } finally { + setIsUpdating(false); + } }; const handleSearchTermChange = (newTerm: string): void => { @@ -452,6 +455,7 @@ export const AlertsViewPageV2: FunctionComponent = () => { @@ -479,6 +483,19 @@ export const AlertsViewPageV2: FunctionComponent = () => { { + resetAlert(Number(alertId)).then(() => { + getAnomaliesQuery.refetch(); + + searchParams.set( + QUERY_PARAM_KEY_ANOMALIES_RETRY, + "true" + ); + setSearchParams(searchParams, { + replace: true, + }); + }); + }} onChange={handleAlertChange} onDetectionRerunSuccess={ handleAnomalyDetectionRerun diff --git a/thirdeye-ui/src/app/utils/routes/routes.util.ts b/thirdeye-ui/src/app/utils/routes/routes.util.ts index 24a1a8e95b..9d112dfecc 100644 --- a/thirdeye-ui/src/app/utils/routes/routes.util.ts +++ b/thirdeye-ui/src/app/utils/routes/routes.util.ts @@ -155,8 +155,11 @@ export const AppRoute = { ALERTS_ALERT_VIEW: `/${AppRouteRelative.ALERTS}/${AppRouteRelative.ALERTS_ALERT}/${AppRouteRelative.ALERTS_VIEW}`, ALERTS_ALERT_ANOMALIES: `/${AppRouteRelative.ALERTS}/${AppRouteRelative.ALERTS_ALERT}/${AppRouteRelative.ALERTS_ANOMALIES}`, ALERTS_UPDATE: `/${AppRouteRelative.ALERTS}/${AppRouteRelative.ALERTS_ALERT}/${AppRouteRelative.ALERTS_UPDATE}`, + ALERTS_UPDATE_SIMPLE: `/${AppRouteRelative.ALERTS}/${AppRouteRelative.ALERTS_ALERT}/${AppRouteRelative.ALERTS_UPDATE}/${AppRouteRelative.ALERTS_CREATE_EASY_ALERT}`, ALERTS_UPDATE_ADVANCED: `/${AppRouteRelative.ALERTS}/${AppRouteRelative.ALERTS_ALERT}/${AppRouteRelative.ALERTS_UPDATE}/${AppRouteRelative.ALERTS_CREATE_ADVANCED}`, + ALERTS_UPDATE_ADVANCED_V2: `/${AppRouteRelative.ALERTS}/${AppRouteRelative.ALERTS_ALERT}/${AppRouteRelative.ALERTS_UPDATE}/${AppRouteRelative.ALERTS_CREATE_ADVANCED_V2}`, ALERTS_UPDATE_JSON_EDITOR: `/${AppRouteRelative.ALERTS}/${AppRouteRelative.ALERTS_ALERT}/${AppRouteRelative.ALERTS_UPDATE}/${AppRouteRelative.ALERTS_CREATE_JSON_EDITOR}`, + ALERTS_UPDATE_JSON_EDITOR_V2: `/${AppRouteRelative.ALERTS}/${AppRouteRelative.ALERTS_ALERT}/${AppRouteRelative.ALERTS_UPDATE}/${AppRouteRelative.ALERTS_CREATE_JSON_EDITOR_V2}`, ANOMALIES: `/${AppRouteRelative.ANOMALIES}`, ANOMALIES_ALL: `/${AppRouteRelative.ANOMALIES}/${AppRouteRelative.ANOMALIES_ALL}`, ANOMALIES_CREATE: `/${AppRouteRelative.ANOMALIES}/${AppRouteRelative.ANOMALIES_CREATE}`, @@ -329,6 +332,16 @@ export const getAlertsUpdatePath = (id: number): string => { return createPathWithRecognizedQueryString(path); }; +export const getAlertsUpdatePathWithType = ( + id: number, + subPath: string +): string => { + let path: string = subPath; + path = path.replace(PLACEHOLDER_ROUTE_ID, `${id}`); + + return createPathWithRecognizedQueryString(path); +}; + export const getAnomaliesPath = (): string => { return createPathWithRecognizedQueryString(AppRoute.ANOMALIES); };