pre commit fix and other fixes
This commit is contained in:
@@ -43,14 +43,30 @@ function parseEnTranslation(): Record<string, unknown> {
|
||||
}
|
||||
}
|
||||
|
||||
const enTranslationResources = parseEnTranslation();
|
||||
if (!i18next.isInitialized) {
|
||||
// initImmediate: false → initialise synchronously from the inline resources
|
||||
// (there's no async backend here), so i18next is ready before the first story
|
||||
// renders. Without it the first render can beat init and stick on raw keys.
|
||||
void i18next.use(initReactI18next).init({
|
||||
lng: "en",
|
||||
fallbackLng: "en",
|
||||
resources: { en: { translation: parseEnTranslation() } },
|
||||
resources: { en: { translation: enTranslationResources } },
|
||||
interpolation: { escapeValue: false },
|
||||
react: { useSuspense: false },
|
||||
initImmediate: false,
|
||||
});
|
||||
} else {
|
||||
// Something initialised i18next first (e.g. the app's async TOML backend):
|
||||
// inject the shipped English copy synchronously so t() never renders raw keys.
|
||||
i18next.addResourceBundle(
|
||||
"en",
|
||||
"translation",
|
||||
enTranslationResources,
|
||||
true,
|
||||
true,
|
||||
);
|
||||
if (i18next.language !== "en") void i18next.changeLanguage("en");
|
||||
}
|
||||
|
||||
// Start MSW once. Storybook runs in a browser so this uses the service worker.
|
||||
|
||||
@@ -7695,13 +7695,6 @@ failed = "Failed"
|
||||
heading = "Outcomes"
|
||||
success = "Delivered"
|
||||
|
||||
[portal.processorFlow.sankey]
|
||||
waist = "Policies · {{n}} active"
|
||||
|
||||
[portal.processorFlow.sankey.empty]
|
||||
description = "Connect a source and switch on a policy to watch documents flow through the processor."
|
||||
title = "No flow yet"
|
||||
|
||||
[portal.processorFlow.policies]
|
||||
activeCount = "{{n}} active"
|
||||
count = "{{n}} · 24h"
|
||||
@@ -7709,6 +7702,13 @@ heading = "Policies"
|
||||
setUp = "Set up"
|
||||
soon = "Soon"
|
||||
|
||||
[portal.processorFlow.sankey]
|
||||
waist = "Policies · {{n}} active"
|
||||
|
||||
[portal.processorFlow.sankey.empty]
|
||||
description = "Connect a source and switch on a policy to watch documents flow through the processor."
|
||||
title = "No flow yet"
|
||||
|
||||
[portal.processorFlow.sources]
|
||||
comingSoonTag = "Connect"
|
||||
editor = "Stirling PDF Editor"
|
||||
|
||||
@@ -109,10 +109,10 @@ function buildPolicies(
|
||||
return POLICY_CATEGORIES.map((cat) => {
|
||||
const dp = decoded.find((p) => p.categoryId === cat.id);
|
||||
const configured = Boolean(dp?.enabled);
|
||||
const state: FlowPolicyState = cat.comingSoon
|
||||
? "locked"
|
||||
: configured
|
||||
? "active"
|
||||
const state: FlowPolicyState = configured
|
||||
? "active"
|
||||
: cat.comingSoon
|
||||
? "locked"
|
||||
: "off";
|
||||
const runs24h = dp
|
||||
? runs.filter((r) => r.policyId === dp.id && r.createdAt >= cutoff).length
|
||||
|
||||
@@ -54,6 +54,10 @@ export function ProcessorFlow() {
|
||||
`${toPortalPath(VIEW_PATHS.policies)}?setup=${encodeURIComponent(key)}`,
|
||||
);
|
||||
|
||||
/** Deep-link to Infrastructure with the audit-log tab open. */
|
||||
const openAuditLog = () =>
|
||||
navigate(`${toPortalPath(VIEW_PATHS.infrastructure)}?tab=audit`);
|
||||
|
||||
const sources = data?.sources ?? [];
|
||||
const policies = data?.policies ?? [];
|
||||
const outcomes = data?.outcomes ?? [];
|
||||
@@ -140,11 +144,7 @@ export function ProcessorFlow() {
|
||||
<Skeleton height="9rem" />
|
||||
</div>
|
||||
) : lens === "sankey" ? (
|
||||
<FlowSankey
|
||||
sources={sources}
|
||||
outcomes={outcomes}
|
||||
activeCount={activeCount}
|
||||
/>
|
||||
<FlowSankey sources={sources} outcomes={outcomes} policies={policies} />
|
||||
) : (
|
||||
<div className="portal-pf__stage" ref={wrapRef}>
|
||||
<svg className="portal-pf__wires" aria-hidden>
|
||||
@@ -168,7 +168,7 @@ export function ProcessorFlow() {
|
||||
<FlowOutcomes
|
||||
outcomes={outcomes}
|
||||
outRefs={outRefs}
|
||||
onOpen={() => setActiveView("infrastructure")}
|
||||
onOpen={openAuditLog}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { EmptyState } from "@app/ui";
|
||||
import type {
|
||||
FlowOutcome,
|
||||
FlowOutcomeKey,
|
||||
FlowPolicy,
|
||||
FlowSource,
|
||||
} from "@portal/api/processorFlow";
|
||||
import {
|
||||
@@ -14,19 +15,18 @@ import {
|
||||
interface FlowSankeyProps {
|
||||
sources: FlowSource[];
|
||||
outcomes: FlowOutcome[];
|
||||
activeCount: number;
|
||||
policies: FlowPolicy[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sankey lens: sources → policies waist → outcomes, ribbon width ∝ 24h volume.
|
||||
* Shows a friendly empty state when nothing has flowed yet.
|
||||
* The waist splits into one segment per active policy. Shows a friendly empty
|
||||
* state when nothing has flowed yet.
|
||||
*/
|
||||
export function FlowSankey({
|
||||
sources,
|
||||
outcomes,
|
||||
activeCount,
|
||||
}: FlowSankeyProps) {
|
||||
export function FlowSankey({ sources, outcomes, policies }: FlowSankeyProps) {
|
||||
const { t } = useTranslation();
|
||||
const activePolicies = policies.filter((p) => p.state === "active");
|
||||
const activeCount = activePolicies.length;
|
||||
|
||||
const flows = sources.filter((s) => s.docs24h > 0);
|
||||
const srcSum = flows.reduce((sum, s) => sum + s.docs24h, 0);
|
||||
@@ -145,19 +145,31 @@ export function FlowSankey({
|
||||
accM += lt[i];
|
||||
});
|
||||
|
||||
// Waist node.
|
||||
bars.push(
|
||||
<rect
|
||||
key="waist"
|
||||
x={xM}
|
||||
y={midY}
|
||||
width={midW}
|
||||
height={midH}
|
||||
rx={2}
|
||||
style={{ fill: waistFill }}
|
||||
opacity={0.9}
|
||||
/>,
|
||||
);
|
||||
// Waist: one segment per active policy (sized by its 24h runs) so the centre
|
||||
// reads as distinct policies rather than a single bar.
|
||||
const segGap = 4;
|
||||
const nSeg = Math.max(activePolicies.length, 1);
|
||||
const segAvail = midH - (nSeg - 1) * segGap;
|
||||
const polSum = activePolicies.reduce((a, p) => a + p.runs24h, 0);
|
||||
const segPolicies = activePolicies.length ? activePolicies : [null];
|
||||
let segY = midY;
|
||||
segPolicies.forEach((p, i) => {
|
||||
const wgt = p && polSum > 0 ? p.runs24h / polSum : 1 / nSeg;
|
||||
const h = Math.max(2, segAvail * wgt);
|
||||
bars.push(
|
||||
<rect
|
||||
key={"waist" + i}
|
||||
x={xM}
|
||||
y={segY}
|
||||
width={midW}
|
||||
height={h}
|
||||
rx={2}
|
||||
style={{ fill: waistFill }}
|
||||
opacity={0.9}
|
||||
/>,
|
||||
);
|
||||
segY += h + segGap;
|
||||
});
|
||||
texts.push(
|
||||
<text
|
||||
key="waist-cap"
|
||||
|
||||
@@ -37,6 +37,30 @@ export function seedPolicies(): WirePolicy[] {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "pol_ingestion_default",
|
||||
name: "Ingestion Policy",
|
||||
owner: "data-eng@acme.com",
|
||||
enabled: true,
|
||||
trigger: null,
|
||||
steps: POLICY_CONFIG.ingestion.defaultOperations,
|
||||
output: {
|
||||
type: "inline",
|
||||
options: {
|
||||
runOn: "upload",
|
||||
mode: "new_version",
|
||||
name: "",
|
||||
position: "suffix",
|
||||
maxRetries: 3,
|
||||
retryDelayMinutes: 5,
|
||||
categoryId: "ingestion",
|
||||
sources: ["src-contracts"],
|
||||
scopeTypes: [],
|
||||
reviewerEmail: "data-eng@acme.com",
|
||||
fieldValues: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -46,12 +70,19 @@ const D = 86400000;
|
||||
|
||||
/** Seed `PolicyRunView` records that drive the activity feed + stats.
|
||||
* 40 delivered + 3 failed within the trailing 24h so the home visualiser shows
|
||||
* a lively flow; a tail of older completed runs keeps the lifetime stats real. */
|
||||
* a lively flow; a tail of older completed runs keeps the lifetime stats real.
|
||||
* Runs are split across the two active policies so the Sankey waist divides. */
|
||||
export function seedPolicyRuns(): PolicyRunView[] {
|
||||
// Split the throughput across the two active policies (security / ingestion)
|
||||
// so both show a 24h count and the Sankey waist splits into two segments.
|
||||
const policyFor = (i: number, total: number) =>
|
||||
i < Math.round(total * 0.6)
|
||||
? "pol_security_default"
|
||||
: "pol_ingestion_default";
|
||||
// 40 successful runs spread across the last ~13h.
|
||||
const delivered = Array.from({ length: 40 }, (_, i) => ({
|
||||
runId: `run_ok_${i}`,
|
||||
policyId: "pol_security_default",
|
||||
policyId: policyFor(i, 40),
|
||||
status: "COMPLETED" as const,
|
||||
currentStep: 2,
|
||||
stepCount: 2,
|
||||
@@ -62,7 +93,7 @@ export function seedPolicyRuns(): PolicyRunView[] {
|
||||
// 3 failures within the last few hours.
|
||||
const failed = Array.from({ length: 3 }, (_, i) => ({
|
||||
runId: `run_fail_${i}`,
|
||||
policyId: "pol_security_default",
|
||||
policyId: policyFor(i, 3),
|
||||
status: "FAILED" as const,
|
||||
currentStep: 1,
|
||||
stepCount: 2,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button, Tabs, type TabItem } from "@app/ui";
|
||||
import { useView } from "@portal/contexts/ViewContext";
|
||||
@@ -18,10 +19,33 @@ type InfraTab =
|
||||
| "storage"
|
||||
| "audit";
|
||||
|
||||
const INFRA_TABS: InfraTab[] = [
|
||||
"deployments",
|
||||
"api-keys",
|
||||
"security",
|
||||
"models",
|
||||
"storage",
|
||||
"audit",
|
||||
];
|
||||
|
||||
export function Infrastructure() {
|
||||
const { t } = useTranslation();
|
||||
const [tab, setTab] = useState<InfraTab>("deployments");
|
||||
const { setActiveView } = useView();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
// Deep-link (?tab=<key>) from elsewhere (e.g. the home visualiser's outcome
|
||||
// cards → audit log): open that tab, then drop the param.
|
||||
useEffect(() => {
|
||||
const requested = searchParams.get("tab");
|
||||
if (!requested) return;
|
||||
if ((INFRA_TABS as string[]).includes(requested)) {
|
||||
setTab(requested as InfraTab);
|
||||
}
|
||||
const next = new URLSearchParams(searchParams);
|
||||
next.delete("tab");
|
||||
setSearchParams(next, { replace: true });
|
||||
}, [searchParams, setSearchParams]);
|
||||
|
||||
const tabs: TabItem<InfraTab>[] = [
|
||||
{ key: "deployments", label: t("portal.infrastructure.tabs.deployments") },
|
||||
|
||||
Reference in New Issue
Block a user