@gooddata/sdk-ui-pluggable-host 11.40.0-alpha.3 → 11.40.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/loader/routing.js +7 -3
- package/esm/platformContext/loadPlatformContext.js +1 -1
- package/esm/platformContext/types.d.ts +1 -1
- package/esm/platformContext/useLoadPlatformContext.js +24 -2
- package/esm/platformContext/useWorkspaceColorPalette.d.ts +17 -0
- package/esm/platformContext/useWorkspaceColorPalette.js +37 -0
- package/esm/platformContext/useWorkspaceTheme.d.ts +17 -0
- package/esm/platformContext/useWorkspaceTheme.js +37 -0
- package/esm/platformContext/waitForInjectedApiToken.d.ts +26 -0
- package/esm/platformContext/waitForInjectedApiToken.js +104 -0
- package/esm/ui/HostChrome.js +5 -4
- package/esm/ui/PluggableApplicationRenderer.js +2 -1
- package/esm/ui/SemanticSearch.d.ts +2 -1
- package/esm/ui/SemanticSearch.js +2 -2
- package/esm/ui/useHostChromeSearch.js +1 -1
- package/package.json +24 -23
package/esm/loader/routing.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
// (C) 2026 GoodData Corporation
|
|
2
2
|
import { isExternalPluggableApplicationRegistryItem, isLocalPluggableApplicationRegistryItem, isRemotePluggableApplicationRegistryItem, } from "@gooddata/sdk-model";
|
|
3
3
|
const WORKSPACE_PATH_PATTERN = /^\/workspace\/(?<workspaceId>[^/]+)(?:\/|$)/;
|
|
4
|
+
function stripEmbedPrefix(pathname) {
|
|
5
|
+
return pathname.startsWith("/embedded/") ? pathname.slice("/embedded".length) : pathname;
|
|
6
|
+
}
|
|
4
7
|
/**
|
|
5
8
|
* Returns the application scope for the given URL path, or undefined if the path
|
|
6
9
|
* does not match a known scope.
|
|
7
10
|
*/
|
|
8
11
|
export function getApplicationScopeFromPath(path) {
|
|
9
|
-
const
|
|
12
|
+
const stripped = stripEmbedPrefix(path);
|
|
13
|
+
const isPathMatching = (definitionPath) => stripped === definitionPath || stripped.startsWith(definitionPath + "/");
|
|
10
14
|
if (isPathMatching("/organization")) {
|
|
11
15
|
return "organization";
|
|
12
16
|
}
|
|
@@ -20,7 +24,7 @@ export function getApplicationScopeFromPath(path) {
|
|
|
20
24
|
* does not contain a workspace id.
|
|
21
25
|
*/
|
|
22
26
|
export function getWorkspaceIdFromPath(pathname) {
|
|
23
|
-
return WORKSPACE_PATH_PATTERN.exec(pathname ?? "")?.groups?.["workspaceId"];
|
|
27
|
+
return WORKSPACE_PATH_PATTERN.exec(stripEmbedPrefix(pathname ?? ""))?.groups?.["workspaceId"];
|
|
24
28
|
}
|
|
25
29
|
function ensureLeadingSlash(path) {
|
|
26
30
|
return path.startsWith("/") ? path : `/${path}`;
|
|
@@ -79,7 +83,7 @@ export function isInternalAppRouteActive(app, ctx, pathname) {
|
|
|
79
83
|
return false;
|
|
80
84
|
}
|
|
81
85
|
const basePath = normalizePath(getApplicationHref(app, ctx, pathname));
|
|
82
|
-
const normalizedPathname = normalizePath(pathname);
|
|
86
|
+
const normalizedPathname = normalizePath(stripEmbedPrefix(pathname));
|
|
83
87
|
return normalizedPathname === basePath || normalizedPathname.startsWith(`${basePath}/`);
|
|
84
88
|
}
|
|
85
89
|
export function getActiveInternalApplication(apps, ctx, pathname) {
|
|
@@ -45,6 +45,6 @@ export async function loadPlatformContext(options = {}) {
|
|
|
45
45
|
whiteLabeling: bootstrap.whiteLabeling,
|
|
46
46
|
pantherTier: bootstrap.pantherTier,
|
|
47
47
|
theme: bootstrap.theme,
|
|
48
|
-
embeddingMode: "none",
|
|
48
|
+
embeddingMode: window.location.pathname.startsWith("/embedded/") ? "iframe" : "none",
|
|
49
49
|
};
|
|
50
50
|
}
|
|
@@ -10,7 +10,7 @@ export interface IPlatformContextErrorResult {
|
|
|
10
10
|
state: "error";
|
|
11
11
|
error: string;
|
|
12
12
|
}
|
|
13
|
-
type RoutePlatformContextFields = "currentWorkspaceId" | "currentApplicationScope" | "workspacePermissions" | "workspaceSettings" | "settings" | "preferredLocale";
|
|
13
|
+
type RoutePlatformContextFields = "currentWorkspaceId" | "currentApplicationScope" | "workspacePermissions" | "workspaceSettings" | "colorPalette" | "settings" | "preferredLocale";
|
|
14
14
|
export type IRoutePlatformContext = Pick<IPlatformContext, RoutePlatformContextFields>;
|
|
15
15
|
export type IBackendPlatformContext = Omit<IPlatformContext, RoutePlatformContextFields>;
|
|
16
16
|
export type IPlatformContextLoadResult<TContext> = IPlatformContextLoadingResult | IPlatformContextReadyResult<TContext> | IPlatformContextErrorResult;
|
|
@@ -6,8 +6,11 @@ import { isProduction } from "../lib/isProduction.js";
|
|
|
6
6
|
import { getApplicationScopeFromPath, getWorkspaceIdFromPath } from "../loader/routing.js";
|
|
7
7
|
import { getBackend } from "./backend.js";
|
|
8
8
|
import { HostApplicationDisabledError, loadPlatformContext, } from "./loadPlatformContext.js";
|
|
9
|
+
import { useWorkspaceColorPalette } from "./useWorkspaceColorPalette.js";
|
|
9
10
|
import { useWorkspacePermissions } from "./useWorkspacePermissions.js";
|
|
10
11
|
import { useWorkspaceSettings } from "./useWorkspaceSettings.js";
|
|
12
|
+
import { useWorkspaceTheme } from "./useWorkspaceTheme.js";
|
|
13
|
+
import { shouldWaitForInjectedApiToken, waitForInjectedApiToken } from "./waitForInjectedApiToken.js";
|
|
11
14
|
function redirectToAppRoot() {
|
|
12
15
|
const rootUrl = new URL("/", window.location.origin).toString();
|
|
13
16
|
window.location.assign(rootUrl);
|
|
@@ -28,6 +31,8 @@ export function useLoadPlatformContext() {
|
|
|
28
31
|
const backend = backendContext.state === "ready" ? getBackend() : undefined;
|
|
29
32
|
const workspacePermissionsState = useWorkspacePermissions(backend, workspaceId);
|
|
30
33
|
const workspaceSettingsState = useWorkspaceSettings(backend, workspaceId);
|
|
34
|
+
const workspaceColorPaletteState = useWorkspaceColorPalette(backend, workspaceId);
|
|
35
|
+
const workspaceThemeState = useWorkspaceTheme(backend, workspaceId);
|
|
31
36
|
return useMemo(() => {
|
|
32
37
|
if (backendContext.state !== "ready") {
|
|
33
38
|
return backendContext;
|
|
@@ -47,6 +52,11 @@ export function useLoadPlatformContext() {
|
|
|
47
52
|
}
|
|
48
53
|
const workspacePermissions = workspacePermissionsState.state === "ready" ? workspacePermissionsState.permissions : undefined;
|
|
49
54
|
const workspaceSettings = workspaceSettingsState.state === "ready" ? workspaceSettingsState.settings : undefined;
|
|
55
|
+
const colorPalette = workspaceColorPaletteState.state === "ready"
|
|
56
|
+
? workspaceColorPaletteState.colorPalette
|
|
57
|
+
: undefined;
|
|
58
|
+
const workspaceTheme = workspaceThemeState.state === "ready" ? workspaceThemeState.theme : undefined;
|
|
59
|
+
const effectiveTheme = workspaceTheme ?? backendContext.ctx.theme;
|
|
50
60
|
const settings = workspaceSettings ?? backendContext.ctx.userSettings;
|
|
51
61
|
const preferredLocale = isLocale(settings.locale) ? settings.locale : undefined;
|
|
52
62
|
const routeCtx = {
|
|
@@ -54,11 +64,20 @@ export function useLoadPlatformContext() {
|
|
|
54
64
|
currentWorkspaceId: workspaceId,
|
|
55
65
|
workspacePermissions,
|
|
56
66
|
workspaceSettings,
|
|
67
|
+
colorPalette,
|
|
57
68
|
settings,
|
|
58
69
|
preferredLocale,
|
|
59
70
|
};
|
|
60
|
-
return { state: "ready", ctx: { ...backendContext.ctx, ...routeCtx } };
|
|
61
|
-
}, [
|
|
71
|
+
return { state: "ready", ctx: { ...backendContext.ctx, ...routeCtx, theme: effectiveTheme } };
|
|
72
|
+
}, [
|
|
73
|
+
backendContext,
|
|
74
|
+
applicationScope,
|
|
75
|
+
workspaceId,
|
|
76
|
+
workspacePermissionsState,
|
|
77
|
+
workspaceSettingsState,
|
|
78
|
+
workspaceColorPaletteState,
|
|
79
|
+
workspaceThemeState,
|
|
80
|
+
]);
|
|
62
81
|
}
|
|
63
82
|
class BackendPlatformContextProviderClass {
|
|
64
83
|
_loadingStateMemo = { state: "loading" };
|
|
@@ -87,6 +106,9 @@ class BackendPlatformContextProviderClass {
|
|
|
87
106
|
}
|
|
88
107
|
this._abortController = new AbortController();
|
|
89
108
|
try {
|
|
109
|
+
if (shouldWaitForInjectedApiToken()) {
|
|
110
|
+
await waitForInjectedApiToken(this._abortController.signal);
|
|
111
|
+
}
|
|
90
112
|
const ctx = await loadPlatformContext({
|
|
91
113
|
signal: this._abortController.signal,
|
|
92
114
|
callbacks: this._callbacks,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type IAnalyticalBackend } from "@gooddata/sdk-backend-spi";
|
|
2
|
+
import { type IColorPalette } from "@gooddata/sdk-model";
|
|
3
|
+
type WorkspaceColorPaletteState = {
|
|
4
|
+
state: "idle";
|
|
5
|
+
} | {
|
|
6
|
+
state: "loading";
|
|
7
|
+
} | {
|
|
8
|
+
state: "ready";
|
|
9
|
+
colorPalette: IColorPalette | undefined;
|
|
10
|
+
} | {
|
|
11
|
+
state: "forbidden";
|
|
12
|
+
} | {
|
|
13
|
+
state: "error";
|
|
14
|
+
error: string;
|
|
15
|
+
};
|
|
16
|
+
export declare function useWorkspaceColorPalette(backend: IAnalyticalBackend | undefined, workspaceId: string | undefined): WorkspaceColorPaletteState;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// (C) 2026 GoodData Corporation
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { UnexpectedResponseError } from "@gooddata/sdk-backend-spi";
|
|
4
|
+
export function useWorkspaceColorPalette(backend, workspaceId) {
|
|
5
|
+
const [paletteState, setPaletteState] = useState({ state: "idle" });
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
if (!backend || !workspaceId) {
|
|
8
|
+
setPaletteState((prev) => (prev.state === "idle" ? prev : { state: "idle" }));
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
let cancelled = false;
|
|
12
|
+
setPaletteState({ state: "loading" });
|
|
13
|
+
backend
|
|
14
|
+
.workspace(workspaceId)
|
|
15
|
+
.styling()
|
|
16
|
+
.getColorPalette()
|
|
17
|
+
.then((colorPalette) => {
|
|
18
|
+
if (!cancelled) {
|
|
19
|
+
setPaletteState({ state: "ready", colorPalette });
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
.catch((e) => {
|
|
23
|
+
if (cancelled)
|
|
24
|
+
return;
|
|
25
|
+
if (e instanceof UnexpectedResponseError && (e.httpStatus === 403 || e.httpStatus === 404)) {
|
|
26
|
+
setPaletteState({ state: "forbidden" });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const error = e instanceof Error ? e.message : "Unknown error loading workspace color palette.";
|
|
30
|
+
setPaletteState({ state: "error", error });
|
|
31
|
+
});
|
|
32
|
+
return () => {
|
|
33
|
+
cancelled = true;
|
|
34
|
+
};
|
|
35
|
+
}, [backend, workspaceId]);
|
|
36
|
+
return paletteState;
|
|
37
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type IAnalyticalBackend } from "@gooddata/sdk-backend-spi";
|
|
2
|
+
import { type ITheme } from "@gooddata/sdk-model";
|
|
3
|
+
type WorkspaceThemeState = {
|
|
4
|
+
state: "idle";
|
|
5
|
+
} | {
|
|
6
|
+
state: "loading";
|
|
7
|
+
} | {
|
|
8
|
+
state: "ready";
|
|
9
|
+
theme: ITheme | undefined;
|
|
10
|
+
} | {
|
|
11
|
+
state: "forbidden";
|
|
12
|
+
} | {
|
|
13
|
+
state: "error";
|
|
14
|
+
error: string;
|
|
15
|
+
};
|
|
16
|
+
export declare function useWorkspaceTheme(backend: IAnalyticalBackend | undefined, workspaceId: string | undefined): WorkspaceThemeState;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// (C) 2026 GoodData Corporation
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { UnexpectedResponseError } from "@gooddata/sdk-backend-spi";
|
|
4
|
+
export function useWorkspaceTheme(backend, workspaceId) {
|
|
5
|
+
const [themeState, setThemeState] = useState({ state: "idle" });
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
if (!backend || !workspaceId) {
|
|
8
|
+
setThemeState((prev) => (prev.state === "idle" ? prev : { state: "idle" }));
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
let cancelled = false;
|
|
12
|
+
setThemeState({ state: "loading" });
|
|
13
|
+
backend
|
|
14
|
+
.workspace(workspaceId)
|
|
15
|
+
.styling()
|
|
16
|
+
.getTheme()
|
|
17
|
+
.then((theme) => {
|
|
18
|
+
if (!cancelled) {
|
|
19
|
+
setThemeState({ state: "ready", theme });
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
.catch((e) => {
|
|
23
|
+
if (cancelled)
|
|
24
|
+
return;
|
|
25
|
+
if (e instanceof UnexpectedResponseError && (e.httpStatus === 403 || e.httpStatus === 404)) {
|
|
26
|
+
setThemeState({ state: "forbidden" });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const error = e instanceof Error ? e.message : "Unknown error loading workspace theme.";
|
|
30
|
+
setThemeState({ state: "error", error });
|
|
31
|
+
});
|
|
32
|
+
return () => {
|
|
33
|
+
cancelled = true;
|
|
34
|
+
};
|
|
35
|
+
}, [backend, workspaceId]);
|
|
36
|
+
return themeState;
|
|
37
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tells the host whether the iframe URL asks for postMessage-based auth injection.
|
|
3
|
+
*
|
|
4
|
+
* When this returns true, the host MUST defer `backend.bootstrap()` until a
|
|
5
|
+
* `SetApiToken` postMessage from the parent has been applied via `setApiToken`
|
|
6
|
+
* / `setJwt`. The flag may live in either `window.location.search` (modern
|
|
7
|
+
* pluggable URLs) or in the hash query (legacy AD iframe URLs).
|
|
8
|
+
*/
|
|
9
|
+
export declare function shouldWaitForInjectedApiToken(): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Performs the pre-bootstrap auth handshake AD has historically owned, but
|
|
12
|
+
* lifted to the host so that pluggable AD can keep the customer-side wire
|
|
13
|
+
* contract unchanged.
|
|
14
|
+
*
|
|
15
|
+
* Sequence:
|
|
16
|
+
* 1. Configure messagingUtils to accept only `SetApiToken` from the
|
|
17
|
+
* "analyticalDesigner" product.
|
|
18
|
+
* 2. Register a one-shot listener for that command.
|
|
19
|
+
* 3. Emit `listeningForApiToken` to the parent frame.
|
|
20
|
+
* 4. Await the parent's `SetApiToken`, apply it to the host backend, then
|
|
21
|
+
* tear the listener down so AD can register its full command set when it
|
|
22
|
+
* mounts.
|
|
23
|
+
*
|
|
24
|
+
* Resolves once a valid token has been applied. Rejects if `signal` aborts.
|
|
25
|
+
*/
|
|
26
|
+
export declare function waitForInjectedApiToken(signal?: AbortSignal): Promise<void>;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// (C) 2026 GoodData Corporation
|
|
2
|
+
import { GdcAdCommandType as GdcAdCommandTypeValues, GdcAdEventType, GdcProductName, isAdSetApiTokenCommandData, } from "@gooddata/sdk-embedding";
|
|
3
|
+
import { messagingUtils } from "@gooddata/sdk-embedding/internal";
|
|
4
|
+
import { setApiToken, setJwt } from "./backend.js";
|
|
5
|
+
const API_TOKEN_AUTHENTICATION_PARAM = "apiTokenAuthentication";
|
|
6
|
+
const EMBEDDED_PATH_PREFIX = "/embedded/";
|
|
7
|
+
function hasFlagInQuery(query) {
|
|
8
|
+
return new URLSearchParams(query).get(API_TOKEN_AUTHENTICATION_PARAM) === "true";
|
|
9
|
+
}
|
|
10
|
+
// Extracts the query portion from a hash like "#/<route>?<query>". Standalone AD
|
|
11
|
+
// uses hash-based routing and the legacy iframe URL keeps the flag inside the
|
|
12
|
+
// hash, so the host has to look there as well as in `window.location.search`.
|
|
13
|
+
function getHashQuery(hash) {
|
|
14
|
+
const idx = hash.indexOf("?");
|
|
15
|
+
return idx >= 0 ? hash.slice(idx + 1) : "";
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Tells the host whether the iframe URL asks for postMessage-based auth injection.
|
|
19
|
+
*
|
|
20
|
+
* When this returns true, the host MUST defer `backend.bootstrap()` until a
|
|
21
|
+
* `SetApiToken` postMessage from the parent has been applied via `setApiToken`
|
|
22
|
+
* / `setJwt`. The flag may live in either `window.location.search` (modern
|
|
23
|
+
* pluggable URLs) or in the hash query (legacy AD iframe URLs).
|
|
24
|
+
*/
|
|
25
|
+
export function shouldWaitForInjectedApiToken() {
|
|
26
|
+
if (!window.location.pathname.startsWith(EMBEDDED_PATH_PREFIX)) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
return (hasFlagInQuery(window.location.search.replace(/^\?/, "")) ||
|
|
30
|
+
hasFlagInQuery(getHashQuery(window.location.hash)));
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Performs the pre-bootstrap auth handshake AD has historically owned, but
|
|
34
|
+
* lifted to the host so that pluggable AD can keep the customer-side wire
|
|
35
|
+
* contract unchanged.
|
|
36
|
+
*
|
|
37
|
+
* Sequence:
|
|
38
|
+
* 1. Configure messagingUtils to accept only `SetApiToken` from the
|
|
39
|
+
* "analyticalDesigner" product.
|
|
40
|
+
* 2. Register a one-shot listener for that command.
|
|
41
|
+
* 3. Emit `listeningForApiToken` to the parent frame.
|
|
42
|
+
* 4. Await the parent's `SetApiToken`, apply it to the host backend, then
|
|
43
|
+
* tear the listener down so AD can register its full command set when it
|
|
44
|
+
* mounts.
|
|
45
|
+
*
|
|
46
|
+
* Resolves once a valid token has been applied. Rejects if `signal` aborts.
|
|
47
|
+
*/
|
|
48
|
+
export async function waitForInjectedApiToken(signal) {
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
let listener;
|
|
51
|
+
const cleanup = () => {
|
|
52
|
+
if (listener) {
|
|
53
|
+
messagingUtils.removeListener(listener);
|
|
54
|
+
listener = undefined;
|
|
55
|
+
}
|
|
56
|
+
signal?.removeEventListener("abort", onAbort);
|
|
57
|
+
};
|
|
58
|
+
const onAbort = () => {
|
|
59
|
+
cleanup();
|
|
60
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
61
|
+
};
|
|
62
|
+
if (signal?.aborted) {
|
|
63
|
+
onAbort();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
67
|
+
listener = (command) => {
|
|
68
|
+
if (!isAdSetApiTokenCommandData(command.data)) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
const payload = command.data.gdc.event.data;
|
|
72
|
+
if (!payload) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
const token = payload.token;
|
|
76
|
+
const tokenType = payload.type ?? "gooddata";
|
|
77
|
+
if (typeof token !== "string" || token.length === 0) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
if (tokenType === "jwt" && !/^[\w-]+\.[\w-]+\.[\w-]+$/.test(token)) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
if (tokenType === "jwt") {
|
|
85
|
+
setJwt(token, payload.secondsBeforeTokenExpirationToEmitReminder ?? 60);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
setApiToken(token);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
cleanup();
|
|
93
|
+
reject(e);
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
cleanup();
|
|
97
|
+
resolve();
|
|
98
|
+
return true;
|
|
99
|
+
};
|
|
100
|
+
messagingUtils.setConfig(GdcProductName.ANALYTICAL_DESIGNER, [GdcAdCommandTypeValues.SetApiToken]);
|
|
101
|
+
messagingUtils.addListener(listener);
|
|
102
|
+
messagingUtils.postEvent(GdcProductName.ANALYTICAL_DESIGNER, GdcAdEventType.ListeningForApiToken, {});
|
|
103
|
+
});
|
|
104
|
+
}
|
package/esm/ui/HostChrome.js
CHANGED
|
@@ -106,10 +106,11 @@ export function HostChrome({ ctx, resolvedApplications, pathname, onNavigate, on
|
|
|
106
106
|
const headerColor = ctx.theme?.header?.backgroundColor ?? defaultHeaderTheme.backgroundColor;
|
|
107
107
|
const headerTextColor = ctx.theme?.header?.color ?? defaultHeaderTheme.color;
|
|
108
108
|
const activeColor = ctx.theme?.header?.activeColor ?? defaultHeaderTheme.activeColor;
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
const isEmbedded = ctx.embeddingMode === "iframe";
|
|
110
|
+
return (_jsx(HostIntlProvider, { locale: locale, additionalMessages: appMessages, children: _jsx(BackendProvider, { backend: getBackend(), children: _jsx(ToastsCenterContextProvider, { children: _jsxs("div", { className: b(), children: [isEmbedded ? null : (_jsx("div", { className: e("header"), onMouseOver: handleHeaderMouseOver, children: _jsx(AppHeader, { logoUrl: ctx.whiteLabeling?.logoUrl || defaultLogoUrl, logoHref: "/organization" // switch the host scope to organization, the first org app will be chosen
|
|
111
111
|
, logoTitle: logoTitle, headerColor: headerColor, headerTextColor: headerTextColor, activeColor: activeColor, userName: userName, organizationName: ctx.organization?.title, isAccessibilityCompliant: true, workspacePicker: workspacePicker, menuItemsGroups: menuItemsGroups, helpMenuItems: helpMenuItems, accountMenuItems: accountMenuItems, onMenuItemClick: handleMenuItemClick, showUpsellButton: pricing.isTrial, onUpsellButtonClick: pricing.onUpsellButtonClick, expiredDate: pricing.isTrial ? pricing.expiredDate : undefined, search: search.element, showChatItem: chat.showChatItem, onChatItemClick: chat.open, notificationsPanel: ctx.userSettings.enableInPlatformNotifications
|
|
112
|
-
? ({ isMobile, closeNotificationsOverlay }) => (_jsx(AppHeaderNotifications, { locale: locale, isMobile: isMobile, closeNotificationsOverlay: closeNotificationsOverlay, useAsOfDateParam: ctx.userSettings.enableExecutionTimestamp ?? false, enableExportToDocumentStorage: ctx.userSettings.enableExportToDocumentStorage ??
|
|
113
|
-
|
|
112
|
+
? ({ isMobile, closeNotificationsOverlay }) => (_jsx(AppHeaderNotifications, { locale: locale, isMobile: isMobile, closeNotificationsOverlay: closeNotificationsOverlay, useAsOfDateParam: ctx.userSettings.enableExecutionTimestamp ?? false, enableExportToDocumentStorage: ctx.userSettings.enableExportToDocumentStorage ??
|
|
113
|
+
false }))
|
|
114
|
+
: undefined }) })), _jsx("main", { className: e("content"), children: children }), chat.element, pricing.element, _jsx(HostNotificationDispatcher, { notification: notification })
|
|
114
115
|
] }) }) }) }));
|
|
115
116
|
}
|
|
@@ -17,7 +17,8 @@ export function PluggableApplicationRenderer({ app, ctx, pathname, onHeaderChang
|
|
|
17
17
|
const containerRef = useRef(null);
|
|
18
18
|
const mountHandleRef = useRef(undefined);
|
|
19
19
|
const [viewState, setViewState] = useState({ state: "loading" });
|
|
20
|
-
const
|
|
20
|
+
const baseHref = getApplicationHref(app, ctx, pathname);
|
|
21
|
+
const appBasePath = ctx.embeddingMode === "iframe" ? `/embedded${baseHref}` : baseHref;
|
|
21
22
|
const lifecycle = getAppLifecycleCallbacks();
|
|
22
23
|
const onTelemetryEvent = useMemo(() => lifecycle?.createTelemetryCallbacks?.(app.id), [lifecycle, app.id]);
|
|
23
24
|
// onEvent is intentionally stable (empty deps) — pluggable apps capture it at mount time
|
|
@@ -17,7 +17,8 @@ export interface ISemanticSearchProps {
|
|
|
17
17
|
isTrial?: boolean;
|
|
18
18
|
enableUseGenAIChat?: boolean;
|
|
19
19
|
useHostedMetricEditor?: boolean;
|
|
20
|
+
useHostedAnalyticalDesigner?: boolean;
|
|
20
21
|
onAskAiAssistant?: (question: string) => void;
|
|
21
22
|
onEvent?: (event: SemanticSearchEvent) => void;
|
|
22
23
|
}
|
|
23
|
-
export declare function SemanticSearch({ backend, workspaceId, metadataTimeZone, canManage, canAnalyze, canFullControl, isTrial, enableUseGenAIChat, useHostedMetricEditor, onAskAiAssistant, onEvent }: ISemanticSearchProps): import("react/jsx-runtime").JSX.Element;
|
|
24
|
+
export declare function SemanticSearch({ backend, workspaceId, metadataTimeZone, canManage, canAnalyze, canFullControl, isTrial, enableUseGenAIChat, useHostedMetricEditor, useHostedAnalyticalDesigner, onAskAiAssistant, onEvent }: ISemanticSearchProps): import("react/jsx-runtime").JSX.Element;
|
package/esm/ui/SemanticSearch.js
CHANGED
|
@@ -4,7 +4,7 @@ import { useCallback, useEffect, useMemo } from "react";
|
|
|
4
4
|
import { useIntl } from "react-intl";
|
|
5
5
|
import { FooterButtonAiAssistant, SearchOverlay, useSearchMetrics, } from "@gooddata/sdk-ui-semantic-search/internal";
|
|
6
6
|
const RESULTS_LIMIT = 10;
|
|
7
|
-
export function SemanticSearch({ backend, workspaceId, metadataTimeZone, canManage = false, canAnalyze = false, canFullControl = false, isTrial = false, enableUseGenAIChat = false, useHostedMetricEditor = false, onAskAiAssistant, onEvent, }) {
|
|
7
|
+
export function SemanticSearch({ backend, workspaceId, metadataTimeZone, canManage = false, canAnalyze = false, canFullControl = false, isTrial = false, enableUseGenAIChat = false, useHostedMetricEditor = false, useHostedAnalyticalDesigner = false, onAskAiAssistant, onEvent, }) {
|
|
8
8
|
const intl = useIntl();
|
|
9
9
|
const objectTypes = useMemo(() => {
|
|
10
10
|
const types = ["dashboard", "visualization"];
|
|
@@ -42,5 +42,5 @@ export function SemanticSearch({ backend, workspaceId, metadataTimeZone, canMana
|
|
|
42
42
|
onAskAiAssistant?.(intl.formatMessage({ id: "gen-ai.ask-assistant.search" }, { question: props.value }));
|
|
43
43
|
} }));
|
|
44
44
|
};
|
|
45
|
-
return (_jsx(SearchOverlay, { limit: RESULTS_LIMIT, onSelect: onSelect, onSearch: onSearchMetrics, deepSearch: false, objectTypes: objectTypes, workspace: workspaceId, backend: backend, canManage: canManage, canAnalyze: canAnalyze, canFullControl: canFullControl, metadataTimezone: metadataTimeZone, uiPathOptions: { useHostedMetricEditor }, renderFooter: renderFooter }));
|
|
45
|
+
return (_jsx(SearchOverlay, { limit: RESULTS_LIMIT, onSelect: onSelect, onSearch: onSearchMetrics, deepSearch: false, objectTypes: objectTypes, workspace: workspaceId, backend: backend, canManage: canManage, canAnalyze: canAnalyze, canFullControl: canFullControl, metadataTimezone: metadataTimeZone, uiPathOptions: { useHostedMetricEditor, useHostedAnalyticalDesigner }, renderFooter: renderFooter }));
|
|
46
46
|
}
|
|
@@ -13,6 +13,6 @@ export function useHostChromeSearch({ features, isTrial, onAskAiAssistant, telem
|
|
|
13
13
|
const handleSearchEvent = useCallback((event) => {
|
|
14
14
|
telemetry?.trackEvent(event.name, event.payload);
|
|
15
15
|
}, [telemetry]);
|
|
16
|
-
const element = features.showSearch && features.workspaceId ? (_jsx(SemanticSearch, { backend: getBackend(), workspaceId: features.workspaceId, canManage: features.canManageProject, canAnalyze: features.canCreateVisualization, canFullControl: features.canFullControl, metadataTimeZone: features.settings.metadataTimeZone, isTrial: isTrial, enableUseGenAIChat: features.showChat, useHostedMetricEditor: Boolean(features.settings.enableShellApplication_metricEditor), onAskAiAssistant: onAskAiAssistant, onEvent: handleSearchEvent })) : null;
|
|
16
|
+
const element = features.showSearch && features.workspaceId ? (_jsx(SemanticSearch, { backend: getBackend(), workspaceId: features.workspaceId, canManage: features.canManageProject, canAnalyze: features.canCreateVisualization, canFullControl: features.canFullControl, metadataTimeZone: features.settings.metadataTimeZone, isTrial: isTrial, enableUseGenAIChat: features.showChat, useHostedMetricEditor: Boolean(features.settings.enableShellApplication_metricEditor), useHostedAnalyticalDesigner: Boolean(features.settings.enableShellApplication_analyticalDesigner), onAskAiAssistant: onAskAiAssistant, onEvent: handleSearchEvent })) : null;
|
|
17
17
|
return { element };
|
|
18
18
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gooddata/sdk-ui-pluggable-host",
|
|
3
|
-
"version": "11.40.0-alpha.
|
|
3
|
+
"version": "11.40.0-alpha.5",
|
|
4
4
|
"description": "GoodData SDK runtime for hosting pluggable applications — registry, loader, routing, platform context, default UI chrome",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "GoodData Corporation",
|
|
@@ -29,19 +29,20 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@module-federation/runtime": "2.3.1",
|
|
31
31
|
"lodash-es": "^4.17.23",
|
|
32
|
-
"@gooddata/sdk-backend-base": "11.40.0-alpha.
|
|
33
|
-
"@gooddata/sdk-backend-spi": "11.40.0-alpha.
|
|
34
|
-
"@gooddata/sdk-backend-tiger": "11.40.0-alpha.
|
|
35
|
-
"@gooddata/sdk-
|
|
36
|
-
"@gooddata/sdk-
|
|
37
|
-
"@gooddata/sdk-
|
|
38
|
-
"@gooddata/sdk-ui": "11.40.0-alpha.
|
|
39
|
-
"@gooddata/sdk-ui-
|
|
40
|
-
"@gooddata/sdk-ui-
|
|
41
|
-
"@gooddata/sdk-ui-
|
|
42
|
-
"@gooddata/sdk-ui-
|
|
43
|
-
"@gooddata/sdk-ui-
|
|
44
|
-
"@gooddata/
|
|
32
|
+
"@gooddata/sdk-backend-base": "11.40.0-alpha.5",
|
|
33
|
+
"@gooddata/sdk-backend-spi": "11.40.0-alpha.5",
|
|
34
|
+
"@gooddata/sdk-backend-tiger": "11.40.0-alpha.5",
|
|
35
|
+
"@gooddata/sdk-embedding": "11.40.0-alpha.5",
|
|
36
|
+
"@gooddata/sdk-model": "11.40.0-alpha.5",
|
|
37
|
+
"@gooddata/sdk-pluggable-application-model": "11.40.0-alpha.5",
|
|
38
|
+
"@gooddata/sdk-ui": "11.40.0-alpha.5",
|
|
39
|
+
"@gooddata/sdk-ui-application-header": "11.40.0-alpha.5",
|
|
40
|
+
"@gooddata/sdk-ui-ext": "11.40.0-alpha.5",
|
|
41
|
+
"@gooddata/sdk-ui-gen-ai": "11.40.0-alpha.5",
|
|
42
|
+
"@gooddata/sdk-ui-kit": "11.40.0-alpha.5",
|
|
43
|
+
"@gooddata/sdk-ui-semantic-search": "11.40.0-alpha.5",
|
|
44
|
+
"@gooddata/sdk-ui-theme-provider": "11.40.0-alpha.5",
|
|
45
|
+
"@gooddata/util": "11.40.0-alpha.5"
|
|
45
46
|
},
|
|
46
47
|
"devDependencies": {
|
|
47
48
|
"@microsoft/api-documenter": "^7.17.0",
|
|
@@ -55,7 +56,7 @@
|
|
|
55
56
|
"@typescript-eslint/eslint-plugin": "8.58.0",
|
|
56
57
|
"@typescript-eslint/parser": "8.58.0",
|
|
57
58
|
"@typescript/native-preview": "7.0.0-dev.20260202.1",
|
|
58
|
-
"@vitest/coverage-v8": "4.1.
|
|
59
|
+
"@vitest/coverage-v8": "4.1.8",
|
|
59
60
|
"@vitest/eslint-plugin": "1.6.6",
|
|
60
61
|
"eslint": "^9.39.2",
|
|
61
62
|
"eslint-import-resolver-typescript": "4.4.4",
|
|
@@ -70,22 +71,22 @@
|
|
|
70
71
|
"happy-dom": "18.0.1",
|
|
71
72
|
"jiti": "2.6.1",
|
|
72
73
|
"npm-run-all": "^4.1.5",
|
|
73
|
-
"oxfmt": "0.
|
|
74
|
-
"oxlint": "
|
|
75
|
-
"oxlint-tsgolint": "0.
|
|
74
|
+
"oxfmt": "0.52.0",
|
|
75
|
+
"oxlint": "1.51.0",
|
|
76
|
+
"oxlint-tsgolint": "0.15.0",
|
|
76
77
|
"react": "19.1.1",
|
|
77
78
|
"react-dom": "19.1.1",
|
|
78
79
|
"react-intl": "7.1.11",
|
|
79
80
|
"react-router": "7.13.1",
|
|
80
|
-
"rolldown": "1.0.
|
|
81
|
+
"rolldown": "1.0.3",
|
|
81
82
|
"sass": "1.70.0",
|
|
82
83
|
"tslib": "2.8.1",
|
|
83
84
|
"typescript": "5.9.3",
|
|
84
|
-
"vite": "8.0.
|
|
85
|
-
"vitest": "4.1.
|
|
85
|
+
"vite": "8.0.16",
|
|
86
|
+
"vitest": "4.1.8",
|
|
86
87
|
"vitest-dom": "0.1.1",
|
|
87
|
-
"@gooddata/eslint-config": "11.40.0-alpha.
|
|
88
|
-
"@gooddata/oxlint-config": "11.40.0-alpha.
|
|
88
|
+
"@gooddata/eslint-config": "11.40.0-alpha.5",
|
|
89
|
+
"@gooddata/oxlint-config": "11.40.0-alpha.5"
|
|
89
90
|
},
|
|
90
91
|
"peerDependencies": {
|
|
91
92
|
"react": ">=18.3.1",
|