diff --git a/src/client/features/content/BriefTargets.tsx b/src/client/features/content/BriefTargets.tsx
index 329c388c..2eadb5b5 100644
--- a/src/client/features/content/BriefTargets.tsx
+++ b/src/client/features/content/BriefTargets.tsx
@@ -5,14 +5,24 @@ export function BriefTargets({
analyzedCount,
paaCount,
analysesPending,
+ analysesFailed,
}: {
wordCounts: number[];
h2Counts: number[];
analyzedCount: number;
paaCount: number;
analysesPending: boolean;
+ /** How many per-page analyses failed. Separate from `analysesPending`, which
+ * goes false when requests SETTLE — successfully or not. Without this a page
+ * where every analysis failed reported "No data", which reads as "the top
+ * pages have nothing to measure" rather than "we could not measure them". */
+ analysesFailed?: number;
}) {
- const pendingLabel = analysesPending ? "Analyzing top pages…" : "No data";
+ const pendingLabel = analysesPending
+ ? "Analyzing top pages…"
+ : (analysesFailed ?? 0) > 0
+ ? "Could not analyze the top pages"
+ : "No data";
return (
diff --git a/src/client/features/content/ContentOptimizerPage.tsx b/src/client/features/content/ContentOptimizerPage.tsx
index b58c3cec..3248fb45 100644
--- a/src/client/features/content/ContentOptimizerPage.tsx
+++ b/src/client/features/content/ContentOptimizerPage.tsx
@@ -379,14 +379,27 @@ export function ContentOptimizerPage({
})),
});
const analysisByUrl = new Map();
+ // A FAILED per-URL analysis also has `data === undefined`, so without tracking
+ // it separately the row cells below could not tell "still fetching" from
+ // "this one is never coming" -- and showed loading dots forever.
+ const failedUrls = new Set();
competitorUrls.forEach((url, index) => {
- const data = analysisQueries[index]?.data;
+ const query_ = analysisQueries[index];
+ const data = query_?.data;
if (data !== undefined) analysisByUrl.set(url, data);
+ if (query_?.isError) failedUrls.add(url);
});
const loadedAnalyses = [...analysisByUrl.values()].filter(
(analysis): analysis is NonNullable => analysis != null,
);
const analysesPending = analysisQueries.some((query_) => query_.isLoading);
+ // Counted so the headline cards can say the analyses failed rather than
+ // "No data". Once every request settles, `analysesPending` is false whether
+ // they succeeded or not, which is how a page of failures came to be reported
+ // as an absence of data.
+ const analysesFailed = analysisQueries.filter(
+ (query_) => query_.isError,
+ ).length;
const wordCounts = loadedAnalyses
.map((analysis) => analysis.wordCount)
@@ -610,6 +623,7 @@ export function ContentOptimizerPage({
analyzedCount={loadedAnalyses.length}
paaCount={brief.paaQuestions.length}
analysesPending={analysesPending}
+ analysesFailed={analysesFailed}
/>
{brief.terms.length > 0 ? (
@@ -696,6 +710,9 @@ export function ContentOptimizerPage({
const analysis = competitor.url
? analysisByUrl.get(competitor.url)
: undefined;
+ const analysisFailed = competitor.url
+ ? failedUrls.has(competitor.url)
+ : false;
return (
|
@@ -715,7 +732,15 @@ export function ContentOptimizerPage({
|
- {competitorsAuthorized && analysis === undefined ? (
+ {analysisFailed ? (
+
+ failed
+
+ ) : competitorsAuthorized &&
+ analysis === undefined ? (
) : analysis?.wordCount != null ? (
analysis.wordCount.toLocaleString()
@@ -724,7 +749,15 @@ export function ContentOptimizerPage({
)}
|
- {competitorsAuthorized && analysis === undefined ? (
+ {analysisFailed ? (
+
+ failed
+
+ ) : competitorsAuthorized &&
+ analysis === undefined ? (
) : (
(analysis?.h2.length ?? "—")
|