@aglyn/plugins-logic 1.0.0-beta.173 → 1.0.0-beta.174
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/plugins-logic",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.174",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,12 +25,12 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-ui-snackstack": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-util-timestamp": "1.0.0-beta.
|
|
33
|
-
"@aglyn/tenant-feature-instance": "1.0.0-beta.
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.174",
|
|
29
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.174",
|
|
30
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.174",
|
|
31
|
+
"@aglyn/shared-ui-snackstack": "1.0.0-beta.174",
|
|
32
|
+
"@aglyn/shared-util-timestamp": "1.0.0-beta.174",
|
|
33
|
+
"@aglyn/tenant-feature-instance": "1.0.0-beta.174",
|
|
34
34
|
"@swc/helpers": "0.5.23"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
@@ -88,9 +88,6 @@ import { useFirestore, useFirestoreCollection, useOrgDataScope } from "@aglyn/te
|
|
|
88
88
|
const workflowDocs = useHostCollection('workflows');
|
|
89
89
|
const variableDocs = useHostCollection('variables');
|
|
90
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
91
|
const overlayDocs = useHostCollection('overlays');
|
|
95
92
|
const webhookDocs = useHostCollection('webhooks');
|
|
96
93
|
// New reference kinds (AGL-345): screen links, commerce entities.
|
|
@@ -131,6 +128,21 @@ import { useFirestore, useFirestoreCollection, useOrgDataScope } from "@aglyn/te
|
|
|
131
128
|
const datasetDocs = useMemo(()=>ceilingedWindow(datasetRead, REFERENCE_CEILING), [
|
|
132
129
|
datasetRead
|
|
133
130
|
]);
|
|
131
|
+
/*
|
|
132
|
+
* The campaign containers an "Assign to a campaign" step runs against
|
|
133
|
+
* (AGL-3052); `campaigns` holds the email sends, which no step names.
|
|
134
|
+
* They are the org's, and the audit judges against the ones placed on
|
|
135
|
+
* THIS site, by the same host tokens as the datasets above.
|
|
136
|
+
*/ const { data: campaignRead } = useFirestoreCollection(()=>dataScope ? collectionCeiling(query(collection(firestore, dataScope[0], dataScope[1], 'emailCampaigns'), where('visibleTo', 'array-contains-any', scopeTokens)), REFERENCE_CEILING) : null, [
|
|
137
|
+
firestore,
|
|
138
|
+
dataScope,
|
|
139
|
+
scopeTokens
|
|
140
|
+
], {
|
|
141
|
+
idField: '$id'
|
|
142
|
+
});
|
|
143
|
+
const campaignDocs = useMemo(()=>ceilingedWindow(campaignRead, REFERENCE_CEILING), [
|
|
144
|
+
campaignRead
|
|
145
|
+
]);
|
|
134
146
|
const { data: listRead } = useFirestoreCollection(()=>dataScope ? collectionCeiling(collection(firestore, dataScope[0], dataScope[1], 'lists'), REFERENCE_CEILING) : null, [
|
|
135
147
|
firestore,
|
|
136
148
|
dataScope
|
|
@@ -1 +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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"}
|
|
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 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 /*\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 * They are the org's, and the audit judges against the ones placed on\n * THIS site, by the same host tokens as the datasets above.\n */\n const { data: campaignRead } = useFirestoreCollection<any>(\n () =>\n dataScope\n ? collectionCeiling(\n query(\n collection(firestore, dataScope[0], dataScope[1], 'emailCampaigns'),\n where('visibleTo', 'array-contains-any', scopeTokens),\n ),\n REFERENCE_CEILING,\n )\n : null,\n [firestore, dataScope, scopeTokens],\n { idField: '$id' },\n )\n const campaignDocs = useMemo(\n () => ceilingedWindow<any>(campaignRead, REFERENCE_CEILING),\n [campaignRead],\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","overlayDocs","webhookDocs","screenDocs","productDocs","collectionDocs","categoryDocs","scopeTokens","datasetRead","datasetDocs","campaignRead","campaignDocs","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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"}
|