feat(webui): govern actions, quick access, and metrics

Refs #264, #285, #289
This commit is contained in:
2026-08-19 18:47:46 +02:00
parent 41db78c201
commit ffaab543d2
31 changed files with 753 additions and 125 deletions
+51 -6
View File
@@ -8,7 +8,9 @@ import type {
import type { TemporalDataSelection } from "./temporal";
export const QUICK_ACCESS_LAUNCH_CONTEXT_VERSION = "1" as const;
export const QUICK_ACCESS_LAUNCH_CONTEXT_VERSION = "2" as const;
export const QUICK_ACCESS_REFERENCE_CONTRACT_VERSION = "1" as const;
export const QUICK_ACCESS_RESULT_CONTRACT_VERSION = "1" as const;
export const QUICK_ACCESS_LAUNCH_STATE_KEY = "govoplanQuickAccessLaunch";
export const QUICK_ACCESS_RESULT_EVENT = "govoplan:quick-access-result";
@@ -37,6 +39,7 @@ export function createQuickAccessLaunchContext({
const principal = auth.principal;
return {
contractVersion: QUICK_ACCESS_LAUNCH_CONTEXT_VERSION,
referenceContractVersion: QUICK_ACCESS_REFERENCE_CONTRACT_VERSION,
origin: {
pathname: normalizePathname(pathname),
search: normalizeSearch(search),
@@ -61,7 +64,9 @@ export function createQuickAccessLaunchContext({
? {
viewId: viewContext.activeViewId,
revisionId: viewContext.activeRevisionId,
name: viewContext.activeViewName
name: viewContext.activeViewName,
recommendedToolIds: viewContext.presentation?.quickAccessRecommendedToolIds ?? [],
focusedToolIds: viewContext.presentation?.quickAccessFocusedToolIds ?? []
}
: null
};
@@ -78,7 +83,11 @@ export function quickAccessLaunchContextFromState(
): QuickAccessLaunchContext | null {
if (!isRecord(value)) return null;
const candidate = value[QUICK_ACCESS_LAUNCH_STATE_KEY];
if (!isRecord(candidate) || candidate.contractVersion !== QUICK_ACCESS_LAUNCH_CONTEXT_VERSION) {
if (
!isRecord(candidate)
|| candidate.contractVersion !== QUICK_ACCESS_LAUNCH_CONTEXT_VERSION
|| candidate.referenceContractVersion !== QUICK_ACCESS_REFERENCE_CONTRACT_VERSION
) {
return null;
}
if (!isRecord(candidate.origin) || typeof candidate.origin.pathname !== "string") {
@@ -101,12 +110,48 @@ export function quickAccessReturnPath(context: QuickAccessLaunchContext): string
export function dispatchQuickAccessResult(
toolId: string,
launchContext: QuickAccessLaunchContext,
result: QuickAccessResult
): void {
if (typeof window === "undefined") return;
result: QuickAccessResult,
returnedReferenceKinds?: readonly string[]
): boolean {
if (
typeof window === "undefined"
|| !isQuickAccessResultAllowed(result, launchContext, returnedReferenceKinds)
) return false;
window.dispatchEvent(new CustomEvent(QUICK_ACCESS_RESULT_EVENT, {
detail: { toolId, launchContext, result }
}));
return true;
}
export function isQuickAccessResultAllowed(
result: QuickAccessResult,
launchContext: QuickAccessLaunchContext,
returnedReferenceKinds?: readonly string[]
): boolean {
if (result.contractVersion !== QUICK_ACCESS_RESULT_CONTRACT_VERSION) return false;
if (result.outcome === "cancelled") {
return result.action === undefined
&& result.reference === undefined
&& (result.reason === undefined
|| (["user", "dismissed", "unavailable", "failed"] as const).includes(result.reason));
}
if (result.outcome !== "completed") return false;
if (!(["created", "selected", "updated", "completed"] as const).includes(result.action)) return false;
if (!result.reference) return true;
if (
result.reference.tenantId !== launchContext.tenantId
|| !result.reference.ownerModule
|| !result.reference.kind
|| !result.reference.objectId
|| (result.reference.path !== undefined
&& result.reference.path !== null
&& (!result.reference.path.startsWith("/") || result.reference.path.startsWith("//")))
) return false;
if (returnedReferenceKinds) {
const returnedKind = `${result.reference.ownerModule}.${result.reference.kind}`;
if (!returnedReferenceKinds.includes(returnedKind)) return false;
}
return true;
}
function normalizePathname(value: string): string {
+6 -1
View File
@@ -182,6 +182,7 @@ function productAreasFromMetadata(info: PlatformModuleInfo): ProductAreaContribu
function quickAccessToolsFromMetadata(info: PlatformModuleInfo): QuickAccessToolMetadata[] {
return (info.frontend?.quick_access_tools ?? []).map((tool) => ({
contractVersion: tool.contract_version,
id: tool.id,
moduleId: tool.module_id,
categoryId: tool.category_id,
@@ -194,7 +195,11 @@ function quickAccessToolsFromMetadata(info: PlatformModuleInfo): QuickAccessTool
anyOf: tool.required_any,
order: tool.order,
defaultEnabled: tool.default_enabled,
modes: tool.modes
modes: tool.modes,
availability: tool.availability,
acceptedReferenceKinds: tool.accepted_reference_kinds,
returnedReferenceKinds: tool.returned_reference_kinds,
helpContextId: tool.help_context_id
}));
}