diff --git a/packages/client/src/index.css b/packages/client/src/index.css index f4cc69c92..729161ba0 100644 --- a/packages/client/src/index.css +++ b/packages/client/src/index.css @@ -14102,6 +14102,21 @@ select{ text-decoration-color:#94a3b8 } +.decoration-white{ + -webkit-text-decoration-color:#fff; + text-decoration-color:#fff +} + +.decoration-amber-700{ + -webkit-text-decoration-color:#b45309; + text-decoration-color:#b45309 +} + +.decoration-blue-800{ + -webkit-text-decoration-color:#1e40af; + text-decoration-color:#1e40af +} + .decoration-dotted{ -webkit-text-decoration-style:dotted; text-decoration-style:dotted diff --git a/packages/client/src/reports/ReportCard.tsx b/packages/client/src/reports/ReportCard.tsx index 85fd41deb..685bebf9b 100644 --- a/packages/client/src/reports/ReportCard.tsx +++ b/packages/client/src/reports/ReportCard.tsx @@ -381,6 +381,9 @@ export default function ReportCard( geographies: baseReportContext.geographies, sketchClass: subjectSketchClass ?? null, errors: cardDependencies.errors, + globalErrors: cardDependencies.globalErrors, + dependenciesAwaitingRefresh: + cardDependencies.dependenciesAwaitingRefresh, dependencyResolutionFailuresByHash: cardDependencies.dependencyResolutionFailuresByHash, }} diff --git a/packages/client/src/reports/ReportEditor.tsx b/packages/client/src/reports/ReportEditor.tsx index 2067e54de..6e1d241da 100644 --- a/packages/client/src/reports/ReportEditor.tsx +++ b/packages/client/src/reports/ReportEditor.tsx @@ -153,9 +153,16 @@ export default function ReportEditor({ const [editing, _setEditing] = useState(null); const [preselectTitle, setPreselectTitle] = useState(false); + const [pendingWidgetSettings, setPendingWidgetSettings] = useState<{ + cardId: number; + widgetPosition: number; + } | null>(null); const setEditing = useCallback( (editing: number | null, preselectTitle?: boolean) => { setPreselectTitle(preselectTitle || false); + if (editing === null) { + setPendingWidgetSettings(null); + } _setEditing(editing); }, [_setEditing, setPreselectTitle] @@ -451,6 +458,18 @@ export default function ReportEditor({ [calcDetailsModalState] ); + const requestWidgetSettings = useCallback( + (cardId: number, widgetPosition: number) => { + setPendingWidgetSettings({ cardId, widgetPosition }); + setEditing(cardId); + }, + [setEditing] + ); + + const clearPendingWidgetSettings = useCallback(() => { + setPendingWidgetSettings(null); + }, []); + const onEditorReadyForFocus = useCallback( (cardId: number, focus: () => void) => { // Ref avoids a race where context still holds a callback from before @@ -485,6 +504,9 @@ export default function ReportEditor({ preselectTitle: preselectTitle, showCalcDetails: calcDetailsModalState.state.cardId ?? undefined, setShowCalcDetails: setShowCalcDetails, + requestWidgetSettings, + pendingWidgetSettings, + clearPendingWidgetSettings, onEditorReadyForFocus, printing, setPrinting, @@ -498,6 +520,9 @@ export default function ReportEditor({ preselectTitle, calcDetailsModalState.state.cardId, setShowCalcDetails, + requestWidgetSettings, + pendingWidgetSettings, + clearPendingWidgetSettings, onEditorReadyForFocus, printing, requestFullReportPrint, diff --git a/packages/client/src/reports/components/MetricSuggestedFixes.tsx b/packages/client/src/reports/components/MetricSuggestedFixes.tsx new file mode 100644 index 000000000..d030fdd67 --- /dev/null +++ b/packages/client/src/reports/components/MetricSuggestedFixes.tsx @@ -0,0 +1,128 @@ +import { ExternalLinkIcon } from "@radix-ui/react-icons"; +import { MetricDependency } from "overlay-engine"; +import { FC } from "react"; +import { useTranslation } from "react-i18next"; + +type MetricSuggestionContext = { + type?: string; + parameters?: Record | null; +}; + +function addSuggestedFixesForError( + suggestedFixes: Record, + error: string, + contexts: MetricSuggestionContext[] +) { + if ( + error.includes("Invalid array length") && + contexts.some((context) => context.type === "raster_stats") + ) { + suggestedFixes["Adjust VRM settings"] = + "https://docs.seasketch.org/seasketch-documentation/administrators-guide/reports/debugging-timeout-and-performance-issues#raster-vrm-settings"; + } else if (/timeout/i.test(error)) { + const overlapContext = contexts.find( + (context) => + context.type === "overlay_area" || + context.parameters?.sourceHasOverlappingFeatures === true + ); + if (overlapContext) { + suggestedFixes["Adjust overlap settings"] = + "https://docs.seasketch.org/seasketch-documentation/administrators-guide/reports/debugging-timeout-and-performance-issues#overlapping-polygon-settings"; + } else { + suggestedFixes["Review report performance documentation"] = + "https://docs.seasketch.org/seasketch-documentation/administrators-guide/reports/debugging-timeout-and-performance-issues"; + } + } +} + +export function getMetricErrorInfo( + errors: string[], + dependencies: MetricDependency[] +) { + const errorMap: Record = {}; + for (const error of errors) { + if (error in errorMap) { + errorMap[error]++; + } else { + errorMap[error] = 1; + } + } + + const suggestedFixes: Record = {}; + const contexts = dependencies.map((dependency) => ({ + type: dependency.type, + parameters: dependency.parameters, + })); + for (const error of Object.keys(errorMap)) { + addSuggestedFixesForError(suggestedFixes, error, contexts); + } + + return { errorMap, suggestedFixes }; +} + +export function getSuggestedFixesForMetricError({ + error, + metricType, + parameters, +}: { + error?: string | null; + metricType?: string; + parameters?: Record | null; +}) { + const suggestedFixes: Record = {}; + if (!error) { + return suggestedFixes; + } + addSuggestedFixesForError(suggestedFixes, error, [ + { type: metricType, parameters }, + ]); + return suggestedFixes; +} + +export const MetricSuggestedFixes: FC<{ + suggestedFixes: Record; + compact?: boolean; + theme?: "light" | "dark"; +}> = ({ suggestedFixes, compact, theme = "light" }) => { + const { t } = useTranslation("reports"); + const entries = Object.entries(suggestedFixes); + if (entries.length === 0) { + return null; + } + const dark = theme === "dark"; + + return ( +
+
+ {t("Suggested fixes")} +
+
+ {entries.map(([msg, url]) => ( + + ))} +
+
+ ); +}; diff --git a/packages/client/src/reports/components/ReportCardBodyEditor.tsx b/packages/client/src/reports/components/ReportCardBodyEditor.tsx index b4cfcbe1c..023866e8b 100644 --- a/packages/client/src/reports/components/ReportCardBodyEditor.tsx +++ b/packages/client/src/reports/components/ReportCardBodyEditor.tsx @@ -1,4 +1,4 @@ -import { EditorState, TextSelection } from "prosemirror-state"; +import { EditorState, NodeSelection, TextSelection } from "prosemirror-state"; import { Node } from "prosemirror-model"; import { EditorView } from "prosemirror-view"; import { @@ -151,6 +151,8 @@ function ReportCardBodyEditorInner({ setShowCalcDetails, showCalcDetails, onEditorReadyForFocus, + pendingWidgetSettings, + clearPendingWidgetSettings, } = useContext(ReportUIStateContext); const onEditorReadyForFocusRef = useRef(onEditorReadyForFocus); @@ -286,7 +288,7 @@ function ReportCardBodyEditorInner({ const anySpatialMetricPending = slim.some( (m) => m.state !== SpatialMetricState.Complete && - m.state !== SpatialMetricState.Error, + m.state !== SpatialMetricState.Error ); if ( !draftDependenciesQuery.loading && @@ -367,7 +369,7 @@ function ReportCardBodyEditorInner({ return {} as { [dependencyHash: string]: string }; } return Object.fromEntries( - errs.map((e) => [e.dependencyHash, e.message] as const), + errs.map((e) => [e.dependencyHash, e.message] as const) ); }, [ draftDependenciesQuery.data?.draftReportDependencies @@ -695,6 +697,37 @@ function ReportCardBodyEditorInner({ saveWithBody, ]); + useEffect(() => { + if (!pendingWidgetSettings || pendingWidgetSettings.cardId !== cardId) { + return; + } + const view = viewRef.current; + if (!view) { + return; + } + const { widgetPosition } = pendingWidgetSettings; + if (widgetPosition < 0 || widgetPosition > view.state.doc.content.size) { + clearPendingWidgetSettings(); + return; + } + const node = view.state.doc.nodeAt(widgetPosition); + if ( + !node || + (node.type.name !== "metric" && node.type.name !== "blockMetric") + ) { + clearPendingWidgetSettings(); + return; + } + const tr = view.state.tr.setSelection( + NodeSelection.create(view.state.doc, widgetPosition) + ); + view.dispatch(tr); + clearPendingWidgetSettings(); + setTimeout(() => { + view.dom.focus({ preventScroll: true }); + }, 30); + }, [cardId, clearPendingWidgetSettings, pendingWidgetSettings, viewRef]); + // Update editor state when language changes (body will be different for different languages) useEffect(() => { if (viewRef.current && body) { diff --git a/packages/client/src/reports/components/ReportCardLoadingIndicator.tsx b/packages/client/src/reports/components/ReportCardLoadingIndicator.tsx index 458059c2b..35a9fddc0 100644 --- a/packages/client/src/reports/components/ReportCardLoadingIndicator.tsx +++ b/packages/client/src/reports/components/ReportCardLoadingIndicator.tsx @@ -50,7 +50,7 @@ export function computeCombinedProgress(items: MinimalJob[]): { progressPercent: number | null; // null => indeterminate farthestEta: Date | null; thresholdMet: boolean; - allComplete: boolean; + allSettled: boolean; } { const N = items.length; if (N === 0) { @@ -58,12 +58,15 @@ export function computeCombinedProgress(items: MinimalJob[]): { progressPercent: null, farthestEta: null, thresholdMet: false, - allComplete: true, + allSettled: true, }; } + const isSettled = (state: SpatialMetricState) => + state === SpatialMetricState.Complete || state === SpatialMetricState.Error; + const withEtaOrComplete = items.filter( - (i) => i.eta !== null || i.state === SpatialMetricState.Complete + (i) => i.eta !== null || isSettled(i.state) ).length; const thresholdMet = withEtaOrComplete / N >= 0.75; @@ -74,16 +77,14 @@ export function computeCombinedProgress(items: MinimalJob[]): { null; const started = items.filter((i) => i.progress > 0); - const allComplete = items.every( - (i) => i.state === SpatialMetricState.Complete - ); + const allSettled = items.every((i) => isSettled(i.state)); - if (allComplete) { + if (allSettled) { return { progressPercent: 100, farthestEta: null, thresholdMet, - allComplete, + allSettled, }; } @@ -93,7 +94,7 @@ export function computeCombinedProgress(items: MinimalJob[]): { progressPercent: null, farthestEta: null, thresholdMet, - allComplete, + allSettled, }; } const minProgress = started.reduce( @@ -109,7 +110,7 @@ export function computeCombinedProgress(items: MinimalJob[]): { progressPercent: scaledProgress, farthestEta: null, thresholdMet, - allComplete, + allSettled, }; } @@ -122,7 +123,7 @@ export function computeCombinedProgress(items: MinimalJob[]): { progressPercent: farthestItem.progress, farthestEta, thresholdMet, - allComplete, + allSettled, }; } @@ -132,7 +133,7 @@ export function computeCombinedProgress(items: MinimalJob[]): { progressPercent: null, farthestEta: null, thresholdMet, - allComplete, + allSettled, }; } const minProgress = started.reduce( @@ -143,7 +144,7 @@ export function computeCombinedProgress(items: MinimalJob[]): { progressPercent: minProgress, farthestEta: null, thresholdMet, - allComplete, + allSettled, }; } @@ -188,7 +189,7 @@ export default function ReportCardLoadingIndicator({ }, [stage, sourceJobs, metricJobs]); const isComplete = - !anySourceInProgress && computeCombinedProgress(metricJobs).allComplete; + !anySourceInProgress && computeCombinedProgress(metricJobs).allSettled; // Enforce monotonic non-decreasing visual progress with phase-aware reset const prevPercentRef = useRef(0); diff --git a/packages/client/src/reports/components/ReportTaskLineItem.tsx b/packages/client/src/reports/components/ReportTaskLineItem.tsx index 7a7bc07e5..069a2672a 100644 --- a/packages/client/src/reports/components/ReportTaskLineItem.tsx +++ b/packages/client/src/reports/components/ReportTaskLineItem.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from "react"; +import React, { useEffect, useMemo, useRef, useState } from "react"; import { SpatialMetricState } from "../../generated/graphql"; import CircularProgressIndicator from "./CircularProgressIndicator"; import ETACountdown from "./ETACountdown"; @@ -13,6 +13,10 @@ import { import bytes from "bytes"; import { Trans, useTranslation } from "react-i18next"; import JsonPreview from "../../components/JsonPreview"; +import { + getSuggestedFixesForMetricError, + MetricSuggestedFixes, +} from "./MetricSuggestedFixes"; /** * Inline menu (no portal) so it stays inside the Radix tooltip hover region. @@ -180,6 +184,7 @@ export default function ReportTaskLineItem({ reprocessLoading, }: ReportTaskLineItemProps) { const { t } = useTranslation("sketching"); + const [tooltipOpen, setTooltipOpen] = useState(false); const hasTopologyIssues = state === SpatialMetricState.Complete && numInvalidFeatures != null && @@ -242,6 +247,13 @@ export default function ReportTaskLineItem({ {t("Error")}
{errorMessage}
+ {tooltipOpen && ( + + )} )} {isAdmin && onReprocessSource && ( @@ -440,7 +452,11 @@ export default function ReportTaskLineItem({ } /> {tooltipContent ? ( - + {hasTopologyIssues ? (
@@ -497,6 +513,34 @@ export default function ReportTaskLineItem({ ); } +function MetricErrorSuggestedFixes({ + errorMessage, + metricType, + parameters, +}: { + errorMessage: string; + metricType?: string; + parameters?: Record | null; +}) { + const suggestedFixes = useMemo( + () => + getSuggestedFixesForMetricError({ + error: errorMessage, + metricType, + parameters, + }), + [errorMessage, metricType, parameters] + ); + + return ( + + ); +} + function humanizeDuration(seconds: number): string { if (seconds < 60) { if (seconds < 3) { diff --git a/packages/client/src/reports/context/CardDependenciesContext.tsx b/packages/client/src/reports/context/CardDependenciesContext.tsx index 6af0d88f1..55d97b88f 100644 --- a/packages/client/src/reports/context/CardDependenciesContext.tsx +++ b/packages/client/src/reports/context/CardDependenciesContext.tsx @@ -22,6 +22,10 @@ export type CardDependenciesValue = { | "validChildren" > | null; errors: { [errorMessage: string]: number }; + /** Errors not attributable to a single metric/source dependency (e.g. query failure). */ + globalErrors: string[]; + /** True while dependency cache was evicted and a fresh payload is in flight. */ + dependenciesAwaitingRefresh: boolean; /** * When the server could not resolve a metric dependency (e.g. stable id from * another project), maps dependencyHash → message so widgets stop waiting for a metric that will never exist. @@ -36,6 +40,8 @@ export const CardDependenciesContext = createContext({ geographies: [], sketchClass: null, errors: {}, + globalErrors: [], + dependenciesAwaitingRefresh: false, dependencyResolutionFailuresByHash: {}, }); diff --git a/packages/client/src/reports/context/ReportDependenciesContext.tsx b/packages/client/src/reports/context/ReportDependenciesContext.tsx index 200873232..82df6f00a 100644 --- a/packages/client/src/reports/context/ReportDependenciesContext.tsx +++ b/packages/client/src/reports/context/ReportDependenciesContext.tsx @@ -80,6 +80,10 @@ export type CardDependenciesResult = { overlaySources: OverlaySourceDetailsFragment[]; loading: boolean; errors: { [errorMessage: string]: number }; + /** Errors not attributable to a single metric/source dependency (e.g. query failure). */ + globalErrors: string[]; + /** True while dependency cache was evicted and a fresh payload is in flight. */ + dependenciesAwaitingRefresh: boolean; dependencyResolutionFailuresByHash: { [dependencyHash: string]: string }; }; diff --git a/packages/client/src/reports/context/ReportUIStateContext.tsx b/packages/client/src/reports/context/ReportUIStateContext.tsx index 9e400c495..1e4b06979 100644 --- a/packages/client/src/reports/context/ReportUIStateContext.tsx +++ b/packages/client/src/reports/context/ReportUIStateContext.tsx @@ -9,6 +9,12 @@ export const ReportUIStateContext = createContext<{ preselectTitle?: boolean; showCalcDetails?: number; setShowCalcDetails: (cardId: number | undefined) => void; + requestWidgetSettings: (cardId: number, widgetPosition: number) => void; + pendingWidgetSettings?: { + cardId: number; + widgetPosition: number; + } | null; + clearPendingWidgetSettings: () => void; /** Called when a card's ProseMirror editor is ready; use the focus fn for proper keyboard focus */ onEditorReadyForFocus?: ( cardId: number, @@ -27,6 +33,9 @@ export const ReportUIStateContext = createContext<{ preselectTitle: false, showCalcDetails: undefined, setShowCalcDetails: () => {}, + requestWidgetSettings: () => {}, + pendingWidgetSettings: null, + clearPendingWidgetSettings: () => {}, printing: false, setPrinting: () => {}, requestFullReportPrint: () => {}, diff --git a/packages/client/src/reports/context/useCardDependencies.tsx b/packages/client/src/reports/context/useCardDependencies.tsx index 0e2de9efb..99cdb8343 100644 --- a/packages/client/src/reports/context/useCardDependencies.tsx +++ b/packages/client/src/reports/context/useCardDependencies.tsx @@ -101,8 +101,13 @@ export function useCardDependencies(cardId: number): CardDependenciesResult { for (const msg of Object.values(dependencyResolutionFailuresByHash)) { errors[msg] = (errors[msg] || 0) + 1; } + + const globalErrors: string[] = []; if (context.error) { - errors["Dependency retrieval error: " + context.error.message] = 1; + const globalErrorMessage = + "Dependency retrieval error: " + context.error.message; + globalErrors.push(globalErrorMessage); + errors[globalErrorMessage] = 1; } return { @@ -110,12 +115,18 @@ export function useCardDependencies(cardId: number): CardDependenciesResult { overlaySources, loading, errors, + globalErrors, + dependenciesAwaitingRefresh: context.dependenciesAwaitingRefresh, dependencyResolutionFailuresByHash, }; } else { const errors: { [errorMessage: string]: number } = {}; + const globalErrors: string[] = []; if (context.error) { - errors["Dependency retrieval error: " + context.error.message] = 1; + const globalErrorMessage = + "Dependency retrieval error: " + context.error.message; + globalErrors.push(globalErrorMessage); + errors[globalErrorMessage] = 1; } return { @@ -123,6 +134,8 @@ export function useCardDependencies(cardId: number): CardDependenciesResult { overlaySources: [], loading: context.loading, errors: errors, + globalErrors, + dependenciesAwaitingRefresh: context.dependenciesAwaitingRefresh, dependencyResolutionFailuresByHash: {}, }; } diff --git a/packages/client/src/reports/hooks/useWidgetDependencies.ts b/packages/client/src/reports/hooks/useWidgetDependencies.ts index a528b50e6..2722f4a54 100644 --- a/packages/client/src/reports/hooks/useWidgetDependencies.ts +++ b/packages/client/src/reports/hooks/useWidgetDependencies.ts @@ -85,6 +85,21 @@ function useStableSources( return ref.current; } +function sourceProcessingInProgress( + source: OverlaySourceDetailsFragment +): boolean { + const jobState = source.sourceProcessingJob?.state; + if (jobState === SpatialMetricState.Complete) return false; + if (jobState === SpatialMetricState.Error) return false; + if ( + jobState === SpatialMetricState.Queued || + jobState === SpatialMetricState.Processing + ) { + return true; + } + return !source.output?.url; +} + /** * Hook that returns stable widget dependencies, only triggering re-renders * when the widget's specific metrics/sources actually change. @@ -100,10 +115,10 @@ export function useWidgetDependencies( const { metrics: cardMetrics, sources: cardSources, - loading: cardLoading, geographies: contextGeographies, sketchClass: contextSketchClass, - errors: cardErrors, + globalErrors, + dependenciesAwaitingRefresh, dependencyResolutionFailuresByHash, } = useCardDependenciesContext(); @@ -171,8 +186,8 @@ export function useWidgetDependencies( // Filter metrics and sources for this widget const { rawMetrics, rawSources, loading, errors } = useMemo(() => { - let loading = cardLoading; - let errors: string[] = []; + let loading = dependenciesAwaitingRefresh; + const errors: string[] = []; const filteredMetrics = filterMetricsByDependencies( allMetrics, @@ -180,7 +195,6 @@ export function useWidgetDependencies( sourceUrlMap ) as CompatibleSpatialMetricDetailsFragment[]; - // Check loading and error states for (const metric of filteredMetrics) { if ( metric.state === SpatialMetricState.DependencyNotReady || @@ -194,30 +208,53 @@ export function useWidgetDependencies( } } - // Filter sources for this widget's dependencies const filteredSources = allSources.filter((s) => (dependencies || []).some( (d: MetricDependency) => d.stableId === s.stableId ) ); - // Check if we're still waiting for metrics - if (!loading) { - for (const dependency of dependencies || []) { - if ( - fragmentMetricsWillNeverExist && - dependency.subjectType === "fragments" - ) { - continue; + for (const source of filteredSources) { + if (sourceProcessingInProgress(source)) { + loading = true; + } + if (source.sourceProcessingJob?.state === SpatialMetricState.Error) { + errors.push( + source.sourceProcessingJob?.errorMessage || "Unknown error" + ); + } + } + + for (const dependency of dependencies || []) { + if ( + fragmentMetricsWillNeverExist && + dependency.subjectType === "fragments" + ) { + continue; + } + const hash = hashMetricDependency(dependency, sourceUrlMap); + const relatedMetric = filteredMetrics.find( + (m) => m.dependencyHash === hash + ); + if (!relatedMetric) { + const resolutionErr = resolutionFailuresByHash[hash]; + if (resolutionErr) { + errors.push(resolutionErr); + } else { + loading = true; } - const hash = hashMetricDependency(dependency, sourceUrlMap); - const relatedMetric = filteredMetrics.find( - (m) => m.dependencyHash === hash + } + + if (dependency.stableId) { + const sourceKnown = allSources.some( + (s) => s.stableId === dependency.stableId ); - if (!relatedMetric) { + if (!sourceKnown) { const resolutionErr = resolutionFailuresByHash[hash]; if (resolutionErr) { - errors.push(resolutionErr); + if (!errors.includes(resolutionErr)) { + errors.push(resolutionErr); + } } else { loading = true; } @@ -225,8 +262,12 @@ export function useWidgetDependencies( } } - if (cardErrors) { - errors.push(...Object.keys(cardErrors)); + if (globalErrors.length > 0) { + for (const msg of globalErrors) { + if (!errors.includes(msg)) { + errors.push(msg); + } + } } return { @@ -238,10 +279,10 @@ export function useWidgetDependencies( }, [ allMetrics, allSources, - cardLoading, dependencies, sourceUrlMap, - cardErrors, + globalErrors, + dependenciesAwaitingRefresh, fragmentMetricsWillNeverExist, resolutionFailuresByHash, ]); diff --git a/packages/client/src/reports/widgets/widgets.tsx b/packages/client/src/reports/widgets/widgets.tsx index 75910d340..cf57121a0 100644 --- a/packages/client/src/reports/widgets/widgets.tsx +++ b/packages/client/src/reports/widgets/widgets.tsx @@ -1,6 +1,14 @@ import { InlineMetric, InlineMetricTooltipControls } from "./InlineMetric"; import { ReportWidgetTooltipControls } from "../../editor/TooltipMenu"; -import { FC, useContext, useMemo, useState, useEffect, memo } from "react"; +import { + FC, + useCallback, + useContext, + useMemo, + useState, + useEffect, + memo, +} from "react"; import { CommandPaletteGroup, CommandPaletteItem, @@ -87,8 +95,12 @@ import { import { Mark, Node } from "prosemirror-model"; import { useWidgetDependencies } from "../hooks/useWidgetDependencies"; import { ReportUIStateContext } from "../context/ReportUIStateContext"; +import { useReactNodeView } from "../ReactNodeView"; import { FormLanguageContext } from "../../formElements/FormElement"; -import { ExclamationTriangleIcon, Pencil2Icon } from "@radix-ui/react-icons"; +import { + ExclamationTriangleIcon, + Pencil2Icon, +} from "@radix-ui/react-icons"; import { FolderIcon } from "@heroicons/react/outline"; import Badge from "../../components/Badge"; import ProfilePhoto from "../../admin/users/ProfilePhoto"; @@ -102,6 +114,10 @@ import * as Popover from "@radix-ui/react-popover"; import { TooltipPopoverContent } from "../../editor/TooltipMenu"; import useDebounce from "../../useDebounce"; import * as Tooltip from "@radix-ui/react-tooltip"; +import { + getMetricErrorInfo, + MetricSuggestedFixes, +} from "../components/MetricSuggestedFixes"; type WidgetComponent = React.FC; @@ -233,18 +249,83 @@ function memoWidget(Component: WidgetComponent, name: string) { return memo(Component, widgetPropsAreEqual); } +const WidgetErrorActions: FC<{ + cardId: number; + compact?: boolean; +}> = ({ cardId, compact }) => { + const { setShowCalcDetails, requestWidgetSettings, adminMode } = + useContext(ReportUIStateContext); + const { getPos } = useReactNodeView(); + const { t } = useTranslation("reports"); + + const onAdjustSettings = useCallback( + (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + if (typeof getPos !== "function") { + return; + } + try { + requestWidgetSettings(cardId, getPos()); + } catch (e) { + // Node views can briefly outlive their ProseMirror position during + // edits. In that case, do nothing rather than throwing from the button. + } + }, + [cardId, getPos, requestWidgetSettings] + ); + + const onViewDetails = useCallback( + (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + setShowCalcDetails(cardId); + }, + [cardId, setShowCalcDetails] + ); + + return ( +
+ + {adminMode && typeof getPos === "function" ? ( + + ) : null} +
+ ); +}; + /** * Error display components that access ReportContext only when errors occur. * By isolating context access here, we avoid subscribing to context in the * normal (non-error) rendering path. */ -const WidgetErrorInline: FC<{ errors: string[]; cardId: number }> = ({ - errors, - cardId, -}) => { +const WidgetErrorInline: FC<{ + errors: string[]; + cardId: number; + dependencies: MetricDependency[]; +}> = ({ errors, cardId, dependencies }) => { const { setShowCalcDetails } = useContext(ReportUIStateContext); const { t } = useTranslation("reports"); const errorDetails = errors.join(". \n"); + const { errorMap, suggestedFixes } = useMemo( + () => getMetricErrorInfo(errors, dependencies), + [dependencies, errors] + ); + return ( @@ -255,77 +336,96 @@ const WidgetErrorInline: FC<{ errors: string[]; cardId: number }> = ({ > {t("Error")} - {errorDetails} + + {errorDetails} + -
- -
-
{t("Error")}
- {errors.length > 1 ? ( -
    - {errors.map((error) => ( -
  • {error}
  • - ))} -
- ) : ( -
{errorDetails}
- )} +
+
+ +
+ {t("Calculation error")} +
+
    + {Object.entries(errorMap).map(([error, count]) => ( +
  • + + + {error} + + {Number(count) > 1 && ( + + {Number(count)} + {t("x")} + + )} +
  • + ))} +
+ +
- + ); }; -const WidgetErrorBlock: FC<{ errors: string[]; cardId: number }> = ({ - errors, - cardId, -}) => { - const { setShowCalcDetails } = useContext(ReportUIStateContext); +const WidgetErrorBlock: FC<{ + errors: string[]; + cardId: number; + dependencies: MetricDependency[]; + widgetType?: string; +}> = ({ errors, cardId, dependencies, widgetType }) => { const { t } = useTranslation("reports"); - const errorMap: Record = {}; - for (const error of errors) { - if (error in errorMap) { - errorMap[error]++; - } else { - errorMap[error] = 1; - } - } + const { errorMap, suggestedFixes } = useMemo( + () => getMetricErrorInfo(errors, dependencies), + [dependencies, errors] + ); return ( -
-
- -
{t("Error")}
+
+
+
+ +
+ {t("Calculation error")} +
+
+ {widgetType ? ( +
+ {widgetType} +
+ ) : null} +
    + {Object.entries(errorMap).map(([msg, count]) => ( +
  • + + {msg} + {Number(count) > 1 && ( + + {Number(count)} + {t("x")} + + )} +
  • + ))} +
+ +
-
    - {Object.entries(errorMap).map(([msg, count]) => ( -
  • - {msg}{" "} - {Number(count) > 1 && ( - - {Number(count)} - {t("x")} - - )} -
  • - ))} -
-
); }; @@ -621,11 +721,20 @@ export const ReportWidgetNodeViewRouter: FC = (props: any) => { const missingSketchClassErrors = ["Sketch class not available"]; if (node.isInline) { return ( - + ); } return ( - + ); } @@ -633,9 +742,22 @@ export const ReportWidgetNodeViewRouter: FC = (props: any) => { // to context when there are actual errors (exceptional case) if (errors.length > 0) { if (node.isInline) { - return ; + return ( + + ); } else { - return ; + return ( + + ); } } @@ -703,9 +825,18 @@ export const ReportWidgetNodeViewRouter: FC = (props: any) => { const message = error instanceof Error ? error.message : "Widget failed to render"; return node.isInline ? ( - + ) : ( - + ); }} > diff --git a/packages/overlay-engine/dist/rasterStats.d.ts b/packages/overlay-engine/dist/rasterStats.d.ts index 724896105..8a599fd62 100644 --- a/packages/overlay-engine/dist/rasterStats.d.ts +++ b/packages/overlay-engine/dist/rasterStats.d.ts @@ -21,12 +21,14 @@ export declare function groundPixelDimensionsMeters(raster: GeorasterLike, cente * - `number` → explicit value; expands to [n, n] (min 1). * - `'auto'` → targets ~100 m virtual grid cells. Returns [1, 1] when * native pixels are already finer than 100 m. - * Hard per-axis cap: MAX_VRM_PER_AXIS. + * Hard per-axis cap: MAX_VRM_PER_AXIS. Also, based on + * intersectingPixelCount, we don't want to end up with more than + * MAX_VRM_PIXELS_PER_AXIS pixels. */ export declare function resolveVrm(vrmOpt: false | "auto" | number | undefined, fragmentAreaSqM: number, groundDims: { mX: number; mY: number; -}): [number, number] | null; +}, intersectingPixelCounts: [number, number]): [number, number] | null; export declare function downsampleHistogram(histogram: HistogramEntry[], maxEntries: number): HistogramEntry[]; /** * Calculate raster statistics for a feature that has already been reprojected diff --git a/packages/overlay-engine/dist/rasterStats.d.ts.map b/packages/overlay-engine/dist/rasterStats.d.ts.map index b08e241e0..119bdfe5f 100644 --- a/packages/overlay-engine/dist/rasterStats.d.ts.map +++ b/packages/overlay-engine/dist/rasterStats.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"rasterStats.d.ts","sourceRoot":"","sources":["../src/rasterStats.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAK/D,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAGpD,MAAM,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAyB9C,UAAU,aAAa;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,aAAa,EACrB,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAsC5B;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,EAC3C,eAAe,EAAE,MAAM,EACvB,UAAU,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACrC,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CA6BzB;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,cAAc,EAAE,EAC3B,UAAU,EAAE,MAAM,GACjB,cAAc,EAAE,CAqClB;AAWD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,OAAO,CAAC,OAAO,GAAG,YAAY,CAAC,EACxC,OAAO,CAAC,EAAE;IACR,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;IAC9B,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GACA,OAAO,CAAC;IAAE,KAAK,EAAE,eAAe,EAAE,CAAA;CAAE,CAAC,CAuKvC"} \ No newline at end of file +{"version":3,"file":"rasterStats.d.ts","sourceRoot":"","sources":["../src/rasterStats.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAK/D,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAGpD,MAAM,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAyB9C,UAAU,aAAa;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,aAAa,EACrB,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAsC5B;AAKD;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,EAC3C,eAAe,EAAE,MAAM,EACvB,UAAU,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,EACtC,uBAAuB,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GACxC,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CA4CzB;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,cAAc,EAAE,EAC3B,UAAU,EAAE,MAAM,GACjB,cAAc,EAAE,CAqClB;AAWD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,OAAO,CAAC,OAAO,GAAG,YAAY,CAAC,EACxC,OAAO,CAAC,EAAE;IACR,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;IAC9B,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GACA,OAAO,CAAC;IAAE,KAAK,EAAE,eAAe,EAAE,CAAA;CAAE,CAAC,CAkLvC"} \ No newline at end of file diff --git a/packages/overlay-engine/dist/rasterStats.js b/packages/overlay-engine/dist/rasterStats.js index 26a8f3bc9..52be6682b 100644 --- a/packages/overlay-engine/dist/rasterStats.js +++ b/packages/overlay-engine/dist/rasterStats.js @@ -65,6 +65,8 @@ function groundPixelDimensionsMeters(raster, centerLonLat) { } return { mX, mY }; } +// Limit to 2 GB of pixels, which is about 1/4 of the memory available. +const MAX_VRM_PIXELS_PER_AXIS = (2 * 1024 * 1024 * 1024) / 2 / 16; /** * Resolve the VRM value to apply given user options and fragment area. * @@ -72,15 +74,29 @@ function groundPixelDimensionsMeters(raster, centerLonLat) { * - `number` → explicit value; expands to [n, n] (min 1). * - `'auto'` → targets ~100 m virtual grid cells. Returns [1, 1] when * native pixels are already finer than 100 m. - * Hard per-axis cap: MAX_VRM_PER_AXIS. + * Hard per-axis cap: MAX_VRM_PER_AXIS. Also, based on + * intersectingPixelCount, we don't want to end up with more than + * MAX_VRM_PIXELS_PER_AXIS pixels. */ -function resolveVrm(vrmOpt, fragmentAreaSqM, groundDims) { +function resolveVrm(vrmOpt, fragmentAreaSqM, groundDims, intersectingPixelCounts) { + let MAX_VRM = MAX_VRM_PER_AXIS; + // first, adjust MAX_VRM based on intersectingPixelCounts + // this is to ensure that, in approaching the goal of 100 m virtual pixels, + // we don't blow thru available memory and get "Invalid array length" errors. + const intersectingPixels = intersectingPixelCounts[0] * intersectingPixelCounts[1]; + if (intersectingPixels > MAX_VRM_PIXELS_PER_AXIS) { + MAX_VRM = Math.floor(MAX_VRM_PIXELS_PER_AXIS / intersectingPixelCounts[0]); + } + if (intersectingPixels > MAX_VRM_PIXELS_PER_AXIS) { + MAX_VRM = Math.floor(MAX_VRM_PIXELS_PER_AXIS / intersectingPixelCounts[1]); + } if (vrmOpt === false) return null; if (typeof vrmOpt === "number") { const v = Math.max(1, Math.round(vrmOpt)); return [v, v]; } + MAX_VRM = Math.max(MAX_VRM, 1); // 'auto': upsample until virtual pixels are ~100 m on each axis const targetMeters = 100; if (!Number.isFinite(groundDims.mX) || @@ -89,8 +105,8 @@ function resolveVrm(vrmOpt, fragmentAreaSqM, groundDims) { groundDims.mY <= 0) { return [1, 1]; } - const vx = Math.min(MAX_VRM_PER_AXIS, Math.max(1, Math.ceil(groundDims.mX / targetMeters))); - const vy = Math.min(MAX_VRM_PER_AXIS, Math.max(1, Math.ceil(groundDims.mY / targetMeters))); + const vx = Math.min(MAX_VRM, Math.max(1, Math.ceil(groundDims.mX / targetMeters))); + const vy = Math.min(MAX_VRM, Math.max(1, Math.ceil(groundDims.mY / targetMeters))); return [vx, vy]; } function downsampleHistogram(histogram, maxEntries) { @@ -198,8 +214,15 @@ async function calculateRasterStats(sourceUrl, feature, options) { const groundDims = centerLonLat != null ? groundPixelDimensionsMeters(raster, centerLonLat) : { mX: 0, mY: 0 }; + // calculate the number of pixels on each axis of the bounding box, based + // on the ground dimensions and the pixel width/height of the raster + const intersectingPixelCounts = [ + Math.floor(featureBBox[2] - featureBBox[0] / raster.pixelWidth), + Math.floor(featureBBox[3] - featureBBox[1] / raster.pixelHeight), + ]; const vrmOpt = options?.vrm ?? "auto"; - const resolvedVrm = resolveVrm(vrmOpt, fragmentAreaSqM, groundDims); + const resolvedVrm = resolveVrm(vrmOpt, fragmentAreaSqM, groundDims, intersectingPixelCounts); + console.log("resolvedVrm", resolvedVrm); const statsExtra = resolvedVrm != null ? { vrm: resolvedVrm, rescale: true } : undefined; @@ -274,7 +297,6 @@ async function calculateRasterStats(sourceUrl, feature, options) { console.error("Error calculating raster stats", e); console.log(sourceUrl); console.log(feature); - console.log(feature.geometry.coordinates); if (typeof e === "string" && e.includes("No Values")) { return { bands: [ diff --git a/packages/overlay-engine/dist/rasterStats.js.map b/packages/overlay-engine/dist/rasterStats.js.map index 7849ba4fa..c98dd3c54 100644 --- a/packages/overlay-engine/dist/rasterStats.js.map +++ b/packages/overlay-engine/dist/rasterStats.js.map @@ -1 +1 @@ -{"version":3,"file":"rasterStats.js","sourceRoot":"","sources":["../src/rasterStats.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"rasterStats.js","sourceRoot":"","sources":["../src/rasterStats.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/overlay-engine/src/rasterStats.ts b/packages/overlay-engine/src/rasterStats.ts index eee7bb3d1..59b77fd23 100644 --- a/packages/overlay-engine/src/rasterStats.ts +++ b/packages/overlay-engine/src/rasterStats.ts @@ -84,6 +84,9 @@ export function groundPixelDimensionsMeters( return { mX, mY }; } +// Limit to 2 GB of pixels, which is about 1/4 of the memory available. +const MAX_VRM_PIXELS_PER_AXIS = (2 * 1024 * 1024 * 1024) / 2 / 16; + /** * Resolve the VRM value to apply given user options and fragment area. * @@ -91,13 +94,29 @@ export function groundPixelDimensionsMeters( * - `number` → explicit value; expands to [n, n] (min 1). * - `'auto'` → targets ~100 m virtual grid cells. Returns [1, 1] when * native pixels are already finer than 100 m. - * Hard per-axis cap: MAX_VRM_PER_AXIS. + * Hard per-axis cap: MAX_VRM_PER_AXIS. Also, based on + * intersectingPixelCount, we don't want to end up with more than + * MAX_VRM_PIXELS_PER_AXIS pixels. */ export function resolveVrm( vrmOpt: false | "auto" | number | undefined, fragmentAreaSqM: number, groundDims: { mX: number; mY: number }, + intersectingPixelCounts: [number, number], ): [number, number] | null { + let MAX_VRM = MAX_VRM_PER_AXIS; + // first, adjust MAX_VRM based on intersectingPixelCounts + // this is to ensure that, in approaching the goal of 100 m virtual pixels, + // we don't blow thru available memory and get "Invalid array length" errors. + const intersectingPixels = + intersectingPixelCounts[0] * intersectingPixelCounts[1]; + if (intersectingPixels > MAX_VRM_PIXELS_PER_AXIS) { + MAX_VRM = Math.floor(MAX_VRM_PIXELS_PER_AXIS / intersectingPixelCounts[0]); + } + if (intersectingPixels > MAX_VRM_PIXELS_PER_AXIS) { + MAX_VRM = Math.floor(MAX_VRM_PIXELS_PER_AXIS / intersectingPixelCounts[1]); + } + if (vrmOpt === false) return null; if (typeof vrmOpt === "number") { @@ -105,6 +124,8 @@ export function resolveVrm( return [v, v]; } + MAX_VRM = Math.max(MAX_VRM, 1); + // 'auto': upsample until virtual pixels are ~100 m on each axis const targetMeters = 100; @@ -118,11 +139,11 @@ export function resolveVrm( } const vx = Math.min( - MAX_VRM_PER_AXIS, + MAX_VRM, Math.max(1, Math.ceil(groundDims.mX / targetMeters)), ); const vy = Math.min( - MAX_VRM_PER_AXIS, + MAX_VRM, Math.max(1, Math.ceil(groundDims.mY / targetMeters)), ); return [vx, vy]; @@ -258,9 +279,21 @@ export async function calculateRasterStats( centerLonLat != null ? groundPixelDimensionsMeters(raster, centerLonLat) : { mX: 0, mY: 0 }; + // calculate the number of pixels on each axis of the bounding box, based + // on the ground dimensions and the pixel width/height of the raster + const intersectingPixelCounts = [ + Math.floor(featureBBox[2] - featureBBox[0] / raster.pixelWidth), + Math.floor(featureBBox[3] - featureBBox[1] / raster.pixelHeight), + ] as [number, number]; const vrmOpt = options?.vrm ?? "auto"; - const resolvedVrm = resolveVrm(vrmOpt, fragmentAreaSqM, groundDims); + const resolvedVrm = resolveVrm( + vrmOpt, + fragmentAreaSqM, + groundDims, + intersectingPixelCounts, + ); + console.log("resolvedVrm", resolvedVrm); const statsExtra = resolvedVrm != null ? { vrm: resolvedVrm, rescale: true as const } @@ -354,7 +387,6 @@ export async function calculateRasterStats( console.error("Error calculating raster stats", e); console.log(sourceUrl); console.log(feature); - console.log(feature.geometry.coordinates); if (typeof e === "string" && e.includes("No Values")) { return { bands: [