(frontend) open the messages widget from the help menu

Some instances use the messages widget for support instead of a plain
mailto link. A new supportMessagesWidget flag in the help menu config
lets the "Contact us" entry open the widget, taking precedence over
the support email when both are configured.
This commit is contained in:
Nathan Vasse
2026-07-27 16:35:39 +02:00
parent 635135d1ed
commit 9120c819ec
3 changed files with 92 additions and 5 deletions
@@ -222,6 +222,7 @@ export type ApiConfig = {
legalNoticeUrl?: string;
};
supportEmail?: string;
supportMessagesWidget?: boolean;
};
FRONTEND_THEME?: string;
FRONTEND_HIDE_GAUFRE?: boolean;
@@ -43,6 +43,7 @@ import i18n from "@/features/i18n/initI18n";
import { useMemo } from "react";
import { UserProfile } from "@/features/ui/components/user/UserProfile";
import { Gaufre } from "@/features/ui/components/gaufre/Gaufre";
import { useMessagesWidget } from "@/features/feedback/useMessagesWidget";
export const getGlobalExplorerLayout = (page: React.ReactElement) => {
return <GlobalExplorerLayout>{page}</GlobalExplorerLayout>;
@@ -199,20 +200,26 @@ const HelpMenuButton = () => {
const helpMenuConfig = config?.FRONTEND_HELP_MENU_CONFIG;
const hasHelpMenu =
!!helpMenuConfig && Object.keys(helpMenuConfig).length > 0;
const { showWidget } = useMessagesWidget();
if (!hasHelpMenu) {
return null;
}
const getOnContactUs = () => {
if (helpMenuConfig.supportMessagesWidget) {
return () => showWidget();
}
return helpMenuConfig.supportEmail
? () => window.open(helpMenuConfig.supportEmail)
: undefined;
};
return (
<HelpMenu
documentationUrl={helpMenuConfig.documentationUrl}
legal={helpMenuConfig.legal}
onContactUs={
helpMenuConfig.supportEmail
? () => window.open(helpMenuConfig.supportEmail)
: undefined
}
onContactUs={getOnContactUs()}
/>
);
};
@@ -12,6 +12,7 @@ const HELP_MENU_CONFIG = {
const overrideHelpMenuConfig = async (
page: import("@playwright/test").Page,
helpMenuConfig: unknown,
extraConfig: Record<string, unknown> = {},
) => {
await page.route("**/api/v1.0/config/", async (route) => {
const response = await route.fetch();
@@ -21,10 +22,25 @@ const overrideHelpMenuConfig = async (
} else {
json.FRONTEND_HELP_MENU_CONFIG = helpMenuConfig;
}
Object.assign(json, extraConfig);
await route.fulfill({ response, json });
});
};
// Records window.open calls so that mailto links can be asserted without
// relying on the browser actually opening them.
const spyOnWindowOpen = async (page: import("@playwright/test").Page) => {
await page.addInitScript(() => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any).__openedUrls = [];
window.open = (url?: string | URL) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any).__openedUrls.push(String(url));
return null;
};
});
};
test.describe("Help menu", () => {
test("renders the help menu with the configured options", async ({
page,
@@ -60,6 +76,69 @@ test.describe("Help menu", () => {
).toHaveCount(0);
});
test("opens the support email when clicking on contact us", async ({
page,
}) => {
await overrideHelpMenuConfig(page, HELP_MENU_CONFIG);
await spyOnWindowOpen(page);
await login(page, "drive@example.com");
await page.goto("/");
const footer = page.locator(".c__left-panel__footer__drive");
await footer.getByRole("button", { name: "Help" }).click();
await page.getByRole("menuitem", { name: "Contact us" }).click();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const openedUrls = await page.evaluate(() => (window as any).__openedUrls);
expect(openedUrls).toEqual([HELP_MENU_CONFIG.supportEmail]);
});
test("opens the messages widget when clicking on contact us", async ({
page,
}) => {
const widgetPath = "https://widget.example.com/";
await overrideHelpMenuConfig(
page,
{
...HELP_MENU_CONFIG,
supportMessagesWidget: true,
},
{
FRONTEND_FEEDBACK_MESSAGES_WIDGET_API_URL: "https://widget.example.com/api/",
FRONTEND_FEEDBACK_MESSAGES_WIDGET_PATH: widgetPath,
FRONTEND_FEEDBACK_MESSAGES_WIDGET_CHANNEL: "drive",
},
);
// Serve an empty widget script so the injected loader does not hit the
// network for real.
await page.route(`${widgetPath}feedback.js`, (route) =>
route.fulfill({ contentType: "application/javascript", body: "" }),
);
await spyOnWindowOpen(page);
await login(page, "drive@example.com");
await page.goto("/");
const footer = page.locator(".c__left-panel__footer__drive");
await footer.getByRole("button", { name: "Help" }).click();
await page.getByRole("menuitem", { name: "Contact us" }).click();
// The widget queues an init call and injects its loader script.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const widgetCalls = await page.evaluate(() => (window as any)._stmsg_widget);
expect(widgetCalls).toHaveLength(1);
expect(widgetCalls[0][0]).toBe("feedback");
expect(widgetCalls[0][1]).toBe("init");
expect(widgetCalls[0][2].channel).toBe("drive");
await expect(
page.locator(`script[src="${widgetPath}feedback.js"]`),
).toHaveCount(1);
// The widget takes precedence over the support email.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const openedUrls = await page.evaluate(() => (window as any).__openedUrls);
expect(openedUrls).toEqual([]);
});
test("does not render the help menu when the config is empty", async ({
page,
}) => {