@aglyn/plugins-logic 1.0.0-beta.143
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 +201 -0
- package/README.md +7 -0
- package/package.json +45 -0
- package/src/index.d.ts +19 -0
- package/src/index.js +20 -0
- package/src/index.js.map +1 -0
- package/src/lib/components/host-functions-card.component.d.ts +19 -0
- package/src/lib/components/host-functions-card.component.js +1075 -0
- package/src/lib/components/host-functions-card.component.js.map +1 -0
- package/src/lib/components/host-reference-health-card.component.d.ts +15 -0
- package/src/lib/components/host-reference-health-card.component.js +279 -0
- package/src/lib/components/host-reference-health-card.component.js.map +1 -0
- package/src/lib/components/host-variables-card.component.d.ts +18 -0
- package/src/lib/components/host-variables-card.component.js +630 -0
- package/src/lib/components/host-variables-card.component.js.map +1 -0
- package/src/lib/components/logic-console-page.d.ts +12 -0
- package/src/lib/components/logic-console-page.js +68 -0
- package/src/lib/components/logic-console-page.js.map +1 -0
- package/src/lib/components/where-used-dialog.component.d.ts +20 -0
- package/src/lib/components/where-used-dialog.component.js +103 -0
- package/src/lib/components/where-used-dialog.component.js.map +1 -0
- package/src/lib/constants/bundle-common.d.ts +8 -0
- package/src/lib/constants/bundle-common.js +9 -0
- package/src/lib/constants/bundle-common.js.map +1 -0
- package/src/lib/model/index.d.ts +23 -0
- package/src/lib/model/index.js +23 -0
- package/src/lib/model/index.js.map +1 -0
- package/src/lib/model/parameter-options.d.ts +26 -0
- package/src/lib/model/parameter-options.js +29 -0
- package/src/lib/model/parameter-options.js.map +1 -0
- package/src/lib/model/reference-audit.d.ts +75 -0
- package/src/lib/model/reference-audit.js +155 -0
- package/src/lib/model/reference-audit.js.map +1 -0
- package/src/lib/plugin.d.ts +29 -0
- package/src/lib/plugin.js +82 -0
- package/src/lib/plugin.js.map +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/logic/src/lib/components/host-functions-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n type AglynOrgBilling,\n checkQuota,\n evaluateHostFunction,\n FUNCTION_BUILTIN_NAMES,\n type FunctionComparator,\n functionGlobals,\n functionReferencedNames,\n type FunctionValueType,\n type HostFunction,\n type HostFunctionParameter,\n type HostVariable,\n pluginDocsHelp,\n VARIABLE_NAME_PATTERN,\n} from '@aglyn/aglyn'\n/*\n * The MODULE, not the barrel, for the two PURE helpers — every spec that\n * renders this card mocks `@aglyn/tenant-feature-instance` wholesale to stage\n * its Firestore hooks, and a query builder imported through that barrel\n * disappears under the mock. Neither of these is a hook.\n */\nimport {\n ceilingedWindow,\n collectionCeiling,\n} from '@aglyn/tenant-feature-instance/hooks/host-collection-queries'\nimport { CardDisplay, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { TABLE_PAGE_SIZE_DEFAULT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { Timestamp } from '@aglyn/shared-util-timestamp'\nimport {\n Alert,\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Divider,\n IconButton,\n MenuItem,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { collection, doc, getCountFromServer, getDocs, limit, query, setDoc, updateDoc } from 'firebase/firestore'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport {\n useFirestore,\n useFirestoreCollection,\n useHostResourceApi,\n useUser,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport {\n formatParameterOptions,\n parseParameterOptions,\n} from '../model/parameter-options'\nimport WhereUsedDialog from './where-used-dialog.component'\nimport {\n fetchWhereUsed,\n summarizeDependents,\n type WhereUsedResult,\n} from '@aglyn/aglyn/app-utils/where-used'\n\n/**\n * How many functions the card reads.\n *\n * A CEILING, not a page size — see the query, which explains why this list\n * cannot be paged by the server without making the duplicate-name check lie.\n */\nconst FUNCTION_CEILING = 100\n\nexport interface HostFunctionsCardProps {\n hostId: string\n /** Resolved entitlement source for quota checks (AGL-395). */\n org?: Partial<AglynOrgBilling>\n}\n\nconst VALUE_TYPES: { value: FunctionValueType; label: string }[] = [\n { value: 'number', label: 'Number' },\n { value: 'text', label: 'Text' },\n { value: 'boolean', label: 'True/false' },\n]\n\nconst COMPARATORS: FunctionComparator[] = ['==', '!=', '<', '<=', '>', '>=']\n\ninterface FunctionDraft extends HostFunction {\n id: string | null\n}\n\nfunction emptyDraft(): FunctionDraft {\n return {\n id: null,\n name: '',\n parameters: [{ name: 'P1', type: 'number', required: true }],\n variables: [{ name: 'P3', type: 'number' }],\n operations: [\n {\n if: { left: '', comparator: '<=', right: '' },\n then: [{ set: '', expression: '' }],\n otherwise: [],\n },\n ],\n returnValue: '',\n }\n}\n\n/**\n * What a VISITOR meets for one parameter (AGL-3202): the label above the\n * input, what it starts with, and — for a question with fixed answers — the\n * choices. Empty fields are dropped from the definition rather than stored\n * as empty strings, so a function nobody has dressed for visitors saves\n * exactly the shape it always did.\n *\n * The choices keep their own text while being typed. Parsing every keystroke\n * back into the box would eat the space after a comma, and the colon after a\n * value, the moment they were typed.\n */\nfunction ParameterAudienceFields(props: {\n parameter: HostFunctionParameter\n onChange: (next: HostFunctionParameter) => void\n}) {\n const { parameter, onChange } = props\n const [choices, setChoices] = useState(() =>\n formatParameterOptions(parameter.options),\n )\n const assign = (field: 'label' | 'defaultValue', value: string) => {\n const next = { ...parameter }\n if (value) next[field] = value\n else delete next[field]\n onChange(next)\n }\n return (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography\n variant=\"body2\"\n color=\"text.secondary\"\n sx={{ width: 110, flexShrink: 0 }}\n noWrap\n >\n {parameter.name || '—'}\n </Typography>\n <TextField\n label=\"Label\"\n placeholder={parameter.name}\n value={parameter.label ?? ''}\n onChange={(event) => assign('label', event.target.value.slice(0, 80))}\n size=\"small\"\n sx={{ flex: 2 }}\n />\n <TextField\n label=\"Starts as\"\n value={parameter.defaultValue ?? ''}\n onChange={(event) =>\n assign('defaultValue', event.target.value.slice(0, 120))\n }\n size=\"small\"\n sx={{ flex: 1 }}\n />\n <TextField\n label=\"Choices\"\n placeholder=\"value: Label, value: Label\"\n value={choices}\n onChange={(event) => {\n setChoices(event.target.value)\n const options = parseParameterOptions(event.target.value)\n const next = { ...parameter }\n if (options.length) next.options = options\n else delete next.options\n onChange(next)\n }}\n size=\"small\"\n sx={{ flex: 3 }}\n />\n </Stack>\n )\n}\n\n/** Small labelled row header matching the mockup's section styling. */\nfunction SectionLabel(props: { children: string }) {\n return (\n <Typography variant=\"overline\" color=\"text.secondary\">\n {props.children}\n </Typography>\n )\n}\n\n/**\n * No-code function builder (Component Builder, AGL-92): the mockup's Edit\n * Function dialog — Parameters with required toggles, local Variables,\n * numbered conditional operation cards (If below is TRUE / Then do this /\n * Otherwise do this), a Return value select, and a test-run panel driving\n * the shared evaluator. Definitions persist to `hosts/{hostId}/functions`;\n * prop/event wiring lands with AGL-93.\n */\nexport function HostFunctionsCard(props: HostFunctionsCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const createHostResource = useHostResourceApi()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const { org } = props\n const {\n data: functionDocs,\n status: functionsStatus,\n /**\n * The rows this editor is seeded from are unconfirmed by the server\n * (AGL-1358). Editing destructures a whole stored function into `draft`\n * and writes `{...definition}` back, so `merge: true` protects nothing —\n * the payload is literally every field of the definition. `parameters`\n * and `operations` are arrays, which a merge replaces wholesale rather\n * than diffing, so a cached seed does not lose one edit: it restores that\n * snapshot's entire logic body and parameter list, and every caller's\n * arguments are positional against the list it just reverted.\n */\n fromCache: functionsFromCache,\n } = useFirestoreCollection<any>(\n /*\n * ORDERED AND CEILINGED, deliberately not paged by the query (AGL-2501) —\n * the same decision as the variables card beside it, for the same two\n * reasons.\n *\n * `limit(100)` alone is answered in DOCUMENT-ID order, so the window is a\n * pseudo-random hundred that the `localeCompare` below dresses up as an\n * alphabetical list, and nothing said the list was bounded.\n *\n * `collectionCeiling` does not change WHICH hundred — document-id order is\n * what the bare cap already returned. What it changes is that the order is\n * NAMED, so the obvious next edit is caught: ordering on `name` would HIDE\n * every function written without one rather than mis-sorting the list, and\n * `/api/hosts/resources` validates no field for presence while\n * `IMPORTABLE_FIELDS.functions` copies one only if the export carried it.\n *\n * The QUERY is not paged because `nameTaken` below tests the draft against\n * these rows: case-insensitive uniqueness is what keeps legacy\n * `{{fn:name(...)}}` resolution unambiguous (AGL-185), and on a ten-row\n * server page that check would compare a new function against a tenth of\n * the site and create the duplicate it exists to prevent.\n */\n () =>\n collectionCeiling(\n collection(firestore, 'hosts', hostId, 'functions'),\n FUNCTION_CEILING,\n ),\n [firestore, hostId],\n { idField: '$id' },\n )\n const { rows: readFunctions, truncated: functionsTruncated } =\n ceilingedWindow<any>(functionDocs, FUNCTION_CEILING)\n // Sorting is safe here in a way it is not on a paged list: these rows are\n // the whole collection below the ceiling, not a slice of one.\n const functions = useMemo(\n () =>\n readFunctions\n .filter((definition: any) => !definition.deletedAt)\n .sort((a: any, b: any) =>\n String(a.name ?? '').localeCompare(String(b.name ?? '')),\n ),\n [readFunctions],\n )\n // The page is a SLICE: the rows are already in hand and `nameTaken` needs\n // all of them.\n const [page, setPage] = useState(0)\n const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\n const visibleFunctions = useMemo(\n () => functions.slice(page * pageSize, page * pageSize + pageSize),\n [functions, page, pageSize],\n )\n /**\n * The function HEAD-COUNT is a server aggregate, not the length of the\n * capped listener (AGL-1716, the AGL-1706 shape) — the same fix as the\n * variables card beside it, for the same reason.\n *\n * The listener is `limit(100)` and `functions.length` fed\n * `checkQuota(org, 'functionsPerHost', …)`, whose bands are 1 / 10 / 50 /\n * 250 / 500 / 1,000. Everything from Business up sits above the window, so\n * on those plans the gate compared 100 against hundreds and could never\n * refuse, while `api/hosts/resources` counted the collection and did.\n *\n * UNFILTERED, matching that route's plain `collection('functions').count()`\n * — soft-deleted functions have always counted server-side, so this is the\n * card catching up with the verdict it exists to predict. Only a create\n * moves the number, so that is where it is re-read.\n */\n const [functionCountEpoch, setFunctionCountEpoch] = useState(0)\n const [serverFunctionCount, setServerFunctionCount] = useState<number | null>(\n null,\n )\n useEffect(() => {\n let active = true\n void getCountFromServer(collection(firestore, 'hosts', hostId, 'functions'))\n .then((snapshot) => {\n if (active) setServerFunctionCount(snapshot.data().count)\n })\n .catch(() => {\n // Falls back to the loaded rows — a LOWER bound and the prior\n // behaviour, never 0, which `checkQuota` would answer as \"no usage\"\n // on a site that is over its band.\n })\n return () => {\n active = false\n }\n }, [firestore, hostId, functionCountEpoch])\n const functionCount = serverFunctionCount ?? functions.length\n\n const [draft, setDraft] = useState<FunctionDraft | null>(null)\n\n // Where-used dialog (AGL-193).\n const [usage, setUsage] = useState<{\n name: string\n result: WhereUsedResult\n } | null>(null)\n const [usageLoading, setUsageLoading] = useState<string | null>(null)\n\n const [testArgs, setTestArgs] = useState<Record<string, string>>({})\n const [testResult, setTestResult] = useState<string | null>(null)\n\n const patch = useCallback(\n (updater: (previous: FunctionDraft) => FunctionDraft) =>\n setDraft((previous) => (previous ? updater(previous) : previous)),\n [],\n )\n\n const names = [\n ...(draft?.parameters ?? []).map((parameter) => parameter.name),\n ...(draft?.variables ?? []).map((variable) => variable.name),\n ].filter((name) => VARIABLE_NAME_PATTERN.test(name))\n\n // Case-insensitive uniqueness (AGL-185): function names must stay\n // unambiguous for legacy {{fn:name(...)}} token resolution.\n const nameTaken = Boolean(\n draft &&\n functions.some(\n (definition: any) =>\n String(definition.name ?? '').toLowerCase() ===\n draft.name.trim().toLowerCase() && definition.$id !== draft.id,\n ),\n )\n\n /**\n * A test run sees what a published page sees (AGL-3202): the site\n * variables the draft names. Read on the click, and only when the draft\n * names something outside itself, so opening this card still costs the one\n * functions window and nothing else — a listener here would bill a hundred\n * variable reads to every author who never presses Run.\n */\n const handleTestRun = useCallback(async () => {\n if (!draft) return\n let globals: Record<string, number | string | boolean> = {}\n if (functionReferencedNames(draft).length) {\n try {\n const snapshot = await getDocs(\n query(collection(firestore, 'hosts', hostId, 'variables'), limit(100)),\n )\n const lookup: Record<string, HostVariable> = {}\n snapshot.forEach((row) => {\n const data = row.data() as HostVariable & { deletedAt?: unknown }\n if (data?.name && !data.deletedAt) lookup[row.id] = data\n })\n globals = functionGlobals(draft, lookup)\n } catch (error) {\n // The run still happens: an unreadable variable list reads as the\n // evaluator's own \"Unknown name\", which names what was missing.\n console.error(error)\n }\n }\n const result = evaluateHostFunction(draft, testArgs, { globals })\n setTestResult(\n result.ok === false\n ? `Error: ${result.error}`\n : `Result: ${String(result.value)}`,\n )\n }, [draft, testArgs, firestore, hostId])\n\n const handleSave = useCallback(async () => {\n if (!draft || !draft.name.trim() || nameTaken) return\n const { id: draftId, ...definition } = draft\n const fields = { ...definition, name: draft.name.trim().slice(0, 60) }\n try {\n if (draftId) {\n /**\n * Edit stays client-direct (no quota consumed) — and is refused when\n * its seed was never confirmed by the server (AGL-1358). `fields` is\n * the whole definition spread out of the listener row, so `merge:\n * true` has nothing left to protect.\n *\n * The guard WRAPS the write. An early return is a shape you can keep\n * while losing the protection; here the write is only reachable\n * through the verdict.\n */\n const verdict = await writeGuardedBySeed(\n {\n subject: 'function',\n unreadable: functionsStatus === 'error',\n fromCache: functionsFromCache,\n },\n async () => {\n await setDoc(\n doc(firestore, 'hosts', hostId, 'functions', draftId),\n { ...fields, updatedAt: Timestamp.now() },\n { merge: true },\n )\n },\n )\n // Before `setDraft(null)`, so a refusal keeps the dialog open with\n // the whole edited body and its test run still on screen.\n if (!verdict.ok) {\n return void enqueueSnackbar(verdict.message, {\n variant: 'warning',\n persist: false,\n })\n }\n } else {\n // New function rides the quota-enforcing resources API (AGL-473).\n await createHostResource({ hostId, resource: 'function', data: fields })\n // A create moves the count; the one-shot aggregate below does not\n // refresh itself the way the listener refreshes the rows.\n setFunctionCountEpoch((epoch) => epoch + 1)\n }\n setDraft(null)\n setTestResult(null)\n enqueueSnackbar('Function saved', { variant: 'success', persist: false })\n } catch (error: any) {\n console.error(error)\n enqueueSnackbar(error?.message ?? 'An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n }, [\n draft,\n nameTaken,\n firestore,\n hostId,\n createHostResource,\n enqueueSnackbar,\n functionsStatus,\n functionsFromCache,\n ])\n\n const handleShowUsage = useCallback(\n (definition: any) => async () => {\n setUsageLoading(definition.$id)\n const result = await fetchWhereUsed(user as any, {\n hostId,\n kind: 'function',\n id: definition.$id,\n name: definition.name,\n })\n setUsageLoading(null)\n setUsage({ name: definition.name, result })\n },\n [user, hostId],\n )\n\n const handleDelete = useCallback(\n (definition: any) => async () => {\n // Dependents warning (AGL-187): screens/layouts binding the function\n // and workflow steps calling it.\n const scan = await fetchWhereUsed(user as any, {\n hostId,\n kind: 'function',\n id: definition.$id,\n name: definition.name,\n })\n const confirmed = await confirm({\n title: 'Delete this function?',\n description: scan.total\n ? `\"${definition.name}\" is used in ${summarizeDependents(scan)} ` +\n 'and those bindings and workflow steps will stop running.'\n : `\"${definition.name}\" will no longer be runnable.`,\n confirmationText: 'Delete',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n await updateDoc(\n doc(firestore, 'hosts', hostId, 'functions', definition.$id),\n { deletedAt: Timestamp.now() },\n )\n enqueueSnackbar('Function deleted', {\n variant: 'success',\n persist: false,\n })\n },\n [user, confirm, firestore, hostId, enqueueSnackbar],\n )\n\n const setRow = (\n section: 'then' | 'otherwise',\n operationIndex: number,\n rowIndex: number,\n key: 'set' | 'expression',\n value: string,\n ) =>\n patch((previous) => {\n const operations = previous.operations.map((operation, index) =>\n index === operationIndex\n ? {\n ...operation,\n [section]: operation[section].map((row, index2) =>\n index2 === rowIndex ? { ...row, [key]: value } : row,\n ),\n }\n : operation,\n )\n return { ...previous, operations }\n })\n\n const renderSetRows = (section: 'then' | 'otherwise', operationIndex: number) => {\n const operation = draft?.operations[operationIndex]\n if (!operation) return null\n return (\n <Stack spacing={1}>\n {operation[section].map((row, rowIndex) => (\n <Stack\n key={rowIndex}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Typography variant=\"caption\">{'SET'}</Typography>\n <TextField\n value={row.set}\n onChange={(event) =>\n setRow(section, operationIndex, rowIndex, 'set', event.target.value)\n }\n size=\"small\"\n select\n sx={{ minWidth: 90 }}\n >\n {names.map((name) => (\n <MenuItem key={name} value={name}>\n {name}\n </MenuItem>\n ))}\n </TextField>\n <Typography variant=\"caption\">{'equal to'}</Typography>\n <TextField\n value={row.expression}\n placeholder=\"P1 + P2\"\n onChange={(event) =>\n setRow(\n section,\n operationIndex,\n rowIndex,\n 'expression',\n event.target.value,\n )\n }\n size=\"small\"\n sx={{ flex: 1 }}\n />\n <IconButton\n size=\"small\"\n aria-label=\"remove operation row\"\n onClick={() =>\n patch((previous) => ({\n ...previous,\n operations: previous.operations.map((op, index) =>\n index === operationIndex\n ? {\n ...op,\n [section]: op[section].filter(\n (_, index2) => index2 !== rowIndex,\n ),\n }\n : op,\n ),\n }))\n }\n >\n {'×'}\n </IconButton>\n </Stack>\n ))}\n <Button\n size=\"small\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() =>\n patch((previous) => ({\n ...previous,\n operations: previous.operations.map((op, index) =>\n index === operationIndex\n ? {\n ...op,\n [section]: [...op[section], { set: '', expression: '' }],\n }\n : op,\n ),\n }))\n }\n >\n {'Add set'}\n </Button>\n </Stack>\n )\n }\n\n return (\n <CardDisplay\n header={'Functions'}\n help={pluginDocsHelp('bindings', { anchor: '#no-code-functions' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n {functions.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Build no-code logic: parameters in, conditional operations, ' +\n 'a value out. Wire functions into components and workflows ' +\n 'as the builder grows.'}\n </Typography>\n ) : (\n visibleFunctions.map((definition: any) => (\n <Stack\n key={definition.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Stack sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {definition.name}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {`${(definition.parameters ?? []).map((p: any) => p.name).join(', ') || 'no parameters'} → ${definition.returnValue || '—'}`}\n </Typography>\n </Stack>\n <Button\n size=\"small\"\n disabled={usageLoading === definition.$id}\n onClick={handleShowUsage(definition)}\n >\n {usageLoading === definition.$id ? 'Scanning…' : 'Usage'}\n </Button>\n <Button\n size=\"small\"\n onClick={() => {\n setTestArgs({})\n setTestResult(null)\n setDraft({\n id: definition.$id,\n name: definition.name ?? '',\n parameters: definition.parameters ?? [],\n variables: definition.variables ?? [],\n operations: definition.operations ?? [],\n returnValue: definition.returnValue ?? '',\n })\n }}\n >\n {'Edit'}\n </Button>\n <Button\n size=\"small\"\n color=\"error\"\n onClick={handleDelete(definition)}\n >\n {'Delete'}\n </Button>\n </Stack>\n ))\n )}\n {functions.length === 0 ? null : (\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={visibleFunctions.length}\n // The functions the card HOLDS. Not `functionCount`, which is the\n // site's total including soft-deleted rows and answers the quota's\n // question rather than the reader's.\n count={functions.length}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n )}\n {functionsTruncated ? (\n <Alert severity=\"info\">\n {`Showing ${FUNCTION_CEILING} functions, ordered by id. This site ` +\n 'has more — the duplicate-name check below only covers the ' +\n 'ones listed here, so a name may already be taken by one that ' +\n 'is not.'}\n </Alert>\n ) : null}\n <Button\n size=\"small\"\n color=\"primary\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() => {\n // Plan cap (AGL-99): dark-launch — plan-less workspaces uncapped.\n // The site's functions, not this page of them (AGL-1716).\n const quota = checkQuota(org, 'functionsPerHost', functionCount)\n if (!quota.allowed) {\n return void enqueueSnackbar(\n `Function limit reached (${quota.limit}) — upgrade in Billing`,\n { variant: 'warning', persist: false },\n )\n }\n setTestArgs({})\n setTestResult(null)\n setDraft(emptyDraft())\n }}\n >\n {'Add function'}\n </Button>\n </Stack>\n\n <Dialog\n open={Boolean(draft)}\n onClose={() => setDraft(null)}\n maxWidth=\"sm\"\n fullWidth\n >\n <DialogTitle>\n {draft?.id ? 'Edit Function' : 'Add Function'}\n </DialogTitle>\n <DialogContent\n sx={{ display: 'flex', flexDirection: 'column', gap: 1.5, pt: 1 }}\n >\n <TextField\n label=\"Name\"\n helperText={\n nameTaken\n ? 'A function with this name already exists'\n : 'Used to identify the function'\n }\n error={nameTaken}\n value={draft?.name ?? ''}\n onChange={(event) =>\n patch((previous) => ({ ...previous, name: event.target.value }))\n }\n size=\"small\"\n autoFocus\n sx={{ mt: 1 }}\n />\n\n <SectionLabel>{'Parameters'}</SectionLabel>\n {(draft?.parameters ?? []).map((parameter, index) => (\n <Stack\n key={index}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <TextField\n label=\"Name\"\n value={parameter.name}\n onChange={(event) =>\n patch((previous) => ({\n ...previous,\n parameters: previous.parameters.map((p, index2) =>\n index2 === index\n ? {\n ...p,\n name: event.target.value.replace(\n /[^a-zA-Z0-9_]/g,\n '',\n ),\n }\n : p,\n ),\n }))\n }\n size=\"small\"\n sx={{ width: 110 }}\n />\n <TextField\n label=\"Type\"\n value={parameter.type}\n onChange={(event) =>\n patch((previous) => ({\n ...previous,\n parameters: previous.parameters.map((p, index2) =>\n index2 === index\n ? { ...p, type: event.target.value as FunctionValueType }\n : p,\n ),\n }))\n }\n size=\"small\"\n select\n sx={{ width: 130 }}\n >\n {VALUE_TYPES.map((type) => (\n <MenuItem key={type.value} value={type.value}>\n {type.label}\n </MenuItem>\n ))}\n </TextField>\n <Stack\n direction=\"row\"\n spacing={0.5}\n sx={{ alignItems: 'center', flex: 1 }}\n >\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Required'}\n </Typography>\n <Switch\n size=\"small\"\n checked={Boolean(parameter.required)}\n onChange={(event) =>\n patch((previous) => ({\n ...previous,\n parameters: previous.parameters.map((p, index2) =>\n index2 === index\n ? { ...p, required: event.target.checked }\n : p,\n ),\n }))\n }\n />\n </Stack>\n <IconButton\n size=\"small\"\n aria-label=\"remove parameter\"\n onClick={() =>\n patch((previous) => ({\n ...previous,\n parameters: previous.parameters.filter(\n (_, index2) => index2 !== index,\n ),\n }))\n }\n >\n {'×'}\n </IconButton>\n </Stack>\n ))}\n <Button\n size=\"small\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() =>\n patch((previous) => ({\n ...previous,\n parameters: [\n ...previous.parameters,\n {\n name: `P${previous.parameters.length + 1}`,\n type: 'number',\n required: false,\n },\n ],\n }))\n }\n >\n {'Add parameter'}\n </Button>\n\n {(draft?.parameters ?? []).length ? (\n <>\n <SectionLabel>{'What visitors see'}</SectionLabel>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Used by a Function Widget on a page. A parameter with ' +\n 'choices is asked as a list, a number as a number box and ' +\n 'a true/false as a switch.'}\n </Typography>\n {(draft?.parameters ?? []).map((parameter, index) => (\n <ParameterAudienceFields\n // Keyed by position: the name is being edited one row up.\n key={index}\n parameter={parameter}\n onChange={(next) =>\n patch((previous) => ({\n ...previous,\n parameters: previous.parameters.map((p, index2) =>\n index2 === index ? next : p,\n ),\n }))\n }\n />\n ))}\n </>\n ) : null}\n\n <SectionLabel>{'Variables'}</SectionLabel>\n {(draft?.variables ?? []).map((variable, index) => (\n <Stack\n key={index}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <TextField\n label=\"Name\"\n value={variable.name}\n onChange={(event) =>\n patch((previous) => ({\n ...previous,\n variables: previous.variables.map((v, index2) =>\n index2 === index\n ? {\n ...v,\n name: event.target.value.replace(\n /[^a-zA-Z0-9_]/g,\n '',\n ),\n }\n : v,\n ),\n }))\n }\n size=\"small\"\n sx={{ width: 110 }}\n />\n <TextField\n label=\"Type\"\n value={variable.type}\n onChange={(event) =>\n patch((previous) => ({\n ...previous,\n variables: previous.variables.map((v, index2) =>\n index2 === index\n ? { ...v, type: event.target.value as FunctionValueType }\n : v,\n ),\n }))\n }\n size=\"small\"\n select\n sx={{ width: 130 }}\n >\n {VALUE_TYPES.map((type) => (\n <MenuItem key={type.value} value={type.value}>\n {type.label}\n </MenuItem>\n ))}\n </TextField>\n <IconButton\n size=\"small\"\n aria-label=\"remove variable\"\n sx={{ ml: 'auto' }}\n onClick={() =>\n patch((previous) => ({\n ...previous,\n variables: previous.variables.filter(\n (_, index2) => index2 !== index,\n ),\n }))\n }\n >\n {'×'}\n </IconButton>\n </Stack>\n ))}\n <Button\n size=\"small\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() =>\n patch((previous) => ({\n ...previous,\n variables: [\n ...previous.variables,\n { name: `V${previous.variables.length + 1}`, type: 'number' },\n ],\n }))\n }\n >\n {'Add variable'}\n </Button>\n\n <SectionLabel>{'Operations'}</SectionLabel>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'An expression can use + − × ÷ and parentheses, any site ' +\n 'variable by its name (a dictionary’s members as ' +\n 'name.member), and ' +\n `${FUNCTION_BUILTIN_NAMES.map((name) => `${name}()`).join(', ')}.`}\n </Typography>\n {(draft?.operations ?? []).map((operation, index) => (\n <Stack\n key={index}\n spacing={1}\n sx={{\n border: 1,\n borderColor: 'divider',\n borderRadius: 1,\n p: 1.5,\n }}\n >\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${index + 1} · If below is TRUE`}\n </Typography>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <TextField\n value={operation.if.left}\n placeholder=\"P1\"\n onChange={(event) =>\n patch((previous) => ({\n ...previous,\n operations: previous.operations.map((op, index2) =>\n index2 === index\n ? { ...op, if: { ...op.if, left: event.target.value } }\n : op,\n ),\n }))\n }\n size=\"small\"\n sx={{ flex: 1 }}\n />\n <TextField\n value={operation.if.comparator}\n onChange={(event) =>\n patch((previous) => ({\n ...previous,\n operations: previous.operations.map((op, index2) =>\n index2 === index\n ? {\n ...op,\n if: {\n ...op.if,\n comparator: event.target\n .value as FunctionComparator,\n },\n }\n : op,\n ),\n }))\n }\n size=\"small\"\n select\n sx={{ width: 84 }}\n >\n {COMPARATORS.map((comparator) => (\n <MenuItem key={comparator} value={comparator}>\n {comparator}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n value={operation.if.right}\n placeholder=\"P2\"\n onChange={(event) =>\n patch((previous) => ({\n ...previous,\n operations: previous.operations.map((op, index2) =>\n index2 === index\n ? { ...op, if: { ...op.if, right: event.target.value } }\n : op,\n ),\n }))\n }\n size=\"small\"\n sx={{ flex: 1 }}\n />\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Then do this'}\n </Typography>\n {renderSetRows('then', index)}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Otherwise do this'}\n </Typography>\n {renderSetRows('otherwise', index)}\n </Stack>\n ))}\n <Button\n size=\"small\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() =>\n patch((previous) => ({\n ...previous,\n operations: [\n ...previous.operations,\n {\n if: { left: '', comparator: '<=', right: '' },\n then: [{ set: '', expression: '' }],\n otherwise: [],\n },\n ],\n }))\n }\n >\n {'Add operation'}\n </Button>\n\n <TextField\n label=\"Return value\"\n value={draft?.returnValue ?? ''}\n onChange={(event) =>\n patch((previous) => ({\n ...previous,\n returnValue: event.target.value,\n }))\n }\n size=\"small\"\n select\n >\n {names.map((name) => (\n <MenuItem key={name} value={name}>\n {name}\n </MenuItem>\n ))}\n </TextField>\n\n <Divider />\n <SectionLabel>{'Test run'}</SectionLabel>\n <Stack direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap' }}>\n {(draft?.parameters ?? []).map((parameter) => (\n <TextField\n key={parameter.name}\n label={parameter.name}\n value={testArgs[parameter.name] ?? ''}\n onChange={(event) =>\n setTestArgs((previous) => ({\n ...previous,\n [parameter.name]: event.target.value,\n }))\n }\n size=\"small\"\n sx={{ width: 110 }}\n />\n ))}\n <Button size=\"small\" color=\"primary\" onClick={handleTestRun}>\n {'Run'}\n </Button>\n </Stack>\n {testResult ? (\n <Alert\n severity={testResult.startsWith('Error') ? 'warning' : 'success'}\n >\n {testResult}\n </Alert>\n ) : null}\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setDraft(null)}>{'Cancel'}</Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!draft?.name.trim() || nameTaken}\n onClick={handleSave}\n >\n {'Done'}\n </Button>\n </DialogActions>\n </Dialog>\n <WhereUsedDialog\n hostId={hostId}\n usage={usage}\n onClose={() => setUsage(null)}\n />\n </CardDisplay>\n )\n}\nHostFunctionsCard.displayName = 'HostFunctionsCard'\n\nexport default HostFunctionsCard\n"],"names":["checkQuota","evaluateHostFunction","FUNCTION_BUILTIN_NAMES","functionGlobals","functionReferencedNames","pluginDocsHelp","VARIABLE_NAME_PATTERN","ceilingedWindow","collectionCeiling","CardDisplay","useConfirmationContext","ListPagination","TABLE_PAGE_SIZE_DEFAULT","useSnackbar","Timestamp","Alert","Button","Dialog","DialogActions","DialogContent","DialogTitle","Divider","IconButton","MenuItem","Stack","Switch","TextField","Typography","collection","doc","getCountFromServer","getDocs","limit","query","setDoc","updateDoc","useCallback","useEffect","useMemo","useState","useFirestore","useFirestoreCollection","useHostResourceApi","useUser","writeGuardedBySeed","formatParameterOptions","parseParameterOptions","WhereUsedDialog","fetchWhereUsed","summarizeDependents","FUNCTION_CEILING","VALUE_TYPES","value","label","COMPARATORS","emptyDraft","id","name","parameters","type","required","variables","operations","if","left","comparator","right","then","set","expression","otherwise","returnValue","ParameterAudienceFields","props","parameter","onChange","choices","setChoices","options","assign","field","next","direction","spacing","sx","alignItems","variant","color","width","flexShrink","noWrap","placeholder","event","target","slice","size","flex","defaultValue","length","SectionLabel","children","HostFunctionsCard","hostId","firestore","data","user","createHostResource","enqueueSnackbar","confirm","org","functionDocs","status","functionsStatus","fromCache","functionsFromCache","idField","rows","readFunctions","truncated","functionsTruncated","functions","filter","definition","deletedAt","sort","a","b","String","localeCompare","page","setPage","pageSize","setPageSize","visibleFunctions","functionCountEpoch","setFunctionCountEpoch","serverFunctionCount","setServerFunctionCount","active","snapshot","count","catch","functionCount","draft","setDraft","usage","setUsage","usageLoading","setUsageLoading","testArgs","setTestArgs","testResult","setTestResult","patch","updater","previous","names","map","variable","test","nameTaken","Boolean","some","toLowerCase","trim","$id","handleTestRun","globals","lookup","forEach","row","error","console","result","ok","handleSave","draftId","fields","verdict","subject","unreadable","updatedAt","now","merge","message","persist","resource","epoch","allowDuplicate","handleShowUsage","kind","handleDelete","scan","confirmed","title","description","total","confirmationText","confirmationButtonProps","setRow","section","operationIndex","rowIndex","key","operation","index","index2","renderSetRows","select","minWidth","aria-label","onClick","op","_","alignSelf","header","help","anchor","contentGutterX","contentGutterY","p","join","disabled","rowCount","onPageChange","onPageSizeChange","severity","quota","allowed","open","onClose","maxWidth","fullWidth","display","flexDirection","gap","pt","helperText","autoFocus","mt","replace","checked","v","ml","border","borderColor","borderRadius","flexWrap","startsWith","displayName"],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface HostReferenceHealthCardProps {
|
|
2
|
+
hostId: string;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Reference health (wave v7): id references are rename-safe (AGL-261)
|
|
6
|
+
* but not delete-safe — this card cross-checks every automation,
|
|
7
|
+
* workflow, and computed-variable reference against what still exists
|
|
8
|
+
* and lists the dangling ones, so broken wiring surfaces here instead
|
|
9
|
+
* of failing silently on a visitor's pageview.
|
|
10
|
+
*/
|
|
11
|
+
export declare function HostReferenceHealthCard(props: HostReferenceHealthCardProps): import("react").JSX.Element;
|
|
12
|
+
export declare namespace HostReferenceHealthCard {
|
|
13
|
+
var displayName: string;
|
|
14
|
+
}
|
|
15
|
+
export default HostReferenceHealthCard;
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ 'use client';
|
|
17
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
18
|
+
import { pluginDocsHelp, scopeTokensForHost } from "@aglyn/aglyn";
|
|
19
|
+
import { auditHostReferences } from "../model/index.js";
|
|
20
|
+
import { CardDisplay } from "@aglyn/shared-ui-jsx";
|
|
21
|
+
import { Alert, Chip, Stack, Typography } from "@mui/material";
|
|
22
|
+
import { collection, documentId, limit, orderBy, query, where } from "firebase/firestore";
|
|
23
|
+
import { useMemo } from "react";
|
|
24
|
+
import { useFirestore, useFirestoreCollection, useOrgDataScope } from "@aglyn/tenant-feature-instance";
|
|
25
|
+
/*
|
|
26
|
+
* The MODULE, not the barrel, for the two PURE helpers — the specs that render
|
|
27
|
+
* this card mock `@aglyn/tenant-feature-instance` wholesale to stage their
|
|
28
|
+
* Firestore hooks, and a query builder imported through that barrel disappears
|
|
29
|
+
* under the mock. Neither of these is a hook.
|
|
30
|
+
*/ import { ceilingedWindow, collectionCeiling } from "@aglyn/tenant-feature-instance/hooks/host-collection-queries";
|
|
31
|
+
/**
|
|
32
|
+
* How much of each collection the audit judges against.
|
|
33
|
+
*
|
|
34
|
+
* Thirteen collections at this ceiling is what an open of this page costs, so
|
|
35
|
+
* the number is a bill as much as a bound. It is stated once because every one
|
|
36
|
+
* of those windows has to agree: the audit compares references drawn from
|
|
37
|
+
* three of them against names drawn from the other ten, and a wider window on
|
|
38
|
+
* one side than the other would report the difference as broken wiring.
|
|
39
|
+
*/ const REFERENCE_CEILING = 100;
|
|
40
|
+
/**
|
|
41
|
+
* Reference health (wave v7): id references are rename-safe (AGL-261)
|
|
42
|
+
* but not delete-safe — this card cross-checks every automation,
|
|
43
|
+
* workflow, and computed-variable reference against what still exists
|
|
44
|
+
* and lists the dangling ones, so broken wiring surfaces here instead
|
|
45
|
+
* of failing silently on a visitor's pageview.
|
|
46
|
+
*/ export function HostReferenceHealthCard(props) {
|
|
47
|
+
const { hostId } = props;
|
|
48
|
+
const firestore = useFirestore();
|
|
49
|
+
// The org lookup is async (AGL-1061). `dataScope` is null until it
|
|
50
|
+
// settles — and stays null for a host with no owning org — so the two
|
|
51
|
+
// org-data queries below simply are not issued rather than falling back
|
|
52
|
+
// to a host path (AGL-1050).
|
|
53
|
+
const { scope: dataScope } = useOrgDataScope({
|
|
54
|
+
hostId
|
|
55
|
+
});
|
|
56
|
+
/*
|
|
57
|
+
* ORDERED AND CEILINGED (AGL-2501), the same decision the actions and
|
|
58
|
+
* workflows cards reached.
|
|
59
|
+
*
|
|
60
|
+
* `limit(100)` alone is answered in DOCUMENT-ID order, so an unnamed window
|
|
61
|
+
* is a pseudo-random hundred — and on THIS card that is worse than a
|
|
62
|
+
* mis-drawn list. The audit asks whether a reference resolves and answers
|
|
63
|
+
* from the windows, so a workflow that exists but falls outside the
|
|
64
|
+
* `workflows` hundred is indistinguishable from one that was deleted, and
|
|
65
|
+
* the card reports live wiring as broken. `collectionCeiling` does not
|
|
66
|
+
* change WHICH hundred — document-id order is what a bare cap returns
|
|
67
|
+
* anyway. What it changes is that the order is NAMED, which is what makes
|
|
68
|
+
* the probe row meaningful and catches the obvious next edit: ordering on
|
|
69
|
+
* `name` would HIDE every row written without one rather than mis-sort
|
|
70
|
+
* anything.
|
|
71
|
+
*/ const useHostCollection = (name)=>{
|
|
72
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
73
|
+
const { data } = useFirestoreCollection(()=>collectionCeiling(collection(firestore, 'hosts', hostId, name), REFERENCE_CEILING), [
|
|
74
|
+
firestore,
|
|
75
|
+
hostId
|
|
76
|
+
], {
|
|
77
|
+
idField: '$id'
|
|
78
|
+
});
|
|
79
|
+
// Memoised so the window keeps one identity while the rows do. The audit
|
|
80
|
+
// below is eleven set builds and a sweep of three collections; a fresh
|
|
81
|
+
// `slice` every render would re-run all of it every render.
|
|
82
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
83
|
+
return useMemo(()=>ceilingedWindow(data, REFERENCE_CEILING), [
|
|
84
|
+
data
|
|
85
|
+
]);
|
|
86
|
+
};
|
|
87
|
+
const actionDocs = useHostCollection('actions');
|
|
88
|
+
const workflowDocs = useHostCollection('workflows');
|
|
89
|
+
const variableDocs = useHostCollection('variables');
|
|
90
|
+
const functionDocs = useHostCollection('functions');
|
|
91
|
+
// The campaign containers an "Assign to a campaign" step runs against
|
|
92
|
+
// (AGL-3052); `campaigns` holds the email sends, which no step names.
|
|
93
|
+
const campaignDocs = useHostCollection('emailCampaigns');
|
|
94
|
+
const overlayDocs = useHostCollection('overlays');
|
|
95
|
+
const webhookDocs = useHostCollection('webhooks');
|
|
96
|
+
// New reference kinds (AGL-345): screen links, commerce entities.
|
|
97
|
+
const screenDocs = useHostCollection('screens');
|
|
98
|
+
const productDocs = useHostCollection('products');
|
|
99
|
+
const collectionDocs = useHostCollection('collections');
|
|
100
|
+
const categoryDocs = useHostCollection('productCategories');
|
|
101
|
+
// Scoped (AGL-1044) — see the note in host-actions-card: an unfiltered
|
|
102
|
+
// list is rejected, not filtered.
|
|
103
|
+
// Scoped to the HOST, not the viewer (AGL-1044): the audit speaks for this host, so it must judge against what THIS host can reach — an org-wide
|
|
104
|
+
// admin would otherwise be offered datasets that resolve to nothing at
|
|
105
|
+
// render time. Filtering by the host's tokens also satisfies the
|
|
106
|
+
// AGL-1041 rules, since they are a subset of any viewer's who can reach
|
|
107
|
+
// this host at all.
|
|
108
|
+
// Memoised: this is a listener DEPENDENCY, and a fresh array each
|
|
109
|
+
// render tears the subscription down and clears its data every time.
|
|
110
|
+
const scopeTokens = useMemo(()=>scopeTokensForHost(hostId), [
|
|
111
|
+
hostId
|
|
112
|
+
]);
|
|
113
|
+
// The `visibleTo` filter is now unconditional: the only scope this hook
|
|
114
|
+
// hands out is an org one, and org datasets are all scoped (AGL-1041).
|
|
115
|
+
// It used to be conditional on having an org, for the host fallback's
|
|
116
|
+
// sake — those rows carried no `visibleTo` and the filter would have
|
|
117
|
+
// matched nothing.
|
|
118
|
+
/*
|
|
119
|
+
* `documentId()` rather than a field, for the reason the audience sweep in
|
|
120
|
+
* `campaign-send.ts` gives: Firestore's automatic single-field index for an
|
|
121
|
+
* array member is keyed on the value and the document name, so
|
|
122
|
+
* `array-contains-any` plus `orderBy(__name__)` is served by it. Ordering on
|
|
123
|
+
* anything else here would need a composite index that does not exist.
|
|
124
|
+
*/ const { data: datasetRead } = useFirestoreCollection(()=>dataScope ? query(collection(firestore, dataScope[0], dataScope[1], 'datasets'), where('visibleTo', 'array-contains-any', scopeTokens), orderBy(documentId()), limit(REFERENCE_CEILING + 1)) : null, [
|
|
125
|
+
firestore,
|
|
126
|
+
dataScope,
|
|
127
|
+
scopeTokens
|
|
128
|
+
], {
|
|
129
|
+
idField: '$id'
|
|
130
|
+
});
|
|
131
|
+
const datasetDocs = useMemo(()=>ceilingedWindow(datasetRead, REFERENCE_CEILING), [
|
|
132
|
+
datasetRead
|
|
133
|
+
]);
|
|
134
|
+
const { data: listRead } = useFirestoreCollection(()=>dataScope ? collectionCeiling(collection(firestore, dataScope[0], dataScope[1], 'lists'), REFERENCE_CEILING) : null, [
|
|
135
|
+
firestore,
|
|
136
|
+
dataScope
|
|
137
|
+
], {
|
|
138
|
+
idField: '$id'
|
|
139
|
+
});
|
|
140
|
+
const listDocs = useMemo(()=>ceilingedWindow(listRead, REFERENCE_CEILING), [
|
|
141
|
+
listRead
|
|
142
|
+
]);
|
|
143
|
+
/**
|
|
144
|
+
* At least one collection holds more than the audit read.
|
|
145
|
+
*
|
|
146
|
+
* The verdict this card renders is computed entirely from the windows, so a
|
|
147
|
+
* ceiling that bit anywhere makes both halves of it unsafe: a reference into
|
|
148
|
+
* the unread part of a `known` collection is reported broken, and a
|
|
149
|
+
* reference held by an unread action, workflow or variable is not checked at
|
|
150
|
+
* all. Neither shows up as a gap in the list, which is why it has to be said
|
|
151
|
+
* outright.
|
|
152
|
+
*/ const truncated = [
|
|
153
|
+
actionDocs,
|
|
154
|
+
workflowDocs,
|
|
155
|
+
variableDocs,
|
|
156
|
+
functionDocs,
|
|
157
|
+
campaignDocs,
|
|
158
|
+
overlayDocs,
|
|
159
|
+
webhookDocs,
|
|
160
|
+
screenDocs,
|
|
161
|
+
productDocs,
|
|
162
|
+
collectionDocs,
|
|
163
|
+
categoryDocs,
|
|
164
|
+
datasetDocs,
|
|
165
|
+
listDocs
|
|
166
|
+
].some((window)=>window.truncated);
|
|
167
|
+
const issues = useMemo(()=>{
|
|
168
|
+
const alive = (docs)=>(docs != null ? docs : []).filter((doc)=>!doc.deletedAt);
|
|
169
|
+
const knownSet = (docs, nameKey = 'name')=>{
|
|
170
|
+
const set = new Set();
|
|
171
|
+
for (const doc of alive(docs)){
|
|
172
|
+
set.add(doc.$id);
|
|
173
|
+
const name = doc[nameKey];
|
|
174
|
+
if (typeof name === 'string' && name.trim()) set.add(name.trim());
|
|
175
|
+
}
|
|
176
|
+
return set;
|
|
177
|
+
};
|
|
178
|
+
return auditHostReferences({
|
|
179
|
+
actions: alive(actionDocs.rows),
|
|
180
|
+
workflows: alive(workflowDocs.rows),
|
|
181
|
+
variables: alive(variableDocs.rows),
|
|
182
|
+
known: {
|
|
183
|
+
workflows: knownSet(workflowDocs.rows),
|
|
184
|
+
functions: knownSet(functionDocs.rows),
|
|
185
|
+
datasets: knownSet(datasetDocs.rows),
|
|
186
|
+
lists: knownSet(listDocs.rows),
|
|
187
|
+
campaigns: knownSet(campaignDocs.rows),
|
|
188
|
+
overlays: knownSet(overlayDocs.rows),
|
|
189
|
+
webhooks: knownSet(webhookDocs.rows),
|
|
190
|
+
screens: knownSet(screenDocs.rows, 'displayName'),
|
|
191
|
+
products: knownSet(productDocs.rows),
|
|
192
|
+
collections: knownSet(collectionDocs.rows),
|
|
193
|
+
categories: knownSet(categoryDocs.rows)
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}, [
|
|
197
|
+
actionDocs,
|
|
198
|
+
workflowDocs,
|
|
199
|
+
variableDocs,
|
|
200
|
+
functionDocs,
|
|
201
|
+
datasetDocs,
|
|
202
|
+
listDocs,
|
|
203
|
+
campaignDocs,
|
|
204
|
+
overlayDocs,
|
|
205
|
+
webhookDocs,
|
|
206
|
+
screenDocs,
|
|
207
|
+
productDocs,
|
|
208
|
+
collectionDocs,
|
|
209
|
+
categoryDocs
|
|
210
|
+
]);
|
|
211
|
+
return /*#__PURE__*/ _jsx(CardDisplay, {
|
|
212
|
+
header: 'Reference health',
|
|
213
|
+
help: pluginDocsHelp('bindings', {
|
|
214
|
+
anchor: '#where-used--safety'
|
|
215
|
+
}),
|
|
216
|
+
contentGutterX: true,
|
|
217
|
+
contentGutterY: true,
|
|
218
|
+
contentBordered: "all",
|
|
219
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
220
|
+
spacing: 1,
|
|
221
|
+
children: [
|
|
222
|
+
truncated ? /*#__PURE__*/ _jsx(Alert, {
|
|
223
|
+
severity: "info",
|
|
224
|
+
children: `Audited against the first ${REFERENCE_CEILING} rows of each ` + 'collection, ordered by id. At least one of them holds more, ' + 'so a step pointing past that window is listed here as broken ' + 'when it is not, and a step held past it is not checked at all.'
|
|
225
|
+
}) : null,
|
|
226
|
+
issues.length === 0 ? /*#__PURE__*/ _jsx(Alert, {
|
|
227
|
+
severity: "success",
|
|
228
|
+
children: truncated ? 'Every reference the audit read resolves.' : 'Every automation, workflow, and variable reference resolves.'
|
|
229
|
+
}) : /*#__PURE__*/ _jsxs(Stack, {
|
|
230
|
+
spacing: 1,
|
|
231
|
+
children: [
|
|
232
|
+
/*#__PURE__*/ _jsx(Alert, {
|
|
233
|
+
severity: "warning",
|
|
234
|
+
children: `${issues.length} broken reference${issues.length === 1 ? '' : 's'} — these steps do nothing until re-pointed or removed.`
|
|
235
|
+
}),
|
|
236
|
+
issues.map((issue, index)=>/*#__PURE__*/ _jsxs(Stack, {
|
|
237
|
+
direction: "row",
|
|
238
|
+
spacing: 1,
|
|
239
|
+
sx: {
|
|
240
|
+
alignItems: 'center'
|
|
241
|
+
},
|
|
242
|
+
children: [
|
|
243
|
+
/*#__PURE__*/ _jsx(Chip, {
|
|
244
|
+
size: "small",
|
|
245
|
+
label: issue.source
|
|
246
|
+
}),
|
|
247
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
248
|
+
variant: "body2",
|
|
249
|
+
noWrap: true,
|
|
250
|
+
sx: {
|
|
251
|
+
maxWidth: '40%'
|
|
252
|
+
},
|
|
253
|
+
children: issue.sourceName
|
|
254
|
+
}),
|
|
255
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
256
|
+
variant: "caption",
|
|
257
|
+
color: "text.secondary",
|
|
258
|
+
children: `→ missing ${issue.refType} `
|
|
259
|
+
}),
|
|
260
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
261
|
+
variant: "caption",
|
|
262
|
+
color: "error",
|
|
263
|
+
sx: {
|
|
264
|
+
fontFamily: 'monospace'
|
|
265
|
+
},
|
|
266
|
+
children: issue.missing || '(empty)'
|
|
267
|
+
})
|
|
268
|
+
]
|
|
269
|
+
}, `${issue.sourceId}:${index}`))
|
|
270
|
+
]
|
|
271
|
+
})
|
|
272
|
+
]
|
|
273
|
+
})
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
HostReferenceHealthCard.displayName = 'HostReferenceHealthCard';
|
|
277
|
+
export default HostReferenceHealthCard;
|
|
278
|
+
|
|
279
|
+
//# sourceMappingURL=host-reference-health-card.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/logic/src/lib/components/host-reference-health-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { pluginDocsHelp, scopeTokensForHost } from '@aglyn/aglyn'\nimport { auditHostReferences } from '../model'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { Alert, Chip, Stack, Typography } from '@mui/material'\nimport {\n collection,\n documentId,\n limit,\n orderBy,\n query,\n where,\n} from 'firebase/firestore'\nimport { useMemo } from 'react'\nimport {\n useFirestore,\n useFirestoreCollection,\n useOrgDataScope,\n} from '@aglyn/tenant-feature-instance'\n/*\n * The MODULE, not the barrel, for the two PURE helpers — the specs that render\n * this card mock `@aglyn/tenant-feature-instance` wholesale to stage their\n * Firestore hooks, and a query builder imported through that barrel disappears\n * under the mock. Neither of these is a hook.\n */\nimport {\n ceilingedWindow,\n collectionCeiling,\n} from '@aglyn/tenant-feature-instance/hooks/host-collection-queries'\n\nexport interface HostReferenceHealthCardProps {\n hostId: string\n}\n\n/**\n * How much of each collection the audit judges against.\n *\n * Thirteen collections at this ceiling is what an open of this page costs, so\n * the number is a bill as much as a bound. It is stated once because every one\n * of those windows has to agree: the audit compares references drawn from\n * three of them against names drawn from the other ten, and a wider window on\n * one side than the other would report the difference as broken wiring.\n */\nconst REFERENCE_CEILING = 100\n\n/**\n * Reference health (wave v7): id references are rename-safe (AGL-261)\n * but not delete-safe — this card cross-checks every automation,\n * workflow, and computed-variable reference against what still exists\n * and lists the dangling ones, so broken wiring surfaces here instead\n * of failing silently on a visitor's pageview.\n */\nexport function HostReferenceHealthCard(props: HostReferenceHealthCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n // The org lookup is async (AGL-1061). `dataScope` is null until it\n // settles — and stays null for a host with no owning org — so the two\n // org-data queries below simply are not issued rather than falling back\n // to a host path (AGL-1050).\n const { scope: dataScope } = useOrgDataScope({ hostId })\n\n /*\n * ORDERED AND CEILINGED (AGL-2501), the same decision the actions and\n * workflows cards reached.\n *\n * `limit(100)` alone is answered in DOCUMENT-ID order, so an unnamed window\n * is a pseudo-random hundred — and on THIS card that is worse than a\n * mis-drawn list. The audit asks whether a reference resolves and answers\n * from the windows, so a workflow that exists but falls outside the\n * `workflows` hundred is indistinguishable from one that was deleted, and\n * the card reports live wiring as broken. `collectionCeiling` does not\n * change WHICH hundred — document-id order is what a bare cap returns\n * anyway. What it changes is that the order is NAMED, which is what makes\n * the probe row meaningful and catches the obvious next edit: ordering on\n * `name` would HIDE every row written without one rather than mis-sort\n * anything.\n */\n const useHostCollection = (name: string) => {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n const { data } = useFirestoreCollection<any>(\n () =>\n collectionCeiling(\n collection(firestore, 'hosts', hostId, name),\n REFERENCE_CEILING,\n ),\n [firestore, hostId],\n { idField: '$id' },\n )\n // Memoised so the window keeps one identity while the rows do. The audit\n // below is eleven set builds and a sweep of three collections; a fresh\n // `slice` every render would re-run all of it every render.\n // eslint-disable-next-line react-hooks/rules-of-hooks\n return useMemo(() => ceilingedWindow<any>(data, REFERENCE_CEILING), [data])\n }\n const actionDocs = useHostCollection('actions')\n const workflowDocs = useHostCollection('workflows')\n const variableDocs = useHostCollection('variables')\n const functionDocs = useHostCollection('functions')\n // The campaign containers an \"Assign to a campaign\" step runs against\n // (AGL-3052); `campaigns` holds the email sends, which no step names.\n const campaignDocs = useHostCollection('emailCampaigns')\n const overlayDocs = useHostCollection('overlays')\n const webhookDocs = useHostCollection('webhooks')\n // New reference kinds (AGL-345): screen links, commerce entities.\n const screenDocs = useHostCollection('screens')\n const productDocs = useHostCollection('products')\n const collectionDocs = useHostCollection('collections')\n const categoryDocs = useHostCollection('productCategories')\n // Scoped (AGL-1044) — see the note in host-actions-card: an unfiltered\n // list is rejected, not filtered.\n // Scoped to the HOST, not the viewer (AGL-1044): the audit speaks for this host, so it must judge against what THIS host can reach — an org-wide\n // admin would otherwise be offered datasets that resolve to nothing at\n // render time. Filtering by the host's tokens also satisfies the\n // AGL-1041 rules, since they are a subset of any viewer's who can reach\n // this host at all.\n // Memoised: this is a listener DEPENDENCY, and a fresh array each\n // render tears the subscription down and clears its data every time.\n const scopeTokens = useMemo(() => scopeTokensForHost(hostId), [hostId])\n // The `visibleTo` filter is now unconditional: the only scope this hook\n // hands out is an org one, and org datasets are all scoped (AGL-1041).\n // It used to be conditional on having an org, for the host fallback's\n // sake — those rows carried no `visibleTo` and the filter would have\n // matched nothing.\n /*\n * `documentId()` rather than a field, for the reason the audience sweep in\n * `campaign-send.ts` gives: Firestore's automatic single-field index for an\n * array member is keyed on the value and the document name, so\n * `array-contains-any` plus `orderBy(__name__)` is served by it. Ordering on\n * anything else here would need a composite index that does not exist.\n */\n const { data: datasetRead } = useFirestoreCollection<any>(\n () =>\n dataScope\n ? query(\n collection(firestore, dataScope[0], dataScope[1], 'datasets'),\n where('visibleTo', 'array-contains-any', scopeTokens),\n orderBy(documentId()),\n limit(REFERENCE_CEILING + 1),\n )\n : null,\n [firestore, dataScope, scopeTokens],\n { idField: '$id' },\n )\n const datasetDocs = useMemo(\n () => ceilingedWindow<any>(datasetRead, REFERENCE_CEILING),\n [datasetRead],\n )\n const { data: listRead } = useFirestoreCollection<any>(\n () =>\n dataScope\n ? collectionCeiling(\n collection(firestore, dataScope[0], dataScope[1], 'lists'),\n REFERENCE_CEILING,\n )\n : null,\n [firestore, dataScope],\n { idField: '$id' },\n )\n const listDocs = useMemo(\n () => ceilingedWindow<any>(listRead, REFERENCE_CEILING),\n [listRead],\n )\n\n /**\n * At least one collection holds more than the audit read.\n *\n * The verdict this card renders is computed entirely from the windows, so a\n * ceiling that bit anywhere makes both halves of it unsafe: a reference into\n * the unread part of a `known` collection is reported broken, and a\n * reference held by an unread action, workflow or variable is not checked at\n * all. Neither shows up as a gap in the list, which is why it has to be said\n * outright.\n */\n const truncated = [\n actionDocs,\n workflowDocs,\n variableDocs,\n functionDocs,\n campaignDocs,\n overlayDocs,\n webhookDocs,\n screenDocs,\n productDocs,\n collectionDocs,\n categoryDocs,\n datasetDocs,\n listDocs,\n ].some((window) => window.truncated)\n\n const issues = useMemo(() => {\n const alive = (docs: any[] | undefined) =>\n (docs ?? []).filter((doc: any) => !doc.deletedAt)\n const knownSet = (docs: any[] | undefined, nameKey = 'name') => {\n const set = new Set<string>()\n for (const doc of alive(docs)) {\n set.add(doc.$id)\n const name = doc[nameKey]\n if (typeof name === 'string' && name.trim()) set.add(name.trim())\n }\n return set\n }\n return auditHostReferences({\n actions: alive(actionDocs.rows),\n workflows: alive(workflowDocs.rows),\n variables: alive(variableDocs.rows),\n known: {\n workflows: knownSet(workflowDocs.rows),\n functions: knownSet(functionDocs.rows),\n datasets: knownSet(datasetDocs.rows),\n lists: knownSet(listDocs.rows),\n campaigns: knownSet(campaignDocs.rows),\n overlays: knownSet(overlayDocs.rows),\n webhooks: knownSet(webhookDocs.rows),\n screens: knownSet(screenDocs.rows, 'displayName'),\n products: knownSet(productDocs.rows),\n collections: knownSet(collectionDocs.rows),\n categories: knownSet(categoryDocs.rows),\n },\n })\n }, [\n actionDocs,\n workflowDocs,\n variableDocs,\n functionDocs,\n datasetDocs,\n listDocs,\n campaignDocs,\n overlayDocs,\n webhookDocs,\n screenDocs,\n productDocs,\n collectionDocs,\n categoryDocs,\n ])\n\n return (\n <CardDisplay\n header={'Reference health'}\n help={pluginDocsHelp('bindings', { anchor: '#where-used--safety' })}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n >\n <Stack spacing={1}>\n {truncated ? (\n <Alert severity=\"info\">\n {`Audited against the first ${REFERENCE_CEILING} rows of each ` +\n 'collection, ordered by id. At least one of them holds more, ' +\n 'so a step pointing past that window is listed here as broken ' +\n 'when it is not, and a step held past it is not checked at all.'}\n </Alert>\n ) : null}\n {issues.length === 0 ? (\n <Alert severity=\"success\">\n {truncated\n ? 'Every reference the audit read resolves.'\n : 'Every automation, workflow, and variable reference resolves.'}\n </Alert>\n ) : (\n <Stack spacing={1}>\n <Alert severity=\"warning\">\n {`${issues.length} broken reference${\n issues.length === 1 ? '' : 's'\n } — these steps do nothing until re-pointed or removed.`}\n </Alert>\n {issues.map((issue, index) => (\n <Stack\n key={`${issue.sourceId}:${index}`}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Chip size=\"small\" label={issue.source} />\n <Typography variant=\"body2\" noWrap sx={{ maxWidth: '40%' }}>\n {issue.sourceName}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`→ missing ${issue.refType} `}\n </Typography>\n <Typography\n variant=\"caption\"\n color=\"error\"\n sx={{ fontFamily: 'monospace' }}\n >\n {issue.missing || '(empty)'}\n </Typography>\n </Stack>\n ))}\n </Stack>\n )}\n </Stack>\n </CardDisplay>\n )\n}\nHostReferenceHealthCard.displayName = 'HostReferenceHealthCard'\n\nexport default HostReferenceHealthCard\n"],"names":["pluginDocsHelp","scopeTokensForHost","auditHostReferences","CardDisplay","Alert","Chip","Stack","Typography","collection","documentId","limit","orderBy","query","where","useMemo","useFirestore","useFirestoreCollection","useOrgDataScope","ceilingedWindow","collectionCeiling","REFERENCE_CEILING","HostReferenceHealthCard","props","hostId","firestore","scope","dataScope","useHostCollection","name","data","idField","actionDocs","workflowDocs","variableDocs","functionDocs","campaignDocs","overlayDocs","webhookDocs","screenDocs","productDocs","collectionDocs","categoryDocs","scopeTokens","datasetRead","datasetDocs","listRead","listDocs","truncated","some","window","issues","alive","docs","filter","doc","deletedAt","knownSet","nameKey","set","Set","add","$id","trim","actions","rows","workflows","variables","known","functions","datasets","lists","campaigns","overlays","webhooks","screens","products","collections","categories","header","help","anchor","contentGutterX","contentGutterY","contentBordered","spacing","severity","length","map","issue","index","direction","sx","alignItems","size","label","source","variant","noWrap","maxWidth","sourceName","color","refType","fontFamily","missing","sourceId","displayName"],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type AglynOrgBilling } from '@aglyn/aglyn';
|
|
2
|
+
export interface HostVariablesCardProps {
|
|
3
|
+
hostId: string;
|
|
4
|
+
/** Resolved entitlement source for quota checks (AGL-395). */
|
|
5
|
+
org?: Partial<AglynOrgBilling>;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Host variables (Component Builder, AGL-91): the mockup's Edit Variable
|
|
9
|
+
* dialog — name + typed value — persisted to `hosts/{hostId}/variables`.
|
|
10
|
+
* Any string prop can reference a variable with `{{name}}`; the org
|
|
11
|
+
* compose pipeline resolves it at render. Soft delete keeps old tokens
|
|
12
|
+
* rendering literally rather than breaking published pages.
|
|
13
|
+
*/
|
|
14
|
+
export declare function HostVariablesCard(props: HostVariablesCardProps): import("react").JSX.Element;
|
|
15
|
+
export declare namespace HostVariablesCard {
|
|
16
|
+
var displayName: string;
|
|
17
|
+
}
|
|
18
|
+
export default HostVariablesCard;
|