diff --git a/ui/src/pages/CloudUpstream.tsx b/ui/src/pages/CloudUpstream.tsx
index 278005500e0..0e7dee0aa72 100644
--- a/ui/src/pages/CloudUpstream.tsx
+++ b/ui/src/pages/CloudUpstream.tsx
@@ -157,7 +157,7 @@ export function CloudUpstream() {
setPreview(nextPreview);
setActionError(null);
},
- onError: (error) => setActionError(error instanceof Error ? error.message : "Failed to preview push."),
+ onError: (error) => setActionError(previewErrorMessage(error)),
});
const runMutation = useMutation({
@@ -267,15 +267,18 @@ export function CloudUpstream() {
Schema {connection.target.schemaMajor}. Max chunk {formatBytes(connection.target.maxChunkBytes)}.
-
+
+
+ {previewMutation.isPending ?
: null}
+
) : (
@@ -404,6 +407,21 @@ export function CloudUpstream() {
);
}
+function PreviewProgressHint() {
+ const [elapsed, setElapsed] = useState(0);
+ useEffect(() => {
+ const startedAt = Date.now();
+ const interval = window.setInterval(() => setElapsed(Math.round((Date.now() - startedAt) / 1000)), 1000);
+ return () => window.clearInterval(interval);
+ }, []);
+ const message = elapsed < 15
+ ? "Building manifest..."
+ : elapsed < 45
+ ? `Building manifest... ${elapsed}s. Large companies can take up to a minute.`
+ : `Still building manifest... ${elapsed}s. PAP-scale companies routinely take ~60s.`;
+ return
{message}
;
+}
+
function Stepper({ activeStep }: { activeStep: CloudUpstreamStep }) {
const activeIndex = STEPS.findIndex((step) => step.key === activeStep);
return (
@@ -601,3 +619,11 @@ function formatBytes(value: number) {
if (value >= 1024) return `${Math.round(value / 1024)} KiB`;
return `${value} B`;
}
+
+function previewErrorMessage(error: unknown): string {
+ const code = error instanceof Error ? error.message : null;
+ if (code === "payload_too_large" || code === "bad_request") {
+ return "Local company is too large to preview as a single request. Click Push to continue (the Push step uploads in chunks), or see the docs for chunked-preview options.";
+ }
+ return code ?? "Failed to preview push.";
+}