Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion apps/customer-portal/webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
"preview": "vite preview",
"test": "vitest"
},
"dependencies": {
"@asgardeo/react": "^0.10.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@ describe("usePostCaseClassifications", () => {

const requestBody: CaseClassificationRequest = {
chatHistory: "User: Hello\nAssistant: Hi",
environments: ["Production"],
productDetails: ["WSO2 Identity Server - v6.1.0"],
envProducts: {
Production: ["WSO2 Identity Server - v6.1.0"],
},
region: "",
tier: "Enterprise",
};
Expand All @@ -83,7 +84,7 @@ describe("usePostCaseClassifications", () => {
const mockResponse = {
issueType: "Question",
severityLevel: "S4",
case_info: {
caseInfo: {
description: "desc",
shortDescription: "short",
productName: "WSO2 Identity Server",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,24 +67,23 @@ const EditableStatePlugin = ({ disabled }: { disabled: boolean }) => {
*/
const InitialValuePlugin = ({ initialHtml }: { initialHtml?: string }) => {
const [editor] = useLexicalComposerContext();
const [isFirstRender, setIsFirstRender] = useState(true);

useEffect(() => {
if (isFirstRender && initialHtml) {
if (initialHtml) {
editor.update(() => {
const root = $getRoot();
// Only set initial value if the editor is empty to avoid overwriting user updates
if (root.getTextContent() === "") {
const currentContent = root.getTextContent();

if (currentContent.trim() === "") {
const parser = new DOMParser();
const dom = parser.parseFromString(initialHtml, "text/html");
const nodes = $generateNodesFromDOM(editor, dom);
root.clear();
root.append(...nodes);
}
});
setIsFirstRender(false);
}
}, [editor, initialHtml, isFirstRender]);
}, [editor, initialHtml]);

return null;
};
Expand Down Expand Up @@ -235,7 +234,7 @@ const Editor = ({
backdropFilter: "blur(10px)",
transition: "border-color 0.2s",
"&:hover:not(:focus-within)": {
borderColor: "text.primary",
borderColor: disabled ? "action.disabled" : "text.primary",
},

"&:focus-within": {
Expand All @@ -262,9 +261,8 @@ const Editor = ({
outline: "none",
minHeight:
typeof minHeight === "number" ? `${minHeight}px` : minHeight,
fontSize: oxygenTheme.typography.body2.fontSize,
color: "text.primary",
typography: "body2",
...oxygenTheme.typography.body2,
color: disabled ? "text.disabled" : "text.primary",
},
"& .editor-text-bold": {
fontWeight: oxygenTheme.typography.fontWeightBold || "bold",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// implied. See the License for the specific language governing
// permissions and limitations under the License.

import { render, screen } from "@testing-library/react";
import { render, screen, waitFor } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { ThemeProvider, createTheme } from "@wso2/oxygen-ui";
import Editor from "@components/common/rich-text-editor/Editor";
Expand All @@ -27,10 +27,14 @@ vi.mock("@hooks/useLogger", () => ({
}),
}));

import { ErrorBannerProvider } from "@context/error-banner/ErrorBannerContext";

function renderEditor(props: Parameters<typeof Editor>[0] = {}) {
return render(
<ThemeProvider theme={createTheme()}>
<Editor {...props} />
<ErrorBannerProvider>
<Editor {...props} />
</ErrorBannerProvider>
</ThemeProvider>,
);
}
Expand All @@ -56,4 +60,30 @@ describe("Editor", () => {
renderEditor({ attachments: [file] });
expect(screen.getByText(/Attachments \(1\)/)).toBeInTheDocument();
});

it("updates content when value prop changes from empty", async () => {
const { rerender } = render(
<ThemeProvider theme={createTheme()}>
<ErrorBannerProvider>
<Editor value="" onChange={vi.fn()} />
</ErrorBannerProvider>
</ThemeProvider>,
);

expect(screen.getByTestId("case-description-editor")).toBeInTheDocument();

rerender(
<ThemeProvider theme={createTheme()}>
<ErrorBannerProvider>
<Editor value="<p>AI Generated Content</p>" onChange={vi.fn()} />
</ErrorBannerProvider>
</ThemeProvider>,
);

await waitFor(() => {
expect(screen.getByTestId("case-description-editor")).toHaveTextContent(
"AI Generated Content",
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export default function EscalationBanner({
isLoading ? <CircularProgress size={16} color="inherit" /> : undefined
}
>
{isLoading ? "Creating..." : "Create Case"}
{isLoading ? "Processing" : "Create Case"}
</Button>
</Paper>
);
Expand Down
70 changes: 70 additions & 0 deletions apps/customer-portal/webapp/src/hooks/useAllDeploymentProducts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright (c) 2026 WSO2 LLC. (https://www.wso2.com).
//
// WSO2 LLC. licenses this file to you under the Apache License,
// Version 2.0 (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.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

import { useMemo } from "react";
import { useQueries } from "@tanstack/react-query";
import { useAuthApiClient } from "@context/AuthApiContext";
import { ApiQueryKeys } from "@constants/apiConstants";
import {
fetchDeploymentProducts,
type FetchFn,
} from "@api/useGetDeploymentsProducts";
import type { DeploymentProductItem } from "@models/responses";

interface DeploymentForProducts {
id: string;
name?: string;
type?: { id?: string; label?: string };
}

/**
* Fetches products for all deployments and returns a map of deploymentId -> products.
*
* @param {DeploymentForProducts[] | undefined} projectDeployments - Project deployments.
* @returns {{ productsByDeploymentId: Record<string, DeploymentProductItem[]>; isLoading: boolean }} Map and loading state.
*/
export function useAllDeploymentProducts(
projectDeployments: DeploymentForProducts[] | undefined,
): {
productsByDeploymentId: Record<string, DeploymentProductItem[]>;
isLoading: boolean;
} {
const fetchFn: FetchFn = useAuthApiClient();
const deploymentIds =
projectDeployments?.map((d) => d.id).filter(Boolean) ?? [];

const results = useQueries({
queries: deploymentIds.map((deploymentId) => ({
queryKey: [ApiQueryKeys.DEPLOYMENT_PRODUCTS, deploymentId],
queryFn: () => fetchDeploymentProducts(deploymentId, { fetchFn }),
enabled: !!deploymentId && !!fetchFn,
staleTime: 5 * 60 * 1000,
})),
});

const productsByDeploymentId = useMemo(() => {
const map: Record<string, DeploymentProductItem[]> = {};
deploymentIds.forEach((id, i) => {
const res = results[i];
map[id] = (res?.data ?? []) as DeploymentProductItem[];
});
return map;
}, [results, deploymentIds]);
Comment thread
dileepapeiris marked this conversation as resolved.

const isLoading = results.some((r) => r.isLoading);

return { productsByDeploymentId, isLoading };
}
3 changes: 1 addition & 2 deletions apps/customer-portal/webapp/src/models/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ export interface CaseSearchRequest {
// Request body for case classification.
export interface CaseClassificationRequest {
chatHistory: string;
environments: string[];
productDetails: string[];
envProducts: Record<string, string[]>;
region: string;
tier: string;
}
Expand Down
13 changes: 9 additions & 4 deletions apps/customer-portal/webapp/src/models/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,10 @@ export interface CaseListItem {
title: string;
description: string;
/** API may return string or { id, label? } or { id, name? } object. */
assignedEngineer: string | { id: string; label?: string; name?: string } | null;
assignedEngineer:
| string
| { id: string; label?: string; name?: string }
| null;
project: {
id: string;
label: string;
Expand Down Expand Up @@ -238,8 +241,10 @@ export interface CaseDetails {
product: string | null;
account: CaseDetailsAccount | null;
csManager: string | null;
/** API may return string or { id, label? } or { id, name? } object. */
assignedEngineer: string | { id: string; label?: string; name?: string } | null;
assignedEngineer:
| string
| { id: string; label?: string; name?: string }
| null;
project: CaseDetailsProject | null;
deployment: { id: string; label: string } | null;
deployedProduct: string | null;
Expand Down Expand Up @@ -493,7 +498,7 @@ export type ProductUpdateLevelsResponse = ProductUpdateLevelsItem[];
export interface CaseClassificationResponse {
issueType: string;
severityLevel: string;
case_info: {
caseInfo: {
description: string;
shortDescription: string;
productName: string;
Expand Down
Loading