@anthusai/papyrus 5.41.4 → 5.41.5
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/amplify/functions/monitoring-view/schema.js +6 -0
- package/amplify/functions/monitoring-view/schema.ts +6 -0
- package/amplify/functions/monitoring-view/wire.js +1 -0
- package/amplify/functions/monitoring-view/wire.ts +1 -0
- package/components/monitoring-overview-view.tsx +7 -5
- package/components/monitoring-panel.tsx +33 -3
- package/lib/monitoring-client.ts +11 -2
- package/package.json +1 -1
- package/routes.manifest.json +1 -1
|
@@ -20,6 +20,12 @@ export const monitoringViewSchemaDefinitions = {
|
|
|
20
20
|
.returns(a.json())
|
|
21
21
|
.authorization((allow) => [allow.groups(editors)])
|
|
22
22
|
.handler(functionHandler("monitoringView", monitoringView)),
|
|
23
|
+
monitoringQueueEmailReference: a
|
|
24
|
+
.mutation()
|
|
25
|
+
.arguments({ input: a.json().required() })
|
|
26
|
+
.returns(a.json())
|
|
27
|
+
.authorization((allow) => [allow.groups(editors)])
|
|
28
|
+
.handler(functionHandler("monitoringView", monitoringView)),
|
|
23
29
|
monitoringResolveEmailArticleLink: a
|
|
24
30
|
.mutation()
|
|
25
31
|
.arguments({ receiptId: a.string().required(), candidateIndex: a.integer().required() })
|
|
@@ -22,6 +22,12 @@ export const monitoringViewSchemaDefinitions = {
|
|
|
22
22
|
.returns(a.json())
|
|
23
23
|
.authorization((allow) => [allow.groups(editors)])
|
|
24
24
|
.handler(functionHandler("monitoringView", monitoringView)),
|
|
25
|
+
monitoringQueueEmailReference: a
|
|
26
|
+
.mutation()
|
|
27
|
+
.arguments({ input: a.json().required() })
|
|
28
|
+
.returns(a.json())
|
|
29
|
+
.authorization((allow) => [allow.groups(editors)])
|
|
30
|
+
.handler(functionHandler("monitoringView", monitoringView)),
|
|
25
31
|
monitoringResolveEmailArticleLink: a
|
|
26
32
|
.mutation()
|
|
27
33
|
.arguments({ receiptId: a.string().required(), candidateIndex: a.integer().required() })
|
|
@@ -22,6 +22,7 @@ export function monitoringViewParameterArn(region, account) {
|
|
|
22
22
|
export function wireMonitoringView(backend, emailMonitorId = "hn") {
|
|
23
23
|
const lambda = backend.monitoringView.resources.lambda;
|
|
24
24
|
const stack = Stack.of(lambda);
|
|
25
|
+
lambda.addEnvironment("PAPYRUS_USER_POOL_ISSUER", `https://cognito-idp.${stack.region}.amazonaws.com/${backend.auth.resources.userPool.userPoolId}`);
|
|
25
26
|
lambda.addEnvironment("PAPYRUS_GRAPHQL_ENDPOINT", backend.data.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl);
|
|
26
27
|
lambda.addEnvironment("PAPYRUS_MONITORING_TABLE_PARAMETER", MONITORING_TABLE_PARAMETER);
|
|
27
28
|
lambda.addEnvironment("PAPYRUS_EMAIL_MONITOR_ID", emailMonitorId);
|
|
@@ -28,6 +28,7 @@ export function monitoringViewParameterArn(region: string, account: string): str
|
|
|
28
28
|
export function wireMonitoringView(backend: any, emailMonitorId = "hn"): void {
|
|
29
29
|
const lambda = backend.monitoringView.resources.lambda as LambdaFunction;
|
|
30
30
|
const stack = Stack.of(lambda);
|
|
31
|
+
lambda.addEnvironment("PAPYRUS_USER_POOL_ISSUER", `https://cognito-idp.${stack.region}.amazonaws.com/${backend.auth.resources.userPool.userPoolId}`);
|
|
31
32
|
lambda.addEnvironment("PAPYRUS_GRAPHQL_ENDPOINT", backend.data.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl as string);
|
|
32
33
|
lambda.addEnvironment("PAPYRUS_MONITORING_TABLE_PARAMETER", MONITORING_TABLE_PARAMETER);
|
|
33
34
|
lambda.addEnvironment("PAPYRUS_EMAIL_MONITOR_ID", emailMonitorId);
|
|
@@ -97,8 +97,8 @@ function QueueItem({ item, ownId, demo }: { item: MonitoringItem; ownId: string
|
|
|
97
97
|
);
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
function EmailSubmissions({ items, detail, onOpen, onResolveArticleLink, onImportCandidates }: {
|
|
101
|
-
items: MonitoringEmailSummary[]; detail: MonitoringEmailDetails | null; onOpen
|
|
100
|
+
export function EmailSubmissions({ items, detail, onOpen, onResolveArticleLink, onImportCandidates }: {
|
|
101
|
+
items: MonitoringEmailSummary[]; detail: MonitoringEmailDetails | null; onOpen?: (receiptId: string) => void;
|
|
102
102
|
onResolveArticleLink?: (receiptId: string, candidateIndex: number) => Promise<MonitoringEmailArticleResolution>;
|
|
103
103
|
onImportCandidates?: (receiptId: string, items: EmailReferenceImportItem[]) => Promise<EmailReferenceImportOutcome>;
|
|
104
104
|
}) {
|
|
@@ -181,7 +181,7 @@ function EmailSubmissions({ items, detail, onOpen, onResolveArticleLink, onImpor
|
|
|
181
181
|
if (result.error) {
|
|
182
182
|
setNoticeByReceipt((current) => ({ ...current, [requestReceiptId]: `${result.imported} of ${candidates.length} selected candidates were registered or matched. ${result.error}` }));
|
|
183
183
|
} else {
|
|
184
|
-
setNoticeByReceipt((current) => ({ ...current, [requestReceiptId]: `${result.imported} selected candidates are available as References. Import does not approve or publish them.` }));
|
|
184
|
+
setNoticeByReceipt((current) => ({ ...current, [requestReceiptId]: `${result.imported} selected candidates are available as References. Predictions are queued in the cloud; review them when ready. Import does not approve or publish them.` }));
|
|
185
185
|
}
|
|
186
186
|
} catch {
|
|
187
187
|
if (isCurrentEmailSubmissionAction(activeReceiptId.current, requestReceiptId)) {
|
|
@@ -207,7 +207,7 @@ function EmailSubmissions({ items, detail, onOpen, onResolveArticleLink, onImpor
|
|
|
207
207
|
<span>SES signals — SPF {item.verdicts.spf}, DKIM {item.verdicts.dkim}, DMARC {item.verdicts.dmarc}; spam {item.verdicts.spam}, virus {item.verdicts.virus}</span>
|
|
208
208
|
{item.warnings.length ? <span>Warnings: {item.warnings.join(", ")}</span> : null}
|
|
209
209
|
{item.errorCode ? <span>State: {item.status} ({item.errorCode})</span> : <span>State: {item.status}</span>}
|
|
210
|
-
<button type="button" onClick={() => onOpen(item.receiptId)}>Read email and review candidate links</button>
|
|
210
|
+
{onOpen ? <button type="button" onClick={() => onOpen(item.receiptId)}>Read email and review candidate links</button> : null}
|
|
211
211
|
</li>)}
|
|
212
212
|
</ul> : <p>No email submissions are available for this publication.</p>}
|
|
213
213
|
{detail && "submission" in detail ? (
|
|
@@ -238,7 +238,9 @@ function EmailSubmissions({ items, detail, onOpen, onResolveArticleLink, onImpor
|
|
|
238
238
|
{busyImport ? "Adding References…" : "Add selected links to References"}
|
|
239
239
|
</button>
|
|
240
240
|
{notice ? <p role="status" data-email-import-result>{notice}</p> : null}
|
|
241
|
-
{importedReferences.length ? <
|
|
241
|
+
{importedReferences.length ? <ul>{importedReferences.map((reference, index) => (
|
|
242
|
+
<li key={`${reference.id}-${index}`}><Link href={getNewsroomNavHref(`/newsroom/references/${encodeURIComponent(reference.lineageId)}`, false)}>Review imported Reference {index + 1}</Link></li>
|
|
243
|
+
))}</ul> : null}
|
|
242
244
|
</section>
|
|
243
245
|
) : detail && "error" in detail ? <p role="alert">You cannot read this submission.</p>
|
|
244
246
|
: detail && "state" in detail ? <p role="alert">Submission details are unavailable.</p> : null}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
|
+
import { useSearchParams } from "next/navigation";
|
|
4
5
|
import {
|
|
5
6
|
importMonitoringEmailCandidates,
|
|
6
7
|
loadMonitoringEmailSubmission,
|
|
@@ -9,12 +10,41 @@ import {
|
|
|
9
10
|
} from "../lib/monitoring-client";
|
|
10
11
|
import { FIXTURE_ALL_PIPELINE_OVERVIEW, FIXTURE_MONITORING_OVERVIEW } from "../lib/monitoring-overview-fixture";
|
|
11
12
|
import type { EmailReferenceImportItem, MonitoringEmailDetails, MonitoringResult } from "../lib/monitoring-overview";
|
|
12
|
-
import { MonitoringOverviewView, type MonitoringMode } from "./monitoring-overview-view";
|
|
13
|
+
import { EmailSubmissions, MonitoringOverviewView, type MonitoringMode } from "./monitoring-overview-view";
|
|
13
14
|
|
|
14
15
|
const REFRESH_MILLISECONDS = 60_000;
|
|
15
16
|
|
|
16
|
-
/**
|
|
17
|
-
export function MonitoringPanel(
|
|
17
|
+
/** A receipt URL mounts only the single-submission reader, never the overview. */
|
|
18
|
+
export function MonitoringPanel(props: { isAdmin: boolean; isDemo: boolean }) {
|
|
19
|
+
const searchParams = useSearchParams();
|
|
20
|
+
if (searchParams.has("receipt")) {
|
|
21
|
+
return <MonitoringEmailReceipt key={searchParams.get("receipt")} receiptId={searchParams.get("receipt") ?? ""} isDemo={props.isDemo} />;
|
|
22
|
+
}
|
|
23
|
+
return <MonitoringOverviewPanel {...props} />;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function MonitoringEmailReceipt({ receiptId, isDemo }: { receiptId: string; isDemo: boolean }) {
|
|
27
|
+
const [detail, setDetail] = useState<MonitoringEmailDetails | null>(null);
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (!receiptId || isDemo) return;
|
|
30
|
+
let live = true;
|
|
31
|
+
void loadMonitoringEmailSubmission(receiptId).then(
|
|
32
|
+
(next) => { if (live) setDetail(next); },
|
|
33
|
+
() => { if (live) setDetail({ state: "unavailable" }); },
|
|
34
|
+
);
|
|
35
|
+
return () => { live = false; };
|
|
36
|
+
}, [receiptId, isDemo]);
|
|
37
|
+
if (!receiptId) return <p role="alert">A receipt is required to open a submission.</p>;
|
|
38
|
+
if (isDemo) return <p role="status">Single email submissions require an editor session.</p>;
|
|
39
|
+
return <div data-news-desk-admin-panel="monitoring">
|
|
40
|
+
{detail === null ? <p role="status">Loading email submission…</p> :
|
|
41
|
+
<EmailSubmissions items={"submission" in detail ? [detail.submission] : []} detail={detail}
|
|
42
|
+
onResolveArticleLink={resolveMonitoringEmailArticleLink} onImportCandidates={importMonitoringEmailCandidates} />}
|
|
43
|
+
</div>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Overview refreshes apply only when no receipt was requested. */
|
|
47
|
+
function MonitoringOverviewPanel({ isAdmin, isDemo }: { isAdmin: boolean; isDemo: boolean }) {
|
|
18
48
|
const [mode, setMode] = useState<MonitoringMode>("publication");
|
|
19
49
|
const [result, setResult] = useState<MonitoringResult | null>(null);
|
|
20
50
|
const [emailDetail, setEmailDetail] = useState<MonitoringEmailDetails | null>(null);
|
package/lib/monitoring-client.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type {
|
|
|
10
10
|
MonitoringResult,
|
|
11
11
|
} from "./monitoring-overview";
|
|
12
12
|
|
|
13
|
-
type QueryField = "monitoringOverview" | "monitoringEmailSubmission" | "monitoringResolveEmailArticleLink" | "intakeEmailSubmissionReferences";
|
|
13
|
+
type QueryField = "monitoringOverview" | "monitoringEmailSubmission" | "monitoringResolveEmailArticleLink" | "intakeEmailSubmissionReferences" | "monitoringQueueEmailReference";
|
|
14
14
|
type GraphQLResult = { data?: Partial<Record<QueryField, unknown>>; errors?: { message?: string; errorType?: string }[] };
|
|
15
15
|
|
|
16
16
|
async function query<T>(field: QueryField, queryText: string, variables: Record<string, string | number>): Promise<T | { error: "forbidden" } | { state: "unavailable" }> {
|
|
@@ -74,10 +74,19 @@ export async function importMonitoringEmailCandidates(
|
|
|
74
74
|
} catch {
|
|
75
75
|
return { imported: references.length, references, error: "The import response could not be read. Completed links are safe to retry." };
|
|
76
76
|
}
|
|
77
|
-
if (!response?.ok || !Array.isArray(response.references)) {
|
|
77
|
+
if (!response?.ok || !Array.isArray(response.references) || response.references.length !== batch.length) {
|
|
78
78
|
return { imported: references.length, references, error: "The import was not accepted. Check your editor access and retry." };
|
|
79
79
|
}
|
|
80
80
|
references.push(...response.references);
|
|
81
|
+
for (const [index, reference] of response.references.entries()) {
|
|
82
|
+
const queued = await query<{ queued: boolean }>("monitoringQueueEmailReference",
|
|
83
|
+
`mutation MonitoringQueueEmailReference($input: AWSJSON!) { monitoringQueueEmailReference(input: $input) }`,
|
|
84
|
+
{ input: JSON.stringify({ receiptId, ...batch[index], referenceId: reference.id }) });
|
|
85
|
+
if (!("queued" in queued) || !queued.queued) {
|
|
86
|
+
return { imported: references.length, references,
|
|
87
|
+
error: "References were registered, but predictions could not be queued. Retry these selected links safely." };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
81
90
|
}
|
|
82
91
|
return { imported: references.length, references };
|
|
83
92
|
}
|
package/package.json
CHANGED
package/routes.manifest.json
CHANGED