@gooddata/sdk-ui-pluggable-host 11.58.0 → 11.59.0-alpha.1

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.
@@ -12,13 +12,6 @@ export declare function getApplicationScopeFromPath(path: string): ApplicationSc
12
12
  */
13
13
  export declare function getWorkspaceIdFromPath(pathname: string | undefined): string | undefined;
14
14
  export declare function normalizePath(path: string): string;
15
- /**
16
- * Substitutes `{workspaceId}` placeholders in an external app URL with the
17
- * currently resolved workspace id. URLs with no placeholder pass through
18
- * unchanged, so apps that don't care about workspace context can keep using
19
- * a plain literal URL (e.g. `https://docs.example.com`).
20
- */
21
- export declare function substituteExternalUrlPlaceholders(url: string, ctx: IPlatformContext, pathname?: string): string;
22
15
  export declare function getApplicationHref(app: PluggableApplicationRegistryItem, ctx: IPlatformContext, pathname?: string): string;
23
16
  export declare function isInternalAppRouteActive(app: PluggableApplicationRegistryItem, ctx: IPlatformContext, pathname: string): boolean;
24
17
  export declare function getActiveInternalApplication(apps: PluggableApplicationRegistryItem[], ctx: IPlatformContext, pathname: string): PluggableApplicationRegistryItem | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/loader/routing.ts"],"names":[],"mappings":"AAEA,OAAO,EACH,KAAK,gBAAgB,EACrB,KAAK,gCAAgC,EAIxC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAIlF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAYtF;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAEvF;AAMD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMlD;AAgCD;;;;;GAKG;AACH,wBAAgB,iCAAiC,CAC7C,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,gBAAgB,EACrB,QAAQ,CAAC,EAAE,MAAM,GAClB,MAAM,CAMR;AAED,wBAAgB,kBAAkB,CAC9B,GAAG,EAAE,gCAAgC,EACrC,GAAG,EAAE,gBAAgB,EACrB,QAAQ,CAAC,EAAE,MAAM,GAClB,MAAM,CAWR;AAED,wBAAgB,wBAAwB,CACpC,GAAG,EAAE,gCAAgC,EACrC,GAAG,EAAE,gBAAgB,EACrB,QAAQ,EAAE,MAAM,GACjB,OAAO,CAQT;AAED,wBAAgB,4BAA4B,CACxC,IAAI,EAAE,gCAAgC,EAAE,EACxC,GAAG,EAAE,gBAAgB,EACrB,QAAQ,EAAE,MAAM,GACjB,gCAAgC,GAAG,SAAS,CAK9C"}
1
+ {"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/loader/routing.ts"],"names":[],"mappings":"AAEA,OAAO,EACH,KAAK,gBAAgB,EACrB,KAAK,gCAAgC,EAGxC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAIlF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAYtF;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAEvF;AAMD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMlD;AAUD,wBAAgB,kBAAkB,CAC9B,GAAG,EAAE,gCAAgC,EACrC,GAAG,EAAE,gBAAgB,EACrB,QAAQ,CAAC,EAAE,MAAM,GAClB,MAAM,CAER;AAED,wBAAgB,wBAAwB,CACpC,GAAG,EAAE,gCAAgC,EACrC,GAAG,EAAE,gBAAgB,EACrB,QAAQ,EAAE,MAAM,GACjB,OAAO,CAQT;AAED,wBAAgB,4BAA4B,CACxC,IAAI,EAAE,gCAAgC,EAAE,EACxC,GAAG,EAAE,gBAAgB,EACrB,QAAQ,EAAE,MAAM,GACjB,gCAAgC,GAAG,SAAS,CAK9C"}
@@ -1,5 +1,5 @@
1
1
  // (C) 2026 GoodData Corporation
2
- import { isExternalPluggableApplicationRegistryItem, isLocalPluggableApplicationRegistryItem, isRemotePluggableApplicationRegistryItem, } from "@gooddata/sdk-model";
2
+ import { getPluggableApplicationHref, isExternalPluggableApplicationRegistryItem, } from "@gooddata/sdk-model";
3
3
  const WORKSPACE_PATH_PATTERN = /^\/workspace\/(?<workspaceId>[^/]+)(?:\/|$)/;
4
4
  export function stripEmbedPrefix(pathname) {
5
5
  return pathname.startsWith("/embedded/") ? pathname.slice("/embedded".length) : pathname;
@@ -42,41 +42,8 @@ function resolveWorkspaceId(ctx, pathname) {
42
42
  }
43
43
  return getWorkspaceIdFromPath(pathname);
44
44
  }
45
- function composeInternalAppPath(app, routeBase, ctx, pathname) {
46
- const scopeBase = app.applicationScope === "organization"
47
- ? "/organization"
48
- : app.applicationScope === "workspace"
49
- ? `/workspace/${resolveWorkspaceId(ctx, pathname) ?? ""}`
50
- : undefined;
51
- if (!scopeBase) {
52
- throw new Error(`[host-runtime/routing] Unsupported application scope "${app.applicationScope}" for app "${app.id}".`);
53
- }
54
- return normalizePath(`${normalizePath(scopeBase)}${ensureLeadingSlash(routeBase)}`);
55
- }
56
- /**
57
- * Substitutes `{workspaceId}` placeholders in an external app URL with the
58
- * currently resolved workspace id. URLs with no placeholder pass through
59
- * unchanged, so apps that don't care about workspace context can keep using
60
- * a plain literal URL (e.g. `https://docs.example.com`).
61
- */
62
- export function substituteExternalUrlPlaceholders(url, ctx, pathname) {
63
- if (!url.includes("{workspaceId}")) {
64
- return url;
65
- }
66
- const workspaceId = resolveWorkspaceId(ctx, pathname) ?? "";
67
- return url.replaceAll("{workspaceId}", workspaceId);
68
- }
69
45
  export function getApplicationHref(app, ctx, pathname) {
70
- if (isExternalPluggableApplicationRegistryItem(app)) {
71
- return substituteExternalUrlPlaceholders(app.external.url, ctx, pathname);
72
- }
73
- if (isLocalPluggableApplicationRegistryItem(app)) {
74
- return composeInternalAppPath(app, app.local.routeBase, ctx, pathname);
75
- }
76
- if (isRemotePluggableApplicationRegistryItem(app)) {
77
- return composeInternalAppPath(app, app.remote.routeBase, ctx, pathname);
78
- }
79
- return "#";
46
+ return getPluggableApplicationHref(app, resolveWorkspaceId(ctx, pathname));
80
47
  }
81
48
  export function isInternalAppRouteActive(app, ctx, pathname) {
82
49
  if (isExternalPluggableApplicationRegistryItem(app)) {
@@ -1 +1 @@
1
- {"version":3,"file":"useLoadPlatformContext.d.ts","sourceRoot":"","sources":["../../src/platformContext/useLoadPlatformContext.ts"],"names":[],"mappings":"AAOA,OAAO,EAA2B,KAAK,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAM3G,OAAO,EACH,KAAK,6BAA6B,EAGrC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACH,KAAK,uBAAuB,EAC5B,KAAK,0BAA0B,EAElC,MAAM,YAAY,CAAC;AAYpB;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,IAAI,0BAA0B,CAAC,gBAAgB,CAAC,CAoJrF;AAED;;;;GAIG;AACH,MAAM,WAAW,+BAA+B;IAC5C,YAAY,CAAC,SAAS,EAAE,6BAA6B,GAAG,IAAI,CAAC;IAC7D,SAAS,EAAE,MAAM,QAAQ,CAAC,0BAA0B,CAAC,uBAAuB,CAAC,CAAC,CAAC;IAC/E,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC;IAChD,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AAmED;;GAEG;AACH,eAAO,MAAM,8BAA8B,EAAE,+BACA,CAAC;AAE9C;;;;;;GAMG;AACH,wBAAgB,gCAAgC,CAAC,SAAS,EAAE,6BAA6B,GAAG,IAAI,CAE/F"}
1
+ {"version":3,"file":"useLoadPlatformContext.d.ts","sourceRoot":"","sources":["../../src/platformContext/useLoadPlatformContext.ts"],"names":[],"mappings":"AAOA,OAAO,EAA2B,KAAK,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAO3G,OAAO,EACH,KAAK,6BAA6B,EAGrC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACH,KAAK,uBAAuB,EAC5B,KAAK,0BAA0B,EAElC,MAAM,YAAY,CAAC;AAYpB;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,IAAI,0BAA0B,CAAC,gBAAgB,CAAC,CA6JrF;AAED;;;;GAIG;AACH,MAAM,WAAW,+BAA+B;IAC5C,YAAY,CAAC,SAAS,EAAE,6BAA6B,GAAG,IAAI,CAAC;IAC7D,SAAS,EAAE,MAAM,QAAQ,CAAC,0BAA0B,CAAC,uBAAuB,CAAC,CAAC,CAAC;IAC/E,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC;IAChD,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AAmED;;GAEG;AACH,eAAO,MAAM,8BAA8B,EAAE,+BACA,CAAC;AAE9C;;;;;;GAMG;AACH,wBAAgB,gCAAgC,CAAC,SAAS,EAAE,6BAA6B,GAAG,IAAI,CAE/F"}
@@ -4,6 +4,7 @@ import { useLocation } from "react-router";
4
4
  import { isLocale } from "@gooddata/sdk-model";
5
5
  import { isProduction } from "../lib/isProduction.js";
6
6
  import { getApplicationScopeFromPath, getWorkspaceIdFromPath } from "../loader/routing.js";
7
+ import { resolveAvailableWorkspaceApplications } from "../registry/pluggableApplicationsRegistry.js";
7
8
  import { getBackend } from "./backend.js";
8
9
  import { HostApplicationDisabledError, loadPlatformContext, } from "./loadPlatformContext.js";
9
10
  import { useWorkspaceColorPalette } from "./useWorkspaceColorPalette.js";
@@ -74,7 +75,11 @@ export function useLoadPlatformContext() {
74
75
  settings,
75
76
  preferredLocale,
76
77
  };
77
- return { state: "ready", ctx: { ...backendContext.ctx, ...routeCtx, theme: effectiveTheme } };
78
+ const ctx = { ...backendContext.ctx, ...routeCtx, theme: effectiveTheme };
79
+ return {
80
+ state: "ready",
81
+ ctx: { ...ctx, availableApplications: resolveAvailableWorkspaceApplications(ctx) },
82
+ };
78
83
  }, [
79
84
  backendContext,
80
85
  applicationScope,
@@ -110,19 +115,23 @@ export function useLoadPlatformContext() {
110
115
  return undefined;
111
116
  }
112
117
  const { currentApplicationScope, currentWorkspaceId, workspacePermissions, workspaceSettings, colorPalette, settings, preferredLocale, theme, } = lastReady.result.ctx;
118
+ // Resolved again rather than carried over: the applications are derived from the CURRENT
119
+ // backend context (registry, entitlements, organization permissions), which a refresh may
120
+ // have changed even though the workspace-scoped values below are still the previous ones.
121
+ const ctx = {
122
+ ...backendContext.ctx,
123
+ currentApplicationScope,
124
+ currentWorkspaceId,
125
+ workspacePermissions,
126
+ workspaceSettings,
127
+ colorPalette,
128
+ settings,
129
+ preferredLocale,
130
+ theme,
131
+ };
113
132
  return {
114
133
  state: "ready",
115
- ctx: {
116
- ...backendContext.ctx,
117
- currentApplicationScope,
118
- currentWorkspaceId,
119
- workspacePermissions,
120
- workspaceSettings,
121
- colorPalette,
122
- settings,
123
- preferredLocale,
124
- theme,
125
- },
134
+ ctx: { ...ctx, availableApplications: resolveAvailableWorkspaceApplications(ctx) },
126
135
  };
127
136
  }, [backendContext, lastReady, workspaceId]);
128
137
  // Keep the previously rendered context while the same workspace's data refreshes (backend
@@ -28,6 +28,16 @@ interface IResolveApplicationsOptions {
28
28
  * Application scope to filter by; if undefined, no apps are returned
29
29
  */
30
30
  scope: ApplicationScope | undefined;
31
+ /**
32
+ * Leaves `requiredWorkspacePermissions` unevaluated, for callers that decide per workspace
33
+ * rather than for the workspace in `ctx`.
34
+ */
35
+ skipWorkspacePermissions?: boolean;
36
+ /**
37
+ * Leaves `requiredSettings` unevaluated, for callers that decide against another workspace's
38
+ * settings rather than those of the active scope.
39
+ */
40
+ skipSettings?: boolean;
31
41
  }
32
42
  /**
33
43
  * Resolves the final list of pluggable applications from the local and remote registries.
@@ -39,7 +49,7 @@ interface IResolveApplicationsOptions {
39
49
  * 4. Apply overrides from the remote registry to the merged list
40
50
  * 5. Filter out disabled applications (isEnabled: false)
41
51
  * 6. Filter by application scope - keep only apps whose applicationScope matches scope; if scope is undefined, no apps pass through
42
- * 7. Filter by requirements - check requiredSettings, requiredWorkspacePermissions, requiredOrganizationPermissions, and requiredEntitlements (embedded and export modes force the shell application flags on)
52
+ * 7. Filter by requirements - check requiredSettings, requiredWorkspacePermissions, requiredOrganizationPermissions, and requiredEntitlements (embedded and export modes force the shell application flags on; `skipWorkspacePermissions` leaves requiredWorkspacePermissions to the caller)
43
53
  * 8. Sort by menuOrder (ascending)
44
54
  *
45
55
  * @param options - Resolution options; see {@link IResolveApplicationsOptions}
@@ -47,7 +57,21 @@ interface IResolveApplicationsOptions {
47
57
  *
48
58
  * @internal - exported for testing
49
59
  */
50
- export declare function resolveApplications({ localApps, remoteRegistry, ctx, scope, }: IResolveApplicationsOptions): PluggableApplicationRegistryItem[];
60
+ export declare function resolveApplications({ localApps, remoteRegistry, ctx, scope, skipWorkspacePermissions, skipSettings, }: IResolveApplicationsOptions): PluggableApplicationRegistryItem[];
61
+ /**
62
+ * Resolves the workspace-scoped applications published to pluggable applications through
63
+ * {@link @gooddata/sdk-pluggable-application-model#IPlatformContextV1.availableApplications}.
64
+ *
65
+ * @remarks
66
+ * Applies every requirement that holds organization-wide and leaves the two that a single workspace
67
+ * answers — `requiredSettings` and `requiredWorkspacePermissions` — to the consumer, which evaluates
68
+ * them against each workspace it lists. A feature flag can be turned on for one workspace alone (early
69
+ * access values resolve into its settings), so deciding here would hide such an application from every
70
+ * workspace that has it.
71
+ *
72
+ * @internal
73
+ */
74
+ export declare function resolveAvailableWorkspaceApplications(ctx: IPlatformContext): PluggableApplicationRegistryItem[];
51
75
  /**
52
76
  * Builds the resolved list of pluggable applications from the local and remote registries.
53
77
  */
@@ -1 +1 @@
1
- {"version":3,"file":"pluggableApplicationsRegistry.d.ts","sourceRoot":"","sources":["../../src/registry/pluggableApplicationsRegistry.tsx"],"names":[],"mappings":"AAIA,OAAO,EACH,KAAK,gBAAgB,EAKrB,KAAK,kCAAkC,EACvC,KAAK,gCAAgC,EACrC,KAAK,mCAAmC,EAE3C,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAEH,KAAK,gBAAgB,EACxB,MAAM,2CAA2C,CAAC;AAMnD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,kCAAkC,GAAG,IAAI,CAE5F;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,gBAAgB,GAAG,mCAAmC,GAAG,SAAS,CAaxG;AA0ND,UAAU,2BAA2B;IACjC;;OAEG;IACH,SAAS,EAAE,gCAAgC,EAAE,CAAC;IAC9C;;OAEG;IACH,cAAc,EAAE,mCAAmC,GAAG,SAAS,CAAC;IAChE;;OAEG;IACH,GAAG,EAAE,gBAAgB,CAAC;IACtB;;OAEG;IACH,KAAK,EAAE,gBAAgB,GAAG,SAAS,CAAC;CACvC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CAAC,EAChC,SAAS,EACT,cAAc,EACd,GAAG,EACH,KAAK,GACR,EAAE,2BAA2B,GAAG,gCAAgC,EAAE,CAelE;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,gBAAgB,GAAG,gCAAgC,EAAE,CAWlG"}
1
+ {"version":3,"file":"pluggableApplicationsRegistry.d.ts","sourceRoot":"","sources":["../../src/registry/pluggableApplicationsRegistry.tsx"],"names":[],"mappings":"AAIA,OAAO,EACH,KAAK,gBAAgB,EAErB,KAAK,kCAAkC,EACvC,KAAK,gCAAgC,EACrC,KAAK,mCAAmC,EAG3C,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAEH,KAAK,gBAAgB,EACxB,MAAM,2CAA2C,CAAC;AAMnD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,kCAAkC,GAAG,IAAI,CAE5F;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,gBAAgB,GAAG,mCAAmC,GAAG,SAAS,CAaxG;AAsMD,UAAU,2BAA2B;IACjC;;OAEG;IACH,SAAS,EAAE,gCAAgC,EAAE,CAAC;IAC9C;;OAEG;IACH,cAAc,EAAE,mCAAmC,GAAG,SAAS,CAAC;IAChE;;OAEG;IACH,GAAG,EAAE,gBAAgB,CAAC;IACtB;;OAEG;IACH,KAAK,EAAE,gBAAgB,GAAG,SAAS,CAAC;IACpC;;;OAGG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CAAC,EAChC,SAAS,EACT,cAAc,EACd,GAAG,EACH,KAAK,EACL,wBAAgC,EAChC,YAAoB,GACvB,EAAE,2BAA2B,GAAG,gCAAgC,EAAE,CAiBlE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qCAAqC,CACjD,GAAG,EAAE,gBAAgB,GACtB,gCAAgC,EAAE,CASpC;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,gBAAgB,GAAG,gCAAgC,EAAE,CAWlG"}
@@ -1,6 +1,6 @@
1
1
  // (C) 2026 GoodData Corporation
2
2
  import { useMemo } from "react";
3
- import { toPluggableApplicationWorkspacePermissions, } from "@gooddata/sdk-model";
3
+ import { evaluateCondition, toPluggableApplicationWorkspacePermissions, } from "@gooddata/sdk-model";
4
4
  const SUPPORTED_API_VERSION = "1.0";
5
5
  let registeredLocalApplications;
6
6
  /**
@@ -86,30 +86,6 @@ function filterDisabled(apps) {
86
86
  // isEnabled is optional but "true" is the default value
87
87
  return apps.filter((app) => app.isEnabled === undefined || app.isEnabled);
88
88
  }
89
- /**
90
- * Evaluates a Condition<T> against an actual value object.
91
- *
92
- * Supports plain object (implicit AND), $or, and $and forms.
93
- */
94
- function evaluateCondition(condition, actual) {
95
- if (typeof condition === "object" && condition !== null && "$or" in condition) {
96
- return condition.$or.some((c) => evaluateCondition(c, actual));
97
- }
98
- if (typeof condition === "object" && condition !== null && "$and" in condition) {
99
- return condition.$and.every((c) => evaluateCondition(c, actual));
100
- }
101
- if (actual === undefined) {
102
- return false;
103
- }
104
- const plain = condition;
105
- const actualRecord = actual;
106
- return Object.keys(plain).every((key) => {
107
- const required = plain[key];
108
- const actual = actualRecord[key];
109
- // A missing setting is treated as falsy: undefined satisfies a requirement of false.
110
- return actual === required || (actual === undefined && required === false);
111
- });
112
- }
113
89
  function entitlementsToRecord(entitlements) {
114
90
  return Object.fromEntries((entitlements ?? []).map((e) => [e.name, e.value === undefined ? true : e.value]));
115
91
  }
@@ -133,12 +109,13 @@ function settingsForRequirements(ctx) {
133
109
  enableShellApplication_analyticalDesigner: true,
134
110
  };
135
111
  }
136
- function appMeetsRequirements({ requiredSettings, requiredWorkspacePermissions, requiredOrganizationPermissions, requiredEntitlements, }, ctx) {
112
+ function appMeetsRequirements({ requiredSettings, requiredWorkspacePermissions, requiredOrganizationPermissions, requiredEntitlements, }, ctx, skipWorkspacePermissions, skipSettings) {
137
113
  if (requiredSettings !== undefined &&
114
+ !skipSettings &&
138
115
  !evaluateCondition(requiredSettings, settingsForRequirements(ctx))) {
139
116
  return false;
140
117
  }
141
- if (requiredWorkspacePermissions !== undefined) {
118
+ if (requiredWorkspacePermissions !== undefined && !skipWorkspacePermissions) {
142
119
  const wsPerms = ctx.workspacePermissions
143
120
  ? toPluggableApplicationWorkspacePermissions(ctx.workspacePermissions)
144
121
  : undefined;
@@ -158,8 +135,8 @@ function appMeetsRequirements({ requiredSettings, requiredWorkspacePermissions,
158
135
  }
159
136
  return true;
160
137
  }
161
- function filterByRequirements(apps, ctx) {
162
- return apps.filter((app) => appMeetsRequirements(app, ctx));
138
+ function filterByRequirements(apps, ctx, skipWorkspacePermissions, skipSettings) {
139
+ return apps.filter((app) => appMeetsRequirements(app, ctx, skipWorkspacePermissions, skipSettings));
163
140
  }
164
141
  function filterByScope(apps, scope) {
165
142
  if (scope === undefined) {
@@ -191,7 +168,7 @@ function filterLocalByBaseUiAccess(localApps, ctx) {
191
168
  * 4. Apply overrides from the remote registry to the merged list
192
169
  * 5. Filter out disabled applications (isEnabled: false)
193
170
  * 6. Filter by application scope - keep only apps whose applicationScope matches scope; if scope is undefined, no apps pass through
194
- * 7. Filter by requirements - check requiredSettings, requiredWorkspacePermissions, requiredOrganizationPermissions, and requiredEntitlements (embedded and export modes force the shell application flags on)
171
+ * 7. Filter by requirements - check requiredSettings, requiredWorkspacePermissions, requiredOrganizationPermissions, and requiredEntitlements (embedded and export modes force the shell application flags on; `skipWorkspacePermissions` leaves requiredWorkspacePermissions to the caller)
195
172
  * 8. Sort by menuOrder (ascending)
196
173
  *
197
174
  * @param options - Resolution options; see {@link IResolveApplicationsOptions}
@@ -199,10 +176,33 @@ function filterLocalByBaseUiAccess(localApps, ctx) {
199
176
  *
200
177
  * @internal - exported for testing
201
178
  */
202
- export function resolveApplications({ localApps, remoteRegistry, ctx, scope, }) {
179
+ export function resolveApplications({ localApps, remoteRegistry, ctx, scope, skipWorkspacePermissions = false, skipSettings = false, }) {
203
180
  const { applications: remoteApps, overrides, allowedStandardApplications } = remoteRegistry ?? {};
204
181
  const filteredLocal = filterLocalByBaseUiAccess(filterByAllowedList(localApps, allowedStandardApplications), ctx);
205
- return sortByMenuOrder(filterByRequirements(filterByScope(filterDisabled(applyOverrides(mergeApplications(filteredLocal, remoteApps ?? []), overrides)), scope), ctx));
182
+ return sortByMenuOrder(filterByRequirements(filterByScope(filterDisabled(applyOverrides(mergeApplications(filteredLocal, remoteApps ?? []), overrides)), scope), ctx, skipWorkspacePermissions, skipSettings));
183
+ }
184
+ /**
185
+ * Resolves the workspace-scoped applications published to pluggable applications through
186
+ * {@link @gooddata/sdk-pluggable-application-model#IPlatformContextV1.availableApplications}.
187
+ *
188
+ * @remarks
189
+ * Applies every requirement that holds organization-wide and leaves the two that a single workspace
190
+ * answers — `requiredSettings` and `requiredWorkspacePermissions` — to the consumer, which evaluates
191
+ * them against each workspace it lists. A feature flag can be turned on for one workspace alone (early
192
+ * access values resolve into its settings), so deciding here would hide such an application from every
193
+ * workspace that has it.
194
+ *
195
+ * @internal
196
+ */
197
+ export function resolveAvailableWorkspaceApplications(ctx) {
198
+ return resolveApplications({
199
+ localApps: getLocalApplications(),
200
+ remoteRegistry: getRemoteRegistry(ctx),
201
+ ctx,
202
+ scope: "workspace",
203
+ skipWorkspacePermissions: true,
204
+ skipSettings: true,
205
+ });
206
206
  }
207
207
  /**
208
208
  * Builds the resolved list of pluggable applications from the local and remote registries.
@@ -1 +1 @@
1
- {"version":3,"file":"HostChrome.d.ts","sourceRoot":"","sources":["../../src/ui/HostChrome.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAuC,KAAK,SAAS,EAAwB,MAAM,OAAO,CAAC;AAElG,OAAO,EACH,KAAK,iBAAiB,EAEtB,KAAK,gCAAgC,EACxC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACH,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACxB,MAAM,2CAA2C,CAAC;AAGnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AA2B7D,OAAO,mBAAmB,CAAC;AAK3B,OAAO,0CAA0C,CAAC;AAClD,OAAO,6CAA6C,CAAC;AACrD,OAAO,sDAAsD,CAAC;AAC9D,OAAO,0DAA0D,CAAC;AAkBlE,MAAM,WAAW,gBAAgB;IAC7B,GAAG,EAAE,gBAAgB,CAAC;IACtB,oBAAoB,EAAE,gCAAgC,EAAE,CAAC;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAClC,YAAY,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC1C;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,4FAA4F;IAC5F,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,oEAAoE;IACpE,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,2FAA2F;IAC3F,gBAAgB,CAAC,EAAE,CACf,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,iBAAiB,EAC/B,YAAY,CAAC,EAAE,OAAO,EACtB,kBAAkB,CAAC,EAAE,OAAO,KAC3B,IAAI,CAAC;IACV;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACxB;AAED,wBAAgB,UAAU,CAAC,EACvB,GAAG,EACH,oBAAoB,EACpB,QAAQ,EACR,UAAU,EACV,SAAS,EAAE,UAAU,EACrB,aAAa,EACb,YAAmB,EACnB,YAAoB,EACpB,UAAkB,EAClB,QAAmB,EACnB,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,QAAQ,GACX,EAAE,gBAAgB,2CAiQlB"}
1
+ {"version":3,"file":"HostChrome.d.ts","sourceRoot":"","sources":["../../src/ui/HostChrome.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAuC,KAAK,SAAS,EAAwB,MAAM,OAAO,CAAC;AAElG,OAAO,EACH,KAAK,iBAAiB,EAEtB,KAAK,gCAAgC,EAExC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACH,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACxB,MAAM,2CAA2C,CAAC;AAGnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AA2B7D,OAAO,mBAAmB,CAAC;AAK3B,OAAO,0CAA0C,CAAC;AAClD,OAAO,6CAA6C,CAAC;AACrD,OAAO,sDAAsD,CAAC;AAC9D,OAAO,0DAA0D,CAAC;AAkBlE,MAAM,WAAW,gBAAgB;IAC7B,GAAG,EAAE,gBAAgB,CAAC;IACtB,oBAAoB,EAAE,gCAAgC,EAAE,CAAC;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAClC,YAAY,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC1C;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,4FAA4F;IAC5F,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,oEAAoE;IACpE,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,2FAA2F;IAC3F,gBAAgB,CAAC,EAAE,CACf,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,iBAAiB,EAC/B,YAAY,CAAC,EAAE,OAAO,EACtB,kBAAkB,CAAC,EAAE,OAAO,KAC3B,IAAI,CAAC;IACV;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACxB;AAED,wBAAgB,UAAU,CAAC,EACvB,GAAG,EACH,oBAAoB,EACpB,QAAQ,EACR,UAAU,EACV,SAAS,EAAE,UAAU,EACrB,aAAa,EACb,YAAmB,EACnB,YAAoB,EACpB,UAAkB,EAClB,QAAmB,EACnB,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,QAAQ,GACX,EAAE,gBAAgB,2CAmQlB"}
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // (C) 2026 GoodData Corporation
3
3
  import { useCallback, useMemo } from "react";
4
+ import { getPluggableApplicationLocalizedTitle, } from "@gooddata/sdk-model";
4
5
  import { BackendProvider, resolveLocale } from "@gooddata/sdk-ui";
5
6
  import { AppHeaderNotifications } from "@gooddata/sdk-ui-application-header";
6
7
  import { AppHeader, DocumentHeader, ToastsCenterContextProvider, generateHeaderAccountMenuItems, generateHeaderCommonHelpMenuItems, generateHeaderStaticHelpMenuItems, } from "@gooddata/sdk-ui-kit";
@@ -9,7 +10,7 @@ import defaultLogoUrl from "../assets/logo-white.svg";
9
10
  import { getAppLifecycleCallbacks } from "../loader/pluggableApplicationsLoader.js";
10
11
  import { getActiveInternalApplication } from "../loader/routing.js";
11
12
  import { getBackend } from "../platformContext/backend.js";
12
- import { buildAppMenu, getLocalizedTitle } from "./appMenuItems.js";
13
+ import { buildAppMenu } from "./appMenuItems.js";
13
14
  import { getUserDisplayName, getWorkspaceSwitchPath, isPlainLeftClick } from "./chromeHelpers.js";
14
15
  import { b, e } from "./hostChromeBem.js";
15
16
  import { HostIntlProvider } from "./HostIntlProvider.js";
@@ -134,7 +135,9 @@ export function HostChrome({ ctx, resolvedApplications, pathname, onNavigate, on
134
135
  // An app may report an empty page segment (e.g. no insight open); treat it the same as `undefined`
135
136
  // (omitted) so the tab falls back to the active application's manifest title rather than going blank.
136
137
  const documentPageTitle = appPageTitle ||
137
- (activeApplication ? getLocalizedTitle(activeApplication, ctx.preferredLocale) : undefined);
138
+ (activeApplication
139
+ ? getPluggableApplicationLocalizedTitle(activeApplication, ctx.preferredLocale)
140
+ : undefined);
138
141
  const documentBrandTitle = ctx.isExportMode
139
142
  ? undefined
140
143
  : ctx.whiteLabeling?.enabled
@@ -1,7 +1,6 @@
1
1
  import { type ILocale, type PluggableApplicationRegistryItem } from "@gooddata/sdk-model";
2
2
  import { type IPlatformContext } from "@gooddata/sdk-pluggable-application-model";
3
3
  import { type IHeaderMenuItem } from "@gooddata/sdk-ui-kit";
4
- export declare function getLocalizedTitle(app: PluggableApplicationRegistryItem, locale: ILocale | undefined): string;
5
4
  export interface IAppMenuResult {
6
5
  menuItemsGroups: IHeaderMenuItem[][];
7
6
  messages: Record<string, string>;
@@ -1 +1 @@
1
- {"version":3,"file":"appMenuItems.d.ts","sourceRoot":"","sources":["../../src/ui/appMenuItems.ts"],"names":[],"mappings":"AAIA,OAAO,EACH,KAAK,OAAO,EACZ,KAAK,gCAAgC,EAExC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAwB,KAAK,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AACxG,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AA2B5D,wBAAgB,iBAAiB,CAC7B,GAAG,EAAE,gCAAgC,EACrC,MAAM,EAAE,OAAO,GAAG,SAAS,GAC5B,MAAM,CAQR;AAED,MAAM,WAAW,cAAc;IAC3B,eAAe,EAAE,eAAe,EAAE,EAAE,CAAC;IACrC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAeD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CACxB,IAAI,EAAE,gCAAgC,EAAE,EACxC,GAAG,EAAE,gBAAgB,EACrB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,OAAO,GAAG,SAAS,GAC5B,cAAc,CA4BhB"}
1
+ {"version":3,"file":"appMenuItems.d.ts","sourceRoot":"","sources":["../../src/ui/appMenuItems.ts"],"names":[],"mappings":"AAIA,OAAO,EACH,KAAK,OAAO,EACZ,KAAK,gCAAgC,EAGxC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAwB,KAAK,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AACxG,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AA2B5D,MAAM,WAAW,cAAc;IAC3B,eAAe,EAAE,eAAe,EAAE,EAAE,CAAC;IACrC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAeD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CACxB,IAAI,EAAE,gCAAgC,EAAE,EACxC,GAAG,EAAE,gBAAgB,EACrB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,OAAO,GAAG,SAAS,GAC5B,cAAc,CA4BhB"}
@@ -1,5 +1,5 @@
1
1
  // (C) 2026 GoodData Corporation
2
- import { isExternalPluggableApplicationRegistryItem, } from "@gooddata/sdk-model";
2
+ import { getPluggableApplicationLocalizedTitle, isExternalPluggableApplicationRegistryItem, } from "@gooddata/sdk-model";
3
3
  import { DefaultApplicationId } from "@gooddata/sdk-pluggable-application-model";
4
4
  import { getApplicationHref, isInternalAppRouteActive } from "../loader/routing.js";
5
5
  import { isPlainLeftClick } from "./chromeHelpers.js";
@@ -21,15 +21,6 @@ const NAV_MSG_PREFIX = "shellApplication.menuItem.";
21
21
  function navMessageKey(appId) {
22
22
  return `${NAV_MSG_PREFIX}${appId}`;
23
23
  }
24
- export function getLocalizedTitle(app, locale) {
25
- if (locale && app.localizedTitle) {
26
- const localizedTitle = app.localizedTitle[locale];
27
- if (localizedTitle) {
28
- return localizedTitle;
29
- }
30
- }
31
- return app.title;
32
- }
33
24
  // When `gdc-home-ui` is the only organization-scoped app, it is reachable via the logo
34
25
  // and would otherwise appear as a redundant standalone "Organization" menu entry. We must
35
26
  // have this shifty logic in place to emulate the old behavior of Panther Home UI app.
@@ -63,7 +54,7 @@ export function buildAppMenu(apps, ctx, pathname, locale) {
63
54
  const href = getApplicationHref(app, ctx, pathname);
64
55
  const isActive = !isExternal && isInternalAppRouteActive(app, ctx, pathname);
65
56
  const key = navMessageKey(app.id);
66
- messages[key] = getLocalizedTitle(app, locale);
57
+ messages[key] = getPluggableApplicationLocalizedTitle(app, locale);
67
58
  return {
68
59
  key,
69
60
  href,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gooddata/sdk-ui-pluggable-host",
3
- "version": "11.58.0",
3
+ "version": "11.59.0-alpha.1",
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.58.0",
33
- "@gooddata/sdk-backend-spi": "11.58.0",
34
- "@gooddata/sdk-backend-tiger": "11.58.0",
35
- "@gooddata/sdk-embedding": "11.58.0",
36
- "@gooddata/sdk-model": "11.58.0",
37
- "@gooddata/sdk-pluggable-application-model": "11.58.0",
38
- "@gooddata/sdk-ui": "11.58.0",
39
- "@gooddata/sdk-ui-application-header": "11.58.0",
40
- "@gooddata/sdk-ui-ext": "11.58.0",
41
- "@gooddata/sdk-ui-gen-ai": "11.58.0",
42
- "@gooddata/sdk-ui-semantic-search": "11.58.0",
43
- "@gooddata/sdk-ui-kit": "11.58.0",
44
- "@gooddata/sdk-ui-theme-provider": "11.58.0",
45
- "@gooddata/util": "11.58.0"
32
+ "@gooddata/sdk-backend-tiger": "11.59.0-alpha.1",
33
+ "@gooddata/sdk-backend-base": "11.59.0-alpha.1",
34
+ "@gooddata/sdk-backend-spi": "11.59.0-alpha.1",
35
+ "@gooddata/sdk-embedding": "11.59.0-alpha.1",
36
+ "@gooddata/sdk-model": "11.59.0-alpha.1",
37
+ "@gooddata/sdk-ui": "11.59.0-alpha.1",
38
+ "@gooddata/sdk-ui-application-header": "11.59.0-alpha.1",
39
+ "@gooddata/sdk-pluggable-application-model": "11.59.0-alpha.1",
40
+ "@gooddata/sdk-ui-ext": "11.59.0-alpha.1",
41
+ "@gooddata/sdk-ui-gen-ai": "11.59.0-alpha.1",
42
+ "@gooddata/sdk-ui-kit": "11.59.0-alpha.1",
43
+ "@gooddata/sdk-ui-semantic-search": "11.59.0-alpha.1",
44
+ "@gooddata/sdk-ui-theme-provider": "11.59.0-alpha.1",
45
+ "@gooddata/util": "11.59.0-alpha.1"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@microsoft/api-documenter": "7.30.10",
@@ -81,8 +81,8 @@
81
81
  "vite": "8.0.16",
82
82
  "vitest": "4.1.11",
83
83
  "vitest-dom": "0.1.1",
84
- "@gooddata/oxlint-config": "11.58.0",
85
- "@gooddata/eslint-config": "11.58.0"
84
+ "@gooddata/eslint-config": "11.59.0-alpha.1",
85
+ "@gooddata/oxlint-config": "11.59.0-alpha.1"
86
86
  },
87
87
  "peerDependencies": {
88
88
  "react": ">=18.3.1",