Add Cancel import button to dashboard
action: handles _action=cancel by calling POST /pipeline/cancel/:jobId UI: - Start button disabled while a job is running for that source - Cancel import button appears only when the active source has a running job (status not in done/error/cancelled) - Toast shown on cancel request confirming stop after current step - runningSources excludes cancelled status so tab resets correctly Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
eae9f19e9d
commit
9f942b4205
@ -90,6 +90,25 @@ export const action = async ({ request }) => {
|
||||
const formData = await request.formData();
|
||||
const backendApiUrl = getBackendApiUrl();
|
||||
const shop = session.shop;
|
||||
const _action = String(formData.get("_action") || "run");
|
||||
|
||||
// Cancel a running job
|
||||
if (_action === "cancel") {
|
||||
const jobId = String(formData.get("jobId") || "").trim();
|
||||
if (!jobId) return { ok: false, error: "Missing jobId." };
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${backendApiUrl}/pipeline/cancel/${encodeURIComponent(jobId)}`,
|
||||
{ method: "POST" }
|
||||
);
|
||||
const payload = await readJsonSafe(response);
|
||||
return { ok: response.ok, cancelled: true, jobId, ...payload };
|
||||
} catch (error) {
|
||||
return { ok: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// Start a new import job
|
||||
const limitValue = String(formData.get("limit") || "").trim();
|
||||
const source = String(formData.get("source") || "kyt").trim() || "kyt";
|
||||
const limit = limitValue ? Number(limitValue) : null;
|
||||
@ -97,9 +116,7 @@ export const action = async ({ request }) => {
|
||||
try {
|
||||
const response = await fetch(`${backendApiUrl}/pipeline/run`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
shop,
|
||||
source,
|
||||
@ -109,10 +126,7 @@ export const action = async ({ request }) => {
|
||||
|
||||
const payload = await readJsonSafe(response);
|
||||
if (!response.ok) {
|
||||
return {
|
||||
ok: false,
|
||||
error: payload?.error || "Failed to start import job.",
|
||||
};
|
||||
return { ok: false, error: payload?.error || "Failed to start import job." };
|
||||
}
|
||||
|
||||
return {
|
||||
@ -124,10 +138,7 @@ export const action = async ({ request }) => {
|
||||
backendApiUrl,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
ok: false,
|
||||
error: error.message,
|
||||
};
|
||||
return { ok: false, error: error.message };
|
||||
}
|
||||
};
|
||||
|
||||
@ -741,7 +752,7 @@ export default function Index() {
|
||||
const runningSources = useMemo(() => {
|
||||
return new Set(
|
||||
Object.entries(sourceJobs || {})
|
||||
.filter(([, sourceJob]) => sourceJob && !["done", "error"].includes(sourceJob.status))
|
||||
.filter(([, sourceJob]) => sourceJob && !["done", "error", "cancelled"].includes(sourceJob.status))
|
||||
.map(([sourceKey]) => sourceKey),
|
||||
);
|
||||
}, [sourceJobs]);
|
||||
@ -780,6 +791,11 @@ export default function Index() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fetcher.data.ok && fetcher.data.cancelled) {
|
||||
shopify.toast.show("Import cancellation requested — stopping after current step.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (fetcher.data.ok && fetcher.data.jobId) {
|
||||
const sourceKey = fetcher.data.source || activeSourceConfig.sourceKey;
|
||||
setActiveSource(sourceKey);
|
||||
@ -955,6 +971,7 @@ export default function Index() {
|
||||
Launch this source independently and track its live sync below.
|
||||
</p>
|
||||
<fetcher.Form method="post">
|
||||
<input type="hidden" name="_action" value="run" />
|
||||
<input type="hidden" name="source" value={activeSourceConfig.sourceKey} />
|
||||
<div className={styles.controlForm}>
|
||||
<div className={styles.actionRow}>
|
||||
@ -962,6 +979,7 @@ export default function Index() {
|
||||
type="submit"
|
||||
variant="primary"
|
||||
{...(connection?.status !== 1 ? { disabled: true } : {})}
|
||||
{...(isSubmitting || runningSources.has(activeSourceConfig.sourceKey) ? { disabled: true } : {})}
|
||||
{...(isSubmitting ? { loading: true } : {})}
|
||||
>
|
||||
Start {activeSourceConfig.label}
|
||||
@ -969,6 +987,23 @@ export default function Index() {
|
||||
</div>
|
||||
</div>
|
||||
</fetcher.Form>
|
||||
|
||||
{runningSources.has(activeSourceConfig.sourceKey) && activeJobId && (
|
||||
<fetcher.Form method="post" style={{ marginTop: "8px" }}>
|
||||
<input type="hidden" name="_action" value="cancel" />
|
||||
<input type="hidden" name="jobId" value={activeJobId} />
|
||||
<div className={styles.actionRow}>
|
||||
<s-button
|
||||
type="submit"
|
||||
variant="secondary"
|
||||
tone="critical"
|
||||
{...(fetcher.state !== "idle" ? { loading: true } : {})}
|
||||
>
|
||||
Cancel import
|
||||
</s-button>
|
||||
</div>
|
||||
</fetcher.Form>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user