@gooddata/sdk-ui-pluggable-host 11.40.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/LICENSE +19 -0
- package/README.md +20 -0
- package/esm/assets/logo-white.svg +3 -0
- package/esm/components/FullScreenLoader.d.ts +1 -0
- package/esm/components/FullScreenLoader.js +8 -0
- package/esm/components/HostUiContainer.d.ts +16 -0
- package/esm/components/HostUiContainer.js +141 -0
- package/esm/components/HostUiContainer.scss +5 -0
- package/esm/components/Root.d.ts +16 -0
- package/esm/components/Root.js +64 -0
- package/esm/components/Root.scss +14 -0
- package/esm/components/lib/translations.d.ts +7 -0
- package/esm/components/lib/translations.js +64 -0
- package/esm/components/useRedirectNavigation.d.ts +7 -0
- package/esm/components/useRedirectNavigation.js +23 -0
- package/esm/components/useRedirectTarget.d.ts +19 -0
- package/esm/components/useRedirectTarget.js +62 -0
- package/esm/debug.d.ts +9 -0
- package/esm/debug.js +18 -0
- package/esm/index.d.ts +11 -0
- package/esm/index.js +10 -0
- package/esm/lib/chunkReloadGuard.d.ts +89 -0
- package/esm/lib/chunkReloadGuard.js +203 -0
- package/esm/lib/hostNotifications.d.ts +20 -0
- package/esm/lib/hostNotifications.js +50 -0
- package/esm/lib/isProduction.d.ts +12 -0
- package/esm/lib/isProduction.js +13 -0
- package/esm/loader/lastVisitedApp.d.ts +11 -0
- package/esm/loader/lastVisitedApp.js +43 -0
- package/esm/loader/localLoader.d.ts +16 -0
- package/esm/loader/localLoader.js +38 -0
- package/esm/loader/pluggableApplicationsLoader.d.ts +13 -0
- package/esm/loader/pluggableApplicationsLoader.js +55 -0
- package/esm/loader/redirectLogic.d.ts +30 -0
- package/esm/loader/redirectLogic.js +143 -0
- package/esm/loader/remoteLoader.d.ts +5 -0
- package/esm/loader/remoteLoader.js +117 -0
- package/esm/loader/remoteUrlSecurity.d.ts +1 -0
- package/esm/loader/remoteUrlSecurity.js +26 -0
- package/esm/loader/routing.d.ts +22 -0
- package/esm/loader/routing.js +87 -0
- package/esm/platformContext/backend.d.ts +44 -0
- package/esm/platformContext/backend.js +131 -0
- package/esm/platformContext/bootstrap.d.ts +15 -0
- package/esm/platformContext/bootstrap.js +122 -0
- package/esm/platformContext/loadPlatformContext.d.ts +18 -0
- package/esm/platformContext/loadPlatformContext.js +50 -0
- package/esm/platformContext/tigerNotAuthenticatedHandler.d.ts +3 -0
- package/esm/platformContext/tigerNotAuthenticatedHandler.js +16 -0
- package/esm/platformContext/types.d.ts +17 -0
- package/esm/platformContext/types.js +2 -0
- package/esm/platformContext/useLoadPlatformContext.d.ts +35 -0
- package/esm/platformContext/useLoadPlatformContext.js +131 -0
- package/esm/platformContext/useWorkspacePermissions.d.ts +26 -0
- package/esm/platformContext/useWorkspacePermissions.js +52 -0
- package/esm/platformContext/useWorkspaceSettings.d.ts +25 -0
- package/esm/platformContext/useWorkspaceSettings.js +46 -0
- package/esm/registry/pluggableApplicationsRegistry.d.ts +55 -0
- package/esm/registry/pluggableApplicationsRegistry.js +203 -0
- package/esm/sdk-ui-pluggable-host.d.ts +262 -0
- package/esm/styles/global.css +16 -0
- package/esm/translations/de-DE.json +34 -0
- package/esm/translations/en-AU.json +34 -0
- package/esm/translations/en-GB.json +34 -0
- package/esm/translations/en-US.json +130 -0
- package/esm/translations/es-419.json +34 -0
- package/esm/translations/es-ES.json +34 -0
- package/esm/translations/fi-FI.json +34 -0
- package/esm/translations/fr-CA.json +34 -0
- package/esm/translations/fr-FR.json +34 -0
- package/esm/translations/id-ID.json +34 -0
- package/esm/translations/it-IT.json +34 -0
- package/esm/translations/ja-JP.json +34 -0
- package/esm/translations/ko-KR.json +34 -0
- package/esm/translations/nl-NL.json +34 -0
- package/esm/translations/pl-PL.json +34 -0
- package/esm/translations/pt-BR.json +34 -0
- package/esm/translations/pt-PT.json +34 -0
- package/esm/translations/ru-RU.json +34 -0
- package/esm/translations/sl-SI.json +34 -0
- package/esm/translations/th-TH.json +34 -0
- package/esm/translations/tr-TR.json +34 -0
- package/esm/translations/uk-UA.json +34 -0
- package/esm/translations/vi-VN.json +34 -0
- package/esm/translations/zh-HK.json +34 -0
- package/esm/translations/zh-Hans.json +34 -0
- package/esm/translations/zh-Hant.json +34 -0
- package/esm/tsdoc-metadata.json +11 -0
- package/esm/types/lifecycle.d.ts +18 -0
- package/esm/types/lifecycle.js +2 -0
- package/esm/ui/DefaultHostUi.d.ts +12 -0
- package/esm/ui/DefaultHostUi.js +101 -0
- package/esm/ui/DefaultHostUi.scss +8 -0
- package/esm/ui/GenAIChat.d.ts +43 -0
- package/esm/ui/GenAIChat.js +102 -0
- package/esm/ui/HostChrome.d.ts +19 -0
- package/esm/ui/HostChrome.js +115 -0
- package/esm/ui/HostChrome.scss +24 -0
- package/esm/ui/HostIntlProvider.d.ts +9 -0
- package/esm/ui/HostIntlProvider.js +13 -0
- package/esm/ui/HostNotificationDispatcher.d.ts +12 -0
- package/esm/ui/HostNotificationDispatcher.js +42 -0
- package/esm/ui/PluggableApplicationRenderer.d.ts +10 -0
- package/esm/ui/PluggableApplicationRenderer.js +100 -0
- package/esm/ui/PluggableApplicationRenderer.scss +29 -0
- package/esm/ui/SemanticSearch.d.ts +23 -0
- package/esm/ui/SemanticSearch.js +46 -0
- package/esm/ui/WorkspacePicker.d.ts +9 -0
- package/esm/ui/WorkspacePicker.js +29 -0
- package/esm/ui/appMenuItems.d.ts +17 -0
- package/esm/ui/appMenuItems.js +81 -0
- package/esm/ui/chromeHelpers.d.ts +17 -0
- package/esm/ui/chromeHelpers.js +29 -0
- package/esm/ui/hostChromeBem.d.ts +1 -0
- package/esm/ui/hostChromeBem.js +3 -0
- package/esm/ui/resolveHostUiModule.d.ts +8 -0
- package/esm/ui/resolveHostUiModule.js +22 -0
- package/esm/ui/useHostChromeChat.d.ts +29 -0
- package/esm/ui/useHostChromeChat.js +38 -0
- package/esm/ui/useHostChromePricing.d.ts +52 -0
- package/esm/ui/useHostChromePricing.js +37 -0
- package/esm/ui/useHostChromeSearch.d.ts +20 -0
- package/esm/ui/useHostChromeSearch.js +18 -0
- package/esm/ui/useHostChromeWorkspaceFeatures.d.ts +19 -0
- package/esm/ui/useHostChromeWorkspaceFeatures.js +36 -0
- package/package.json +114 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// (C) 2026 GoodData Corporation
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { toPluggableApplicationWorkspacePermissions, } from "@gooddata/sdk-model";
|
|
4
|
+
const SUPPORTED_API_VERSION = "1.0";
|
|
5
|
+
let registeredLocalApplications;
|
|
6
|
+
/**
|
|
7
|
+
* Registers the local pluggable applications manifest. Called by the host or harness
|
|
8
|
+
* before rendering.
|
|
9
|
+
*
|
|
10
|
+
* @alpha
|
|
11
|
+
*/
|
|
12
|
+
export function registerLocalApplications(registry) {
|
|
13
|
+
registeredLocalApplications = registry;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* @internal - exported for testing
|
|
17
|
+
*/
|
|
18
|
+
export function getRemoteRegistry(ctx) {
|
|
19
|
+
const raw = ctx.userSettings.registeredPluggableApplications;
|
|
20
|
+
if (raw?.apiVersion === undefined) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
if (raw.apiVersion !== SUPPORTED_API_VERSION) {
|
|
24
|
+
console.error(`[host-runtime/registry] Remote registry apiVersion "${raw.apiVersion}" is not supported. ` +
|
|
25
|
+
`Expected "${SUPPORTED_API_VERSION}". Remote registry will be ignored.`);
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
return raw;
|
|
29
|
+
}
|
|
30
|
+
function getLocalApplications() {
|
|
31
|
+
if (!registeredLocalApplications) {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
if (registeredLocalApplications.apiVersion !== SUPPORTED_API_VERSION) {
|
|
35
|
+
console.error(`[host-runtime/registry] Local registry apiVersion "${registeredLocalApplications.apiVersion}" ` +
|
|
36
|
+
`is not supported. Expected "${SUPPORTED_API_VERSION}". Local registry will be ignored.`);
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
return registeredLocalApplications.applications;
|
|
40
|
+
}
|
|
41
|
+
function mergeApplications(localApps, remoteApps) {
|
|
42
|
+
const seenIds = new Set();
|
|
43
|
+
const merged = [];
|
|
44
|
+
for (const app of localApps) {
|
|
45
|
+
if (seenIds.has(app.id)) {
|
|
46
|
+
console.error(`[host-runtime/registry] Duplicate application ID "${app.id}" in local registry, skipping.`);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
seenIds.add(app.id);
|
|
50
|
+
merged.push(app);
|
|
51
|
+
}
|
|
52
|
+
for (const app of remoteApps) {
|
|
53
|
+
if (seenIds.has(app.id)) {
|
|
54
|
+
console.error(`[host-runtime/registry] Duplicate application ID "${app.id}" from remote registry, ` +
|
|
55
|
+
`local application takes precedence.`);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
seenIds.add(app.id);
|
|
59
|
+
merged.push(app);
|
|
60
|
+
}
|
|
61
|
+
return merged;
|
|
62
|
+
}
|
|
63
|
+
function applyOverrides(apps, overrides) {
|
|
64
|
+
if (!overrides || Object.keys(overrides).length === 0) {
|
|
65
|
+
return apps;
|
|
66
|
+
}
|
|
67
|
+
return apps.map((app) => {
|
|
68
|
+
const override = overrides[app.id];
|
|
69
|
+
if (!override) {
|
|
70
|
+
return app;
|
|
71
|
+
}
|
|
72
|
+
return { ...app, ...override };
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function sortByMenuOrder(apps) {
|
|
76
|
+
return [...apps].sort((a, b) => a.menuOrder - b.menuOrder);
|
|
77
|
+
}
|
|
78
|
+
function filterByAllowedList(apps, allowedIds) {
|
|
79
|
+
if (allowedIds === undefined) {
|
|
80
|
+
return apps;
|
|
81
|
+
}
|
|
82
|
+
const allowedSet = new Set(allowedIds);
|
|
83
|
+
return apps.filter((app) => allowedSet.has(app.id));
|
|
84
|
+
}
|
|
85
|
+
function filterDisabled(apps) {
|
|
86
|
+
// isEnabled is optional but "true" is the default value
|
|
87
|
+
return apps.filter((app) => app.isEnabled === undefined || app.isEnabled);
|
|
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
|
+
function entitlementsToRecord(entitlements) {
|
|
114
|
+
return Object.fromEntries((entitlements ?? []).map((e) => [e.name, e.value === undefined ? true : e.value]));
|
|
115
|
+
}
|
|
116
|
+
function toPluggableApplicationOrganizationPermissions(permissions) {
|
|
117
|
+
return {
|
|
118
|
+
canManageOrganization: permissions.canManageOrganization ?? false,
|
|
119
|
+
canCreateDevToken: permissions.canCreateDevToken ?? false,
|
|
120
|
+
hasBaseUiAccess: permissions.hasBaseUiAccess ?? false,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function appMeetsRequirements({ requiredSettings, requiredWorkspacePermissions, requiredOrganizationPermissions, requiredEntitlements, }, ctx) {
|
|
124
|
+
if (requiredSettings !== undefined && !evaluateCondition(requiredSettings, ctx.settings)) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
if (requiredWorkspacePermissions !== undefined) {
|
|
128
|
+
const wsPerms = ctx.workspacePermissions
|
|
129
|
+
? toPluggableApplicationWorkspacePermissions(ctx.workspacePermissions)
|
|
130
|
+
: undefined;
|
|
131
|
+
if (!evaluateCondition(requiredWorkspacePermissions, wsPerms)) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (requiredOrganizationPermissions !== undefined &&
|
|
136
|
+
!evaluateCondition(requiredOrganizationPermissions, toPluggableApplicationOrganizationPermissions(ctx.organizationPermissions ?? {}))) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
if (requiredEntitlements !== undefined) {
|
|
140
|
+
const entitlementMap = entitlementsToRecord(ctx.entitlements);
|
|
141
|
+
if (!evaluateCondition(requiredEntitlements, entitlementMap)) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
function filterByRequirements(apps, ctx) {
|
|
148
|
+
return apps.filter((app) => appMeetsRequirements(app, ctx));
|
|
149
|
+
}
|
|
150
|
+
function filterByScope(apps, scope) {
|
|
151
|
+
if (scope === undefined) {
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
return apps.filter((app) => app.applicationScope === scope);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Returns true when the platform context indicates that access to the standard (local) apps is not
|
|
158
|
+
* restricted. Otherwise, the user must have BASE_UI_ACCESS organization permission.
|
|
159
|
+
*
|
|
160
|
+
* BASE_UI_ACCESS is a platform-wide organization permission that gates visibility of all
|
|
161
|
+
* standard (local) applications. Remote applications are not subject to this check because
|
|
162
|
+
* they are registered explicitly by the tenant and are expected to manage their own access.
|
|
163
|
+
*/
|
|
164
|
+
function filterLocalByBaseUiAccess(localApps, ctx) {
|
|
165
|
+
if (!ctx.userSettings.restrictBaseUi) {
|
|
166
|
+
return localApps;
|
|
167
|
+
}
|
|
168
|
+
return ctx.organizationPermissions?.hasBaseUiAccess ? localApps : [];
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Resolves the final list of pluggable applications from the local and remote registries.
|
|
172
|
+
*
|
|
173
|
+
* Processing steps (in order):
|
|
174
|
+
* 1. Filter local apps by allowedStandardApplications list (if provided in remote registry)
|
|
175
|
+
* 2. Filter local apps by BASE_UI_ACCESS organization permission (if restrictBaseUi is set)
|
|
176
|
+
* 3. Merge local and remote applications - local apps are added first, duplicates (by ID) are skipped with console.error
|
|
177
|
+
* 4. Apply overrides from the remote registry to the merged list
|
|
178
|
+
* 5. Filter out disabled applications (isEnabled: false)
|
|
179
|
+
* 6. Filter by application scope - keep only apps whose applicationScope matches scope; if scope is undefined, no apps pass through
|
|
180
|
+
* 7. Filter by requirements - check requiredSettings, requiredWorkspacePermissions, requiredOrganizationPermissions, and requiredEntitlements
|
|
181
|
+
* 8. Sort by menuOrder (ascending)
|
|
182
|
+
*
|
|
183
|
+
* @param options - Resolution options; see {@link IResolveApplicationsOptions}
|
|
184
|
+
* @returns Filtered and sorted list of pluggable applications ready for display
|
|
185
|
+
*
|
|
186
|
+
* @internal - exported for testing
|
|
187
|
+
*/
|
|
188
|
+
export function resolveApplications({ localApps, remoteRegistry, ctx, scope, }) {
|
|
189
|
+
const { applications: remoteApps, overrides, allowedStandardApplications } = remoteRegistry ?? {};
|
|
190
|
+
const filteredLocal = filterLocalByBaseUiAccess(filterByAllowedList(localApps, allowedStandardApplications), ctx);
|
|
191
|
+
return sortByMenuOrder(filterByRequirements(filterByScope(filterDisabled(applyOverrides(mergeApplications(filteredLocal, remoteApps ?? []), overrides)), scope), ctx));
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Builds the resolved list of pluggable applications from the local and remote registries.
|
|
195
|
+
*/
|
|
196
|
+
export function usePluggableApplications(ctx) {
|
|
197
|
+
return useMemo(() => resolveApplications({
|
|
198
|
+
localApps: getLocalApplications(),
|
|
199
|
+
remoteRegistry: getRemoteRegistry(ctx),
|
|
200
|
+
ctx,
|
|
201
|
+
scope: ctx.currentApplicationScope,
|
|
202
|
+
}), [ctx]);
|
|
203
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { IAnalyticalBackend } from '@gooddata/sdk-backend-spi';
|
|
2
|
+
import { IHostUiNotification } from '@gooddata/sdk-pluggable-application-model';
|
|
3
|
+
import { IPlatformContext } from '@gooddata/sdk-pluggable-application-model';
|
|
4
|
+
import { IPluggableApp } from '@gooddata/sdk-pluggable-application-model';
|
|
5
|
+
import { IPluggableAppTelemetryCallbacks } from '@gooddata/sdk-pluggable-application-model';
|
|
6
|
+
import { JSX } from 'react/jsx-runtime';
|
|
7
|
+
import { LocalPluggableApplicationsRegistry } from '@gooddata/sdk-model';
|
|
8
|
+
import { ReactNode } from 'react';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Dispatches a notification into the currently mounted host UI module.
|
|
12
|
+
*
|
|
13
|
+
* @remarks
|
|
14
|
+
* If no UI is mounted yet (e.g. the host bootstrap is still in progress) the notification
|
|
15
|
+
* is queued and replayed when the host UI handle registers. The queue is capped at a small
|
|
16
|
+
* bounded size; oldest entries are dropped on overflow.
|
|
17
|
+
*
|
|
18
|
+
* @alpha
|
|
19
|
+
*/
|
|
20
|
+
export declare function dispatchHostNotification(notification: IHostUiNotification): void;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @alpha
|
|
24
|
+
*/
|
|
25
|
+
export declare function getBackend(): IAnalyticalBackend;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @alpha
|
|
29
|
+
*/
|
|
30
|
+
export declare interface IAppLifecycleCallbacks {
|
|
31
|
+
onHostUiMounted?: (durationMs: number) => void;
|
|
32
|
+
onAppNavigation?: (appId: string, pathname: string) => void;
|
|
33
|
+
onPageVisited?: (appId: string) => void;
|
|
34
|
+
onPreloadStarted?: (appId: string) => void;
|
|
35
|
+
onPreloadCompleted?: (appId: string, durationMs: number) => void;
|
|
36
|
+
onLoadStarted?: (appId: string) => void;
|
|
37
|
+
onLoadCompleted?: (appId: string, durationMs: number) => void;
|
|
38
|
+
onMountCompleted?: (appId: string, durationMs: number) => void;
|
|
39
|
+
onRendered?: (appId: string, totalDurationMs: number) => void;
|
|
40
|
+
onLoadFailed?: (appId: string, error: string) => void;
|
|
41
|
+
onUnmounted?: (appId: string) => void;
|
|
42
|
+
createTelemetryCallbacks?: (appId: string) => IPluggableAppTelemetryCallbacks;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Shape returned by a pricing extension hook. Provides the trial-info derived from
|
|
47
|
+
* entitlements + a dialog element so the host chrome can wire the upsell affordances
|
|
48
|
+
* on `<AppHeader>` without owning the dialog implementation.
|
|
49
|
+
*
|
|
50
|
+
* @alpha
|
|
51
|
+
*/
|
|
52
|
+
export declare interface IHostChromePricing {
|
|
53
|
+
/** The pricing dialog React element (or null to skip rendering). */
|
|
54
|
+
element: ReactNode;
|
|
55
|
+
/** Whether the current org is on a trial entitlement (drives upsell button visibility). */
|
|
56
|
+
isTrial: boolean;
|
|
57
|
+
/** ISO date string for the trial contract expiry, derived from entitlements. */
|
|
58
|
+
expiredDate: string;
|
|
59
|
+
/** Open the pricing dialog (wired to the upsell button click). */
|
|
60
|
+
onUpsellButtonClick: () => void;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @alpha
|
|
65
|
+
*/
|
|
66
|
+
export declare interface ILoadPlatformContextCallbacks {
|
|
67
|
+
onBootstrapError?: (error: string, context: string) => void;
|
|
68
|
+
onLoaded?: (durationMs: number) => void;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Installs a window listener for `vite:preloadError`. When fired (e.g. a chunk has
|
|
73
|
+
* been removed by a redeploy), the page is hard-reloaded once to pick up new chunk
|
|
74
|
+
* hashes from a fresh index.html.
|
|
75
|
+
*
|
|
76
|
+
* Idempotent: calling more than once registers the listener only once.
|
|
77
|
+
*
|
|
78
|
+
* @alpha
|
|
79
|
+
*/
|
|
80
|
+
export declare function installPreloadErrorHandler(): void;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Periodically polls the COMMITHASH file and fires `onNewDeployment` once when the
|
|
84
|
+
* deployed commit differs from the one the tab was loaded with. Stops polling
|
|
85
|
+
* after the first detection — the caller decides what to do (typically: show a
|
|
86
|
+
* "please reload" banner).
|
|
87
|
+
*
|
|
88
|
+
* Idempotent: calling more than once is a no-op after the first invocation.
|
|
89
|
+
*
|
|
90
|
+
* @alpha
|
|
91
|
+
*/
|
|
92
|
+
export declare function installVersionWatcher({ url, intervalMs, onNewDeployment }: IVersionWatcherOptions): void;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @alpha
|
|
96
|
+
*/
|
|
97
|
+
export declare interface IRootCallbacks {
|
|
98
|
+
onReady?: (ctx: IPlatformContext) => void;
|
|
99
|
+
onError?: (error: string, context: string) => void;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Optional callback invoked synchronously immediately before a stale-chunk hard reload.
|
|
104
|
+
* Gives the host app a chance to record telemetry before navigation cancels in-flight requests.
|
|
105
|
+
*
|
|
106
|
+
* @alpha
|
|
107
|
+
*/
|
|
108
|
+
export declare interface IStaleChunkReloadInfo {
|
|
109
|
+
/** Human-readable reason — e.g. the underlying preload error message. */
|
|
110
|
+
reason: string;
|
|
111
|
+
/** COMMITHASH of the build the tab was loaded with, or an empty string if unknown. */
|
|
112
|
+
commitHash: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Options for {@link installVersionWatcher}.
|
|
117
|
+
*
|
|
118
|
+
* @alpha
|
|
119
|
+
*/
|
|
120
|
+
export declare interface IVersionWatcherOptions {
|
|
121
|
+
/** URL of the COMMITHASH file emitted by the host build. */
|
|
122
|
+
url: string;
|
|
123
|
+
/** Poll interval in ms. Default: 5 minutes. */
|
|
124
|
+
intervalMs?: number;
|
|
125
|
+
/** Called once when a new deployment is detected. */
|
|
126
|
+
onNewDeployment: (newHash: string) => void;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* @alpha
|
|
131
|
+
*/
|
|
132
|
+
export declare type LocalPluggableApplicationLoader = () => Promise<{
|
|
133
|
+
default?: IPluggableApp | unknown;
|
|
134
|
+
pluggableApp?: IPluggableApp;
|
|
135
|
+
}>;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Registers app lifecycle callbacks used by the loader (e.g. for preload telemetry).
|
|
139
|
+
* Called by the host or harness at startup.
|
|
140
|
+
*
|
|
141
|
+
* @alpha
|
|
142
|
+
*/
|
|
143
|
+
export declare function registerAppLifecycleCallbacks(callbacks: IAppLifecycleCallbacks): void;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Registers the local application loaders map. Called by the host or harness
|
|
147
|
+
* before any app loading occurs.
|
|
148
|
+
*
|
|
149
|
+
* @alpha
|
|
150
|
+
*/
|
|
151
|
+
export declare function registerLocalApplicationLoaders(loaders: Record<string, LocalPluggableApplicationLoader>): void;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Registers the local pluggable applications manifest. Called by the host or harness
|
|
155
|
+
* before rendering.
|
|
156
|
+
*
|
|
157
|
+
* @alpha
|
|
158
|
+
*/
|
|
159
|
+
export declare function registerLocalApplications(registry: LocalPluggableApplicationsRegistry): void;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Registers the host's platform-context lifecycle callbacks. Must be called once at
|
|
163
|
+
* host boot before `<Root>` is rendered so the very first load can report telemetry
|
|
164
|
+
* / errors back to the host application.
|
|
165
|
+
*
|
|
166
|
+
* @alpha
|
|
167
|
+
*/
|
|
168
|
+
export declare function registerPlatformContextCallbacks(callbacks: ILoadPlatformContextCallbacks): void;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Triggers a hard page reload once, guarded against loops by sessionStorage.
|
|
172
|
+
*
|
|
173
|
+
* If the same COMMITHASH already triggered a reload within the last 30 seconds,
|
|
174
|
+
* the call is a no-op so the user is not stuck reloading a broken build.
|
|
175
|
+
*
|
|
176
|
+
* @remarks
|
|
177
|
+
* Uses `location.replace` with a cache-busting query parameter rather than
|
|
178
|
+
* `location.reload()`. A soft reload honours the HTTP cache, which has bitten
|
|
179
|
+
* us when an intermediate chunk (max-age=30d) is still served from cache and
|
|
180
|
+
* keeps replaying a stale module graph after the deploy moved forward. Writing
|
|
181
|
+
* a unique URL forces the browser to treat it as a fresh navigation.
|
|
182
|
+
*
|
|
183
|
+
* @alpha
|
|
184
|
+
*/
|
|
185
|
+
export declare function reloadForStaleChunks(reason: string): void;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @alpha
|
|
189
|
+
*/
|
|
190
|
+
export declare function Root({ callbacks }: {
|
|
191
|
+
callbacks?: IRootCallbacks;
|
|
192
|
+
}): JSX.Element;
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Registers a pricing extension that the default host chrome will consume to render
|
|
196
|
+
* trial-upgrade affordances. Optional — when no extension is registered, the chrome
|
|
197
|
+
* shows no upsell button and no pricing dialog.
|
|
198
|
+
*
|
|
199
|
+
* @remarks
|
|
200
|
+
* This indirection keeps the chrome free of any hard dependency on a specific pricing
|
|
201
|
+
* dialog implementation (e.g. `gdc-pricing-dialog`). The host app supplies
|
|
202
|
+
* the extension at boot; removing the extension is a one-line change.
|
|
203
|
+
*
|
|
204
|
+
* Contract:
|
|
205
|
+
* - MUST be called once before the first render of `<Root>`.
|
|
206
|
+
* - The supplied function MUST obey React Rules of Hooks — it is invoked from
|
|
207
|
+
* `HostChrome`'s render.
|
|
208
|
+
* - Swapping or unregistering the extension after first render is unsupported and
|
|
209
|
+
* will violate Rules of Hooks.
|
|
210
|
+
*
|
|
211
|
+
* @alpha
|
|
212
|
+
*/
|
|
213
|
+
export declare function setHostPricingExtension(extension: UseHostPricingExtension | undefined): void;
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Sets the package name reported to the backend for telemetry.
|
|
217
|
+
* Must be called before the first backend request (i.e., before Root renders).
|
|
218
|
+
*
|
|
219
|
+
* @alpha
|
|
220
|
+
*/
|
|
221
|
+
export declare function setRuntimePackageName(name: string): void;
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Registers a listener invoked synchronously just before a stale-chunk hard reload,
|
|
225
|
+
* intended for telemetry. Called only when the reload actually happens — skipped
|
|
226
|
+
* reloads (loop-guard hits) do not fire the listener.
|
|
227
|
+
*
|
|
228
|
+
* @remarks
|
|
229
|
+
* Trackers should send the event via `navigator.sendBeacon` or `fetch` with `keepalive`
|
|
230
|
+
* because the page is navigating away immediately afterwards.
|
|
231
|
+
*
|
|
232
|
+
* @alpha
|
|
233
|
+
*/
|
|
234
|
+
export declare function setStaleChunkReloadListener(listener: StaleChunkReloadListener | undefined): void;
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Query-string parameter appended to the URL on a stale-chunk reload. The value
|
|
238
|
+
* is a timestamp; its only purpose is to make the navigation target a URL the
|
|
239
|
+
* browser has never seen before, defeating any cached HTML/remoteEntry that
|
|
240
|
+
* might otherwise replay the stale module graph.
|
|
241
|
+
*
|
|
242
|
+
* Exported so callers and tests can recognise (and strip) the marker if needed.
|
|
243
|
+
*
|
|
244
|
+
* @alpha
|
|
245
|
+
*/
|
|
246
|
+
export declare const STALE_CHUNK_RELOAD_PARAM = "_gdcr";
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* @alpha
|
|
250
|
+
*/
|
|
251
|
+
export declare type StaleChunkReloadListener = (info: IStaleChunkReloadInfo) => void;
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* A host pricing extension. The `Use` prefix signals that this MUST be a React hook —
|
|
255
|
+
* it is invoked from `HostChrome`'s render and therefore must obey the React Rules of
|
|
256
|
+
* Hooks (stable call order, no conditional hook calls, etc.).
|
|
257
|
+
*
|
|
258
|
+
* @alpha
|
|
259
|
+
*/
|
|
260
|
+
export declare type UseHostPricingExtension = (ctx: IPlatformContext, locale: string) => IHostChromePricing;
|
|
261
|
+
|
|
262
|
+
export { }
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/* (C) 2026 GoodData Corporation */
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Base typography for the host app. Portaled components (GenAIChat, dialogs)
|
|
5
|
+
* render at document.body level and inherit these rules. Without them, browser
|
|
6
|
+
* defaults (16px / normal) apply, causing buttons and text to be oversized.
|
|
7
|
+
*/
|
|
8
|
+
html,
|
|
9
|
+
body {
|
|
10
|
+
font-family: var(--gd-font-family), gdcustomfont, Avenir, "Helvetica Neue", arial, sans-serif;
|
|
11
|
+
font-size: 14px;
|
|
12
|
+
line-height: 1.4rem;
|
|
13
|
+
color: var(--gd-palette-complementary-8, #464e56);
|
|
14
|
+
margin: 0;
|
|
15
|
+
padding: 0;
|
|
16
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"gs.header.help": "Hilfe",
|
|
3
|
+
"gs.header.help.label": "Hilfe-Links",
|
|
4
|
+
"gs.header.logout": "Abmelden",
|
|
5
|
+
"gs.header.menu": "Menü",
|
|
6
|
+
"gs.header.accessibility.label": "Globale Anwendungskopfzeile",
|
|
7
|
+
"gs.header.href.accessibility": "Zur Startseite",
|
|
8
|
+
"gs.header.logo.title.accessibility": "{organizationName} logo",
|
|
9
|
+
"gs.header.mainMenu.ariaLabel": "Hauptmenü",
|
|
10
|
+
"gs.header.menu.accessibility.label": "Globale Navigation",
|
|
11
|
+
"gs.header.account.title": "Konto",
|
|
12
|
+
"gs.header.slack": "Slack",
|
|
13
|
+
"gs.header.community": "Gemeinschaft",
|
|
14
|
+
"gs.header.university": "Universität",
|
|
15
|
+
"gs.header.documentation": "Dokumentation",
|
|
16
|
+
"gs.host.error.applicationFailedToLoad": "Anwendung konnte nicht geladen werden",
|
|
17
|
+
"gs.host.error.failedToLoad": "Konnte nicht geladen werden",
|
|
18
|
+
"gs.host.error.pageNotFound": "Seite nicht gefunden",
|
|
19
|
+
"gs.host.error.pageNotFoundDescription": "Die Seite, die Sie suchen, existiert nicht oder Sie haben keinen Zugriff darauf.",
|
|
20
|
+
"gs.host.error.somethingWentWrong": "Ein unvorhergesehener Fehler ist aufgetreten.",
|
|
21
|
+
"gs.host.notification.newDeployment.message": "Eine neue Version von GoodData ist verfügbar.",
|
|
22
|
+
"gs.host.notification.newDeployment.reloadLink": "Neu laden",
|
|
23
|
+
"gs.header.helpMenu.gettingStarted": "Erste Schritte",
|
|
24
|
+
"gs.header.helpMenu.connectData": "Daten verbinden",
|
|
25
|
+
"gs.header.helpMenu.manage.ws": "Workspaces und Workspace-Hierarchie verwalten",
|
|
26
|
+
"gs.header.helpMenu.manage.user": "Benutzer und Benutzergruppen verwalten",
|
|
27
|
+
"messages.genAi.visualisation.saved.success": "Großartig! Wir haben Ihre Visualisierung gespeichert.",
|
|
28
|
+
"messages.genAi.visualisation.saved.error": "Hoppla, beim Speichern Ihrer Visualisierung ist ein Problem aufgetreten.",
|
|
29
|
+
"messages.genAi.visualisation.saved.error.detail": "{errorType}: {errorMessage}",
|
|
30
|
+
"messages.genAi.visualisation.link.copied": "Der Visualisierungslink wurde in Ihre Zwischenablage kopiert.",
|
|
31
|
+
"messages.showMore": "Mehr anzeigen",
|
|
32
|
+
"messages.showLess": "Weniger anzeigen",
|
|
33
|
+
"gen-ai.ask-assistant.search": "Erstellen Sie eine neue Visualisierung basierend auf: {question}"
|
|
34
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"gs.header.help": "Help",
|
|
3
|
+
"gs.header.help.label": "Help links",
|
|
4
|
+
"gs.header.logout": "Log out",
|
|
5
|
+
"gs.header.menu": "Menu",
|
|
6
|
+
"gs.header.accessibility.label": "Global application header",
|
|
7
|
+
"gs.header.href.accessibility": "Go to homepage",
|
|
8
|
+
"gs.header.logo.title.accessibility": "{organizationName} logo",
|
|
9
|
+
"gs.header.mainMenu.ariaLabel": "Main menu",
|
|
10
|
+
"gs.header.menu.accessibility.label": "Global navigation",
|
|
11
|
+
"gs.header.account.title": "Account",
|
|
12
|
+
"gs.header.slack": "Slack",
|
|
13
|
+
"gs.header.community": "Community",
|
|
14
|
+
"gs.header.university": "University",
|
|
15
|
+
"gs.header.documentation": "Documentation",
|
|
16
|
+
"gs.host.error.applicationFailedToLoad": "Application failed to load",
|
|
17
|
+
"gs.host.error.failedToLoad": "Failed to load",
|
|
18
|
+
"gs.host.error.pageNotFound": "Page not found",
|
|
19
|
+
"gs.host.error.pageNotFoundDescription": "The page you are looking for does not exist or you do not have access to it.",
|
|
20
|
+
"gs.host.error.somethingWentWrong": "Something went wrong",
|
|
21
|
+
"gs.host.notification.newDeployment.message": "A new version of GoodData is available.",
|
|
22
|
+
"gs.host.notification.newDeployment.reloadLink": "Reload",
|
|
23
|
+
"gs.header.helpMenu.gettingStarted": "Getting started",
|
|
24
|
+
"gs.header.helpMenu.connectData": "Connecting data",
|
|
25
|
+
"gs.header.helpMenu.manage.ws": "Managing workspaces and workspace hierarchy",
|
|
26
|
+
"gs.header.helpMenu.manage.user": "Managing users and user groups",
|
|
27
|
+
"messages.genAi.visualisation.saved.success": "Great! We've saved your visualisation.",
|
|
28
|
+
"messages.genAi.visualisation.saved.error": "Unfortunately, there was an issue when saving your visualisation.",
|
|
29
|
+
"messages.genAi.visualisation.saved.error.detail": "{errorType}: {errorMessage}",
|
|
30
|
+
"messages.genAi.visualisation.link.copied": "The visualisation link has been copied to your clipboard.",
|
|
31
|
+
"messages.showMore": "Show more",
|
|
32
|
+
"messages.showLess": "Show less",
|
|
33
|
+
"gen-ai.ask-assistant.search": "Create new visualisation based on: {question}"
|
|
34
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"gs.header.help": "Help",
|
|
3
|
+
"gs.header.help.label": "Assistance links",
|
|
4
|
+
"gs.header.logout": "Log out",
|
|
5
|
+
"gs.header.menu": "Menu",
|
|
6
|
+
"gs.header.accessibility.label": "Global application header",
|
|
7
|
+
"gs.header.href.accessibility": "Go to homepage",
|
|
8
|
+
"gs.header.logo.title.accessibility": "{organizationName} logo",
|
|
9
|
+
"gs.header.mainMenu.ariaLabel": "Main Menu",
|
|
10
|
+
"gs.header.menu.accessibility.label": "Global navigation",
|
|
11
|
+
"gs.header.account.title": "Account",
|
|
12
|
+
"gs.header.slack": "Slack",
|
|
13
|
+
"gs.header.community": "Community",
|
|
14
|
+
"gs.header.university": "University",
|
|
15
|
+
"gs.header.documentation": "Documentation",
|
|
16
|
+
"gs.host.error.applicationFailedToLoad": "Application failed to load",
|
|
17
|
+
"gs.host.error.failedToLoad": "Failed to load",
|
|
18
|
+
"gs.host.error.pageNotFound": "Page not found",
|
|
19
|
+
"gs.host.error.pageNotFoundDescription": "The page you are looking for does not exist or you do not have access to it.",
|
|
20
|
+
"gs.host.error.somethingWentWrong": "Something went wrong",
|
|
21
|
+
"gs.host.notification.newDeployment.message": "A new version of GoodData is available.",
|
|
22
|
+
"gs.host.notification.newDeployment.reloadLink": "Reload",
|
|
23
|
+
"gs.header.helpMenu.gettingStarted": "Getting started",
|
|
24
|
+
"gs.header.helpMenu.connectData": "Connecting data",
|
|
25
|
+
"gs.header.helpMenu.manage.ws": "Managing workspaces and workspace hierarchy",
|
|
26
|
+
"gs.header.helpMenu.manage.user": "Managing users and user groups",
|
|
27
|
+
"messages.genAi.visualisation.saved.success": "Great! We saved your visualisation.",
|
|
28
|
+
"messages.genAi.visualisation.saved.error": "An error occurred while saving your visualisation.",
|
|
29
|
+
"messages.genAi.visualisation.saved.error.detail": "{errorType}: {errorMessage}",
|
|
30
|
+
"messages.genAi.visualisation.link.copied": "The visualisation link has been copied to your clipboard.",
|
|
31
|
+
"messages.showMore": "Show more",
|
|
32
|
+
"messages.showLess": "Show less",
|
|
33
|
+
"gen-ai.ask-assistant.search": "Build new visualisation based on: {question}"
|
|
34
|
+
}
|