From 22d1decd8becc43bdbcbb1d40014d5769bf72bab Mon Sep 17 00:00:00 2001 From: nalin-patidar Date: Fri, 8 Nov 2024 18:23:22 +0530 Subject: [PATCH] feat(ui): TE-2448 added namespace config tab in configuration page (#1604) * feat(ui): TE-2448 added namespace config tab in configuration page * added update and reset actions * handle disabled state * show full json * tab name chaged to Settings * fixes --------- Co-authored-by: Nalin Patidar --- .../configuration-page-header.component.tsx | 5 + .../src/app/locale/languages/en-us.json | 2 + .../namespace-configuration/api/index.tsx | 141 ++++++++++++++++++ .../pages/namespace-configuration/index.tsx | 91 +++++++++++ .../src/app/rest/dto/workspace.interfaces.ts | 15 ++ .../app/rest/workspace/workspace-action.ts | 79 +++++++++- .../app/rest/workspace/workspace.interface.ts | 23 ++- .../src/app/rest/workspace/workspace.rest.ts | 24 ++- .../configuration/configuration.router.tsx | 16 ++ .../src/app/utils/routes/routes.util.ts | 6 + 10 files changed, 397 insertions(+), 5 deletions(-) create mode 100644 thirdeye-ui/src/app/pages/namespace-configuration/api/index.tsx create mode 100644 thirdeye-ui/src/app/pages/namespace-configuration/index.tsx diff --git a/thirdeye-ui/src/app/components/configuration-page-header/configuration-page-header.component.tsx b/thirdeye-ui/src/app/components/configuration-page-header/configuration-page-header.component.tsx index 0f9d4d8fce..2a5bd6b90d 100644 --- a/thirdeye-ui/src/app/components/configuration-page-header/configuration-page-header.component.tsx +++ b/thirdeye-ui/src/app/components/configuration-page-header/configuration-page-header.component.tsx @@ -21,6 +21,7 @@ import { getEventsAllPath, getMetricsPath, getSubscriptionGroupsPath, + getNamespaceConfigPath, } from "../../utils/routes/routes.util"; import { PageHeader } from "../page-header/page-header.component"; import { ConfigurationPageHeaderProps } from "./configuration-page-header.interfaces"; @@ -57,6 +58,10 @@ export const ConfigurationPageHeader: FunctionComponent void; + getWorkspaceConfiguration: () => Promise< + WorkspaceConfiguration | undefined + >; + resetWorkspaceConfiguration: () => Promise< + WorkspaceConfiguration | undefined + >; + updateWorkspaceConfiguration: ( + config: WorkspaceConfiguration + ) => Promise; +}; + +export const useWorkspaceApiRequests = (): WorkspaceApiReques => { + const [namespaceConfig, setNamespaceConfig] = + useState(null); + + const { notify } = useNotificationProviderV1(); + const { t } = useTranslation(); + + const { + workspaceConfiguration, + getWorkspaceConfiguration, + status: workspaceStatus, + errorMessages: workspaceErrorMessages, + } = useGetWorkspaceConfiguration(); + + const { + resetWorkspaceConfiguration, + status: resetStaus, + errorMessages: resetErrorMessages, + } = useResetWorkspaceConfiguration(); + + const { + updateWorkspaceConfiguration, + status: updateStatus, + errorMessages: updateErrorMessages, + } = useUpdateWorkspaceConfiguration(); + + useEffect(() => { + getWorkspaceConfiguration(); + }, []); + + useEffect(() => { + if (!isEmpty(workspaceConfiguration)) { + setNamespaceConfig(workspaceConfiguration); + } + }, [workspaceConfiguration]); + + useEffect(() => { + notifyIfErrors( + workspaceStatus, + workspaceErrorMessages, + notify, + t("message.error-while-fetching", { + entity: t("label.settings"), + }) + ); + }, [workspaceStatus]); + + useEffect(() => { + if (resetStaus === ActionStatus.Done) { + getWorkspaceConfiguration(); + } + notifyIfErrors( + resetStaus, + resetErrorMessages, + notify, + t("message.update-error", { + entity: t("label.settings"), + }) + ); + }, [resetStaus]); + + useEffect(() => { + if (updateStatus === ActionStatus.Done) { + getWorkspaceConfiguration(); + } + notifyIfErrors( + updateStatus, + updateErrorMessages, + notify, + t("message.update-error", { + entity: t("label.settings"), + }) + ); + }, [updateStatus]); + + const isError = workspaceStatus === ActionStatus.Error; + + const isLoading = + workspaceStatus === ActionStatus.Working || + updateStatus === ActionStatus.Working || + resetStaus === ActionStatus.Working; + + const isUpdateDisabled = isEqual(namespaceConfig, workspaceConfiguration); + + return { + isError, + isLoading, + isUpdateDisabled, + namespaceConfig, + workspaceConfiguration, + setNamespaceConfig, + getWorkspaceConfiguration, + resetWorkspaceConfiguration, + updateWorkspaceConfiguration, + }; +}; diff --git a/thirdeye-ui/src/app/pages/namespace-configuration/index.tsx b/thirdeye-ui/src/app/pages/namespace-configuration/index.tsx new file mode 100644 index 0000000000..221e389337 --- /dev/null +++ b/thirdeye-ui/src/app/pages/namespace-configuration/index.tsx @@ -0,0 +1,91 @@ +/* + * Copyright 2024 StarTree Inc + * + * Licensed under the StarTree Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at http://www.startree.ai/legal/startree-community-license + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OF ANY KIND, + * either express or implied. + * + * See the License for the specific language governing permissions and limitations under + * the License. + */ +import React from "react"; +import { ConfigurationPageHeader } from "../../components/configuration-page-header/configuration-page-header.component"; +import { + JSONEditorV1, + PageContentsGridV1, + PageV1, +} from "../../platform/components"; +import { LoadingErrorStateSwitch } from "../../components/page-states/loading-error-state-switch/loading-error-state-switch.component"; + +import { Box, Button, Grid } from "@material-ui/core"; +import { WorkspaceConfiguration } from "../../rest/dto/workspace.interfaces"; +import { useWorkspaceApiRequests } from "./api"; + +export const NamespaceConfiguration = (): JSX.Element => { + const { + isError, + isLoading, + isUpdateDisabled, + namespaceConfig, + setNamespaceConfig, + resetWorkspaceConfiguration, + updateWorkspaceConfiguration, + } = useWorkspaceApiRequests(); + + const handleNamespaceConfigChange = (value: string): void => { + setNamespaceConfig(JSON.parse(value)); + }; + const handleReset = (): void => { + resetWorkspaceConfiguration(); + }; + const handleUpdate = async (): Promise => { + updateWorkspaceConfiguration(namespaceConfig as WorkspaceConfiguration); + }; + + return ( + + + + + + + + hideValidationSuccessIcon + value={namespaceConfig} + onChange={handleNamespaceConfigChange} + /> + + + + + + + + + + + + ); +}; diff --git a/thirdeye-ui/src/app/rest/dto/workspace.interfaces.ts b/thirdeye-ui/src/app/rest/dto/workspace.interfaces.ts index 2fdf1f4258..1718024dcd 100644 --- a/thirdeye-ui/src/app/rest/dto/workspace.interfaces.ts +++ b/thirdeye-ui/src/app/rest/dto/workspace.interfaces.ts @@ -15,3 +15,18 @@ export interface Workspace { id: string | null; } + +export interface WorkspaceConfiguration { + id: number; + auth: { + namespace: string | null; + }; + timeConfiguration: { + timezone: string; + dateTimePattern: string; + minimumOnboardingStartTime: number; + }; + templateConfiguration: { + sqlLimitStatement: number; + }; +} diff --git a/thirdeye-ui/src/app/rest/workspace/workspace-action.ts b/thirdeye-ui/src/app/rest/workspace/workspace-action.ts index 1cb86ede67..014dc27d67 100644 --- a/thirdeye-ui/src/app/rest/workspace/workspace-action.ts +++ b/thirdeye-ui/src/app/rest/workspace/workspace-action.ts @@ -13,9 +13,19 @@ * the License. */ import { useHTTPAction } from "../create-rest-action"; -import { Workspace } from "../dto/workspace.interfaces"; -import { GetWorkspaces } from "./workspace.interface"; -import { getWorkspaces as getWorkspacesREST } from "./workspace.rest"; +import { Workspace, WorkspaceConfiguration } from "../dto/workspace.interfaces"; +import { + GetWorkspaceConfiguration, + GetWorkspaces, + ResetWorkspaceConfiguration, + UpdateWorkspaceConfiguration, +} from "./workspace.interface"; +import { + getWorkspaces as getWorkspacesREST, + getWorkspaceConfiguration as getWorkspaceConfigurationREST, + updateWorkspaceConfiguration as updateWorkspaceConfigurationREST, + resetWorkspaceConfiguration as resetWorkspaceConfigurationREST, +} from "./workspace.rest"; export const useGetWorkspaces = (): GetWorkspaces => { const { data, makeRequest, status, errorMessages, resetData } = @@ -33,3 +43,66 @@ export const useGetWorkspaces = (): GetWorkspaces => { resetData, }; }; + +export const useGetWorkspaceConfiguration = (): GetWorkspaceConfiguration => { + const { data, makeRequest, status, errorMessages, resetData } = + useHTTPAction(getWorkspaceConfigurationREST); + + const getWorkspaceConfiguration = (): Promise< + WorkspaceConfiguration | undefined + > => { + return makeRequest(); + }; + + return { + workspaceConfiguration: data, + getWorkspaceConfiguration, + status, + errorMessages, + resetData, + }; +}; + +export const useUpdateWorkspaceConfiguration = + (): UpdateWorkspaceConfiguration => { + const { data, makeRequest, status, errorMessages, resetData } = + useHTTPAction( + updateWorkspaceConfigurationREST + ); + + const updateWorkspaceConfiguration = ( + config: WorkspaceConfiguration + ): Promise => { + return makeRequest(config); + }; + + return { + workspaceConfiguration: data, + updateWorkspaceConfiguration, + status, + errorMessages, + resetData, + }; + }; + +export const useResetWorkspaceConfiguration = + (): ResetWorkspaceConfiguration => { + const { data, makeRequest, status, errorMessages, resetData } = + useHTTPAction( + resetWorkspaceConfigurationREST + ); + + const resetWorkspaceConfiguration = (): Promise< + WorkspaceConfiguration | undefined + > => { + return makeRequest(); + }; + + return { + workspaceConfiguration: data, + resetWorkspaceConfiguration, + status, + errorMessages, + resetData, + }; + }; diff --git a/thirdeye-ui/src/app/rest/workspace/workspace.interface.ts b/thirdeye-ui/src/app/rest/workspace/workspace.interface.ts index 11cf83a33f..b65d600dce 100644 --- a/thirdeye-ui/src/app/rest/workspace/workspace.interface.ts +++ b/thirdeye-ui/src/app/rest/workspace/workspace.interface.ts @@ -13,9 +13,30 @@ * the License. */ import { ActionHook } from "../actions.interfaces"; -import { Workspace } from "../dto/workspace.interfaces"; +import { Workspace, WorkspaceConfiguration } from "../dto/workspace.interfaces"; export interface GetWorkspaces extends ActionHook { workspaces: Workspace[] | null; getWorkspaces: () => Promise; } + +export interface GetWorkspaceConfiguration extends ActionHook { + workspaceConfiguration: WorkspaceConfiguration | null; + getWorkspaceConfiguration: () => Promise< + WorkspaceConfiguration | undefined + >; +} + +export interface UpdateWorkspaceConfiguration extends ActionHook { + workspaceConfiguration: WorkspaceConfiguration | null; + updateWorkspaceConfiguration: ( + config: WorkspaceConfiguration + ) => Promise; +} + +export interface ResetWorkspaceConfiguration extends ActionHook { + workspaceConfiguration: WorkspaceConfiguration | null; + resetWorkspaceConfiguration: () => Promise< + WorkspaceConfiguration | undefined + >; +} diff --git a/thirdeye-ui/src/app/rest/workspace/workspace.rest.ts b/thirdeye-ui/src/app/rest/workspace/workspace.rest.ts index da7f45a710..dd17971640 100644 --- a/thirdeye-ui/src/app/rest/workspace/workspace.rest.ts +++ b/thirdeye-ui/src/app/rest/workspace/workspace.rest.ts @@ -13,7 +13,7 @@ * the License. */ import axios from "axios"; -import { Workspace } from "../dto/workspace.interfaces"; +import { Workspace, WorkspaceConfiguration } from "../dto/workspace.interfaces"; const BASE_URL_WORKSPACES = "/api/workspaces"; @@ -22,3 +22,25 @@ export const getWorkspaces = async (): Promise => { return response.data; }; + +export const getWorkspaceConfiguration = + async (): Promise => { + const response = await axios.get("/api/workspace-configuration"); + + return response.data; + }; + +export const resetWorkspaceConfiguration = + async (): Promise => { + const response = await axios.post("/api/workspace-configuration/reset"); + + return response.data; + }; + +export const updateWorkspaceConfiguration = async ( + config: WorkspaceConfiguration +): Promise => { + const response = await axios.put("/api/workspace-configuration", config); + + return response.data; +}; diff --git a/thirdeye-ui/src/app/routers/configuration/configuration.router.tsx b/thirdeye-ui/src/app/routers/configuration/configuration.router.tsx index 062a442817..7cf3eba53d 100644 --- a/thirdeye-ui/src/app/routers/configuration/configuration.router.tsx +++ b/thirdeye-ui/src/app/routers/configuration/configuration.router.tsx @@ -54,6 +54,12 @@ const EventsRouter = lazy(() => ).then((module) => ({ default: module.EventRouter })) ); +const NamespaceConfiguration = lazy(() => + import( + /* webpackChunkName: "metrics-router" */ "../../pages/namespace-configuration" + ).then((module) => ({ default: module.NamespaceConfiguration })) +); + const PageNotFoundPage = lazy(() => import( /* webpackChunkName: "page-not-found-page" */ "../../pages/page-not-found-page/page-not-found-page.component" @@ -142,6 +148,16 @@ export const ConfigurationRouter: FunctionComponent = () => { } path={`${AppRouteRelative.EVENTS}/*`} /> + + + + } + path={`${AppRouteRelative.NAMESPACE}/*`} + /> {/* No match found, render page not found */} } path="*" /> diff --git a/thirdeye-ui/src/app/utils/routes/routes.util.ts b/thirdeye-ui/src/app/utils/routes/routes.util.ts index b3bcdc8244..24a1a8e95b 100644 --- a/thirdeye-ui/src/app/utils/routes/routes.util.ts +++ b/thirdeye-ui/src/app/utils/routes/routes.util.ts @@ -96,6 +96,7 @@ export const AppRouteRelative = { METRICS_VIEW: `view/id/${PLACEHOLDER_ROUTE_ID}`, METRICS_CREATE: `create`, METRICS_UPDATE: `update/id/${PLACEHOLDER_ROUTE_ID}`, + NAMESPACE: "namespace", ROOT_CAUSE_ANALYSIS: `root-cause-analysis`, ROOT_CAUSE_ANALYSIS_FOR_ANOMALY: `anomaly/${PLACEHOLDER_ROUTE_ID}`, ROOT_CAUSE_ANALYSIS_FOR_ANOMALY_V2: `v2/anomaly/${PLACEHOLDER_ROUTE_ID}`, @@ -234,6 +235,7 @@ export const AppRoute = { `/${AppRouteRelative.WELCOME}/${AppRouteRelative.WELCOME_CREATE_ALERT}` + `/${AppRouteRelative.WELCOME_CREATE_ALERT_TUNE_ALERT}`, IMPACT_DASHBOARD: `/${AppRouteRelative.IMPACT_DASHBOARD}`, + NAMESPACE_CONFIGURATION: `/${AppRouteRelative.CONFIGURATION}/${AppRouteRelative.NAMESPACE}`, } as const; export const getBasePath = (): string => { @@ -574,6 +576,10 @@ export const getEventsPath = (): string => { return createPathWithRecognizedQueryString(AppRoute.EVENTS); }; +export const getNamespaceConfigPath = (): string => { + return AppRoute.NAMESPACE_CONFIGURATION; +}; + export const getEventsAllPath = (searchTerm?: string): string => { const urlQuery = getRecognizedQuery();