@gooddata/sdk-ui-pluggable-host 11.50.0-alpha.2 → 11.50.0-alpha.3
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/components/Root.js +2 -2
- package/esm/components/useRedirectTarget.d.ts +1 -1
- package/esm/components/useRedirectTarget.d.ts.map +1 -1
- package/esm/components/useRedirectTarget.js +3 -2
- package/esm/loader/legacyRedirect.d.ts +10 -0
- package/esm/loader/legacyRedirect.d.ts.map +1 -1
- package/esm/loader/legacyRedirect.js +22 -0
- package/esm/loader/redirectLogic.d.ts +3 -1
- package/esm/loader/redirectLogic.d.ts.map +1 -1
- package/esm/loader/redirectLogic.js +21 -1
- package/package.json +17 -17
package/esm/components/Root.js
CHANGED
|
@@ -34,10 +34,10 @@ function ReadyRoot({ ctx, callbacks }) {
|
|
|
34
34
|
useEffect(() => {
|
|
35
35
|
callbacks?.onReady?.(ctx);
|
|
36
36
|
}, []); // eslint-disable-line react-hooks/exhaustive-deps -- run once on mount
|
|
37
|
-
const { pathname } = useLocation();
|
|
37
|
+
const { pathname, search } = useLocation();
|
|
38
38
|
const routerNavigate = useNavigate();
|
|
39
39
|
const apps = usePluggableApplications(ctx);
|
|
40
|
-
const redirect = useRedirectTarget(apps, ctx, pathname);
|
|
40
|
+
const redirect = useRedirectTarget(apps, ctx, pathname, search);
|
|
41
41
|
useRedirectNavigation(redirect, routerNavigate);
|
|
42
42
|
useEffect(() => {
|
|
43
43
|
if (redirect.state === "not-found") {
|
|
@@ -16,5 +16,5 @@ export type RedirectTargetState = {
|
|
|
16
16
|
/**
|
|
17
17
|
* Resolves the redirect target for the current URL and permission context.
|
|
18
18
|
*/
|
|
19
|
-
export declare function useRedirectTarget(apps: PluggableApplicationRegistryItem[], ctx: IPlatformContext, pathname: string): RedirectTargetState;
|
|
19
|
+
export declare function useRedirectTarget(apps: PluggableApplicationRegistryItem[], ctx: IPlatformContext, pathname: string, search?: string): RedirectTargetState;
|
|
20
20
|
//# sourceMappingURL=useRedirectTarget.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useRedirectTarget.d.ts","sourceRoot":"","sources":["../../src/components/useRedirectTarget.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,gCAAgC,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAMlF,MAAM,MAAM,mBAAmB,GACzB;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,GACpB;IAAE,KAAK,EAAE,QAAQ,CAAA;CAAE,GACnB;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,KAAK,EAAE,WAAW,CAAA;CAAE,GACtB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAExC;;GAEG;AACH,wBAAgB,iBAAiB,CAC7B,IAAI,EAAE,gCAAgC,EAAE,EACxC,GAAG,EAAE,gBAAgB,EACrB,QAAQ,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"useRedirectTarget.d.ts","sourceRoot":"","sources":["../../src/components/useRedirectTarget.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,gCAAgC,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAMlF,MAAM,MAAM,mBAAmB,GACzB;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,GACpB;IAAE,KAAK,EAAE,QAAQ,CAAA;CAAE,GACnB;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,KAAK,EAAE,WAAW,CAAA;CAAE,GACtB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAExC;;GAEG;AACH,wBAAgB,iBAAiB,CAC7B,IAAI,EAAE,gCAAgC,EAAE,EACxC,GAAG,EAAE,gBAAgB,EACrB,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,GAChB,mBAAmB,CA4DrB"}
|
|
@@ -6,7 +6,7 @@ import { getBackend } from "../platformContext/backend.js";
|
|
|
6
6
|
/**
|
|
7
7
|
* Resolves the redirect target for the current URL and permission context.
|
|
8
8
|
*/
|
|
9
|
-
export function useRedirectTarget(apps, ctx, pathname) {
|
|
9
|
+
export function useRedirectTarget(apps, ctx, pathname, search) {
|
|
10
10
|
const [redirectState, setRedirectState] = useState({ state: "loading" });
|
|
11
11
|
useEffect(() => {
|
|
12
12
|
let cancelled = false;
|
|
@@ -24,6 +24,7 @@ export function useRedirectTarget(apps, ctx, pathname) {
|
|
|
24
24
|
apps,
|
|
25
25
|
ctx: ctx,
|
|
26
26
|
pathname,
|
|
27
|
+
search,
|
|
27
28
|
fetchFirstWorkspaceId,
|
|
28
29
|
})
|
|
29
30
|
.then((url) => {
|
|
@@ -57,6 +58,6 @@ export function useRedirectTarget(apps, ctx, pathname) {
|
|
|
57
58
|
return () => {
|
|
58
59
|
cancelled = true;
|
|
59
60
|
};
|
|
60
|
-
}, [apps, pathname, ctx]);
|
|
61
|
+
}, [apps, pathname, search, ctx]);
|
|
61
62
|
return redirectState;
|
|
62
63
|
}
|
|
@@ -14,4 +14,14 @@ export interface ILegacyLocation {
|
|
|
14
14
|
* @alpha
|
|
15
15
|
*/
|
|
16
16
|
export declare function mapLegacyUrlToHost(location: ILegacyLocation): string | null;
|
|
17
|
+
/**
|
|
18
|
+
* Detects a bare legacy app landing — /dashboards, /analyze, /metrics or /modeler with no
|
|
19
|
+
* workspace in the hash. (Hash-bearing legacy URLs never get this far: {@link mapLegacyUrlToHost}
|
|
20
|
+
* rewrites them synchronously before the host boots.) The legacy standalone apps redirected such
|
|
21
|
+
* landings to the app in the user's first workspace; the host preserves that by resolving the
|
|
22
|
+
* first workspace asynchronously, so this only maps the pathname to the target app's route base.
|
|
23
|
+
*
|
|
24
|
+
* @returns the host route base of the app, or `null` when the pathname is not a bare legacy app path
|
|
25
|
+
*/
|
|
26
|
+
export declare function mapBareLegacyPathToApp(pathname: string): string | null;
|
|
17
27
|
//# sourceMappingURL=legacyRedirect.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"legacyRedirect.d.ts","sourceRoot":"","sources":["../../src/loader/legacyRedirect.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,WAAW,eAAe;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAClB;AAoBD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CA2C3E"}
|
|
1
|
+
{"version":3,"file":"legacyRedirect.d.ts","sourceRoot":"","sources":["../../src/loader/legacyRedirect.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,WAAW,eAAe;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAClB;AAoBD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CA2C3E;AAYD;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGtE"}
|
|
@@ -49,3 +49,25 @@ export function mapLegacyUrlToHost(location) {
|
|
|
49
49
|
}
|
|
50
50
|
return null;
|
|
51
51
|
}
|
|
52
|
+
// The host route base of each legacy standalone app, keyed by the path it was served under.
|
|
53
|
+
// Legacy paths and host route bases happen to coincide today, but keep the mapping explicit —
|
|
54
|
+
// the two are owned by different things (deployment paths vs. host registry routeBase).
|
|
55
|
+
const BARE_LEGACY_APP_ROUTES = {
|
|
56
|
+
"/dashboards": "/dashboards",
|
|
57
|
+
"/analyze": "/analyze",
|
|
58
|
+
"/metrics": "/metrics",
|
|
59
|
+
"/modeler": "/modeler",
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Detects a bare legacy app landing — /dashboards, /analyze, /metrics or /modeler with no
|
|
63
|
+
* workspace in the hash. (Hash-bearing legacy URLs never get this far: {@link mapLegacyUrlToHost}
|
|
64
|
+
* rewrites them synchronously before the host boots.) The legacy standalone apps redirected such
|
|
65
|
+
* landings to the app in the user's first workspace; the host preserves that by resolving the
|
|
66
|
+
* first workspace asynchronously, so this only maps the pathname to the target app's route base.
|
|
67
|
+
*
|
|
68
|
+
* @returns the host route base of the app, or `null` when the pathname is not a bare legacy app path
|
|
69
|
+
*/
|
|
70
|
+
export function mapBareLegacyPathToApp(pathname) {
|
|
71
|
+
const normalized = pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
|
|
72
|
+
return BARE_LEGACY_APP_ROUTES[normalized] ?? null;
|
|
73
|
+
}
|
|
@@ -14,6 +14,8 @@ export interface IResolveRedirectTargetOptions {
|
|
|
14
14
|
apps: PluggableApplicationRegistryItem[];
|
|
15
15
|
ctx: IPlatformContext;
|
|
16
16
|
pathname: string;
|
|
17
|
+
/** Query string of the current URL (location.search, `?`-prefixed or empty). */
|
|
18
|
+
search?: string;
|
|
17
19
|
/** Fetches the first workspace ID for the current user. */
|
|
18
20
|
fetchFirstWorkspaceId: () => Promise<string | undefined>;
|
|
19
21
|
}
|
|
@@ -27,5 +29,5 @@ export interface IResolveRedirectTargetOptions {
|
|
|
27
29
|
* @throws {AppNotFoundError} The current path maps to no accessible application (show 404)
|
|
28
30
|
* @throws {Error} Unexpected failure (show generic error screen)
|
|
29
31
|
*/
|
|
30
|
-
export declare function resolveRedirectTarget({ apps, ctx, pathname, fetchFirstWorkspaceId }: IResolveRedirectTargetOptions): Promise<string | null>;
|
|
32
|
+
export declare function resolveRedirectTarget({ apps, ctx, pathname, search, fetchFirstWorkspaceId }: IResolveRedirectTargetOptions): Promise<string | null>;
|
|
31
33
|
//# sourceMappingURL=redirectLogic.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redirectLogic.d.ts","sourceRoot":"","sources":["../../src/loader/redirectLogic.ts"],"names":[],"mappings":"AAEA,OAAO,EAAyB,KAAK,gCAAgC,EAAE,MAAM,qBAAqB,CAAC;AACnG,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;
|
|
1
|
+
{"version":3,"file":"redirectLogic.d.ts","sourceRoot":"","sources":["../../src/loader/redirectLogic.ts"],"names":[],"mappings":"AAEA,OAAO,EAAyB,KAAK,gCAAgC,EAAE,MAAM,qBAAqB,CAAC;AACnG,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAQlF;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACvC,YAAY,OAAO,EAAE,MAAM,EAG1B;CACJ;AAED,MAAM,WAAW,6BAA6B;IAC1C;;;OAGG;IACH,IAAI,EAAE,gCAAgC,EAAE,CAAC;IACzC,GAAG,EAAE,gBAAgB,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,qBAAqB,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CAC5D;AAqFD;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CAAC,EACxC,IAAI,EACJ,GAAG,EACH,QAAQ,EACR,MAAM,EACN,qBAAqB,EACxB,EAAE,6BAA6B,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAwFxD"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// (C) 2026 GoodData Corporation
|
|
2
2
|
import { debugLog } from "../debug.js";
|
|
3
3
|
import { getLastVisitedApp, setLastVisitedApp } from "./lastVisitedApp.js";
|
|
4
|
+
import { mapBareLegacyPathToApp } from "./legacyRedirect.js";
|
|
4
5
|
import { getActiveInternalApplication, getApplicationHref } from "./routing.js";
|
|
5
6
|
/**
|
|
6
7
|
* Thrown when the current URL does not correspond to any accessible application.
|
|
@@ -89,10 +90,29 @@ function resolveOrgScopeTarget(apps, ctx, pathname) {
|
|
|
89
90
|
* @throws {AppNotFoundError} The current path maps to no accessible application (show 404)
|
|
90
91
|
* @throws {Error} Unexpected failure (show generic error screen)
|
|
91
92
|
*/
|
|
92
|
-
export async function resolveRedirectTarget({ apps, ctx, pathname, fetchFirstWorkspaceId, }) {
|
|
93
|
+
export async function resolveRedirectTarget({ apps, ctx, pathname, search, fetchFirstWorkspaceId, }) {
|
|
93
94
|
const scope = ctx.currentApplicationScope;
|
|
94
95
|
const workspaceId = ctx.currentWorkspaceId;
|
|
95
96
|
debugLog(`[host-runtime/redirect] resolveRedirectTarget: scope=${scope ?? "(none)"} workspaceId=${workspaceId ?? "(none)"} pathname=${pathname} apps=${apps.length}`);
|
|
97
|
+
// Bare legacy app landing (/dashboards, /analyze, /metrics, /modeler — no workspace in the
|
|
98
|
+
// hash; hash-bearing legacy URLs are rewritten synchronously before the host boots). The
|
|
99
|
+
// legacy standalone apps redirected these to the app in the user's first workspace, so
|
|
100
|
+
// preserve the app intent instead of falling through to the generic root redirect below
|
|
101
|
+
// (which would land on /organization or the preferred workspace app).
|
|
102
|
+
const legacyAppRouteBase = mapBareLegacyPathToApp(pathname);
|
|
103
|
+
if (legacyAppRouteBase) {
|
|
104
|
+
debugLog(`[host-runtime/redirect] bare legacy app path — fetching first workspace`);
|
|
105
|
+
const legacyWorkspaceId = await fetchFirstWorkspaceId();
|
|
106
|
+
if (!legacyWorkspaceId) {
|
|
107
|
+
debugLog("[host-runtime/redirect] no workspace available for user — throwing not-found");
|
|
108
|
+
throw new AppNotFoundError("No workspace is available for this user.");
|
|
109
|
+
}
|
|
110
|
+
// Keep the query string — the legacy bare flow preserved it through its hash replacement
|
|
111
|
+
// (e.g. /modeler?displayEditMode opened the modeler in edit mode).
|
|
112
|
+
const legacyHref = `/workspace/${legacyWorkspaceId}${legacyAppRouteBase}${search ?? ""}`;
|
|
113
|
+
debugLog(`[host-runtime/redirect] redirecting bare legacy app path → ${legacyHref}`);
|
|
114
|
+
return legacyHref;
|
|
115
|
+
}
|
|
96
116
|
if (scope === "organization") {
|
|
97
117
|
return resolveOrgScopeTarget(apps, ctx, pathname);
|
|
98
118
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gooddata/sdk-ui-pluggable-host",
|
|
3
|
-
"version": "11.50.0-alpha.
|
|
3
|
+
"version": "11.50.0-alpha.3",
|
|
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,20 +29,20 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@module-federation/runtime": "2.6.0",
|
|
31
31
|
"lodash-es": "^4.17.23",
|
|
32
|
-
"@gooddata/sdk-backend-base": "11.50.0-alpha.
|
|
33
|
-
"@gooddata/sdk-backend-
|
|
34
|
-
"@gooddata/sdk-embedding": "11.50.0-alpha.
|
|
35
|
-
"@gooddata/sdk-backend-
|
|
36
|
-
"@gooddata/sdk-
|
|
37
|
-
"@gooddata/sdk-model": "11.50.0-alpha.
|
|
38
|
-
"@gooddata/sdk-ui": "11.50.0-alpha.
|
|
39
|
-
"@gooddata/sdk-ui-application-header": "11.50.0-alpha.
|
|
40
|
-
"@gooddata/sdk-ui-ext": "11.50.0-alpha.
|
|
41
|
-
"@gooddata/sdk-ui-
|
|
42
|
-
"@gooddata/sdk-ui-
|
|
43
|
-
"@gooddata/sdk-ui-
|
|
44
|
-
"@gooddata/sdk-ui-
|
|
45
|
-
"@gooddata/util": "11.50.0-alpha.
|
|
32
|
+
"@gooddata/sdk-backend-base": "11.50.0-alpha.3",
|
|
33
|
+
"@gooddata/sdk-backend-tiger": "11.50.0-alpha.3",
|
|
34
|
+
"@gooddata/sdk-embedding": "11.50.0-alpha.3",
|
|
35
|
+
"@gooddata/sdk-backend-spi": "11.50.0-alpha.3",
|
|
36
|
+
"@gooddata/sdk-model": "11.50.0-alpha.3",
|
|
37
|
+
"@gooddata/sdk-pluggable-application-model": "11.50.0-alpha.3",
|
|
38
|
+
"@gooddata/sdk-ui": "11.50.0-alpha.3",
|
|
39
|
+
"@gooddata/sdk-ui-application-header": "11.50.0-alpha.3",
|
|
40
|
+
"@gooddata/sdk-ui-ext": "11.50.0-alpha.3",
|
|
41
|
+
"@gooddata/sdk-ui-gen-ai": "11.50.0-alpha.3",
|
|
42
|
+
"@gooddata/sdk-ui-kit": "11.50.0-alpha.3",
|
|
43
|
+
"@gooddata/sdk-ui-semantic-search": "11.50.0-alpha.3",
|
|
44
|
+
"@gooddata/sdk-ui-theme-provider": "11.50.0-alpha.3",
|
|
45
|
+
"@gooddata/util": "11.50.0-alpha.3"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@microsoft/api-documenter": "^7.17.0",
|
|
@@ -85,8 +85,8 @@
|
|
|
85
85
|
"vite": "8.0.16",
|
|
86
86
|
"vitest": "4.1.8",
|
|
87
87
|
"vitest-dom": "0.1.1",
|
|
88
|
-
"@gooddata/
|
|
89
|
-
"@gooddata/
|
|
88
|
+
"@gooddata/oxlint-config": "11.50.0-alpha.3",
|
|
89
|
+
"@gooddata/eslint-config": "11.50.0-alpha.3"
|
|
90
90
|
},
|
|
91
91
|
"peerDependencies": {
|
|
92
92
|
"react": ">=18.3.1",
|