|
1 | 1 | <script> |
| 2 | + import { env } from '$env/dynamic/public'; |
2 | 3 | import { onMount } from 'svelte'; |
3 | 4 | import { writable } from 'svelte/store'; |
4 | | - import { goto, beforeNavigate } from '$app/navigation'; |
| 5 | + import { beforeNavigate } from '$app/navigation'; |
5 | 6 | import { page } from '$app/stores'; |
6 | 7 | import ArgumentForm from '$lib/components/workflow/ArgumentForm.svelte'; |
7 | 8 | import ConfirmActionButton from '$lib/components/common/ConfirmActionButton.svelte'; |
|
14 | 15 | import StandardDismissableAlert from '$lib/components/common/StandardDismissableAlert.svelte'; |
15 | 16 | import VersionUpdate from '$lib/components/workflow/VersionUpdate.svelte'; |
16 | 17 | import { getAllNewVersions } from '$lib/components/workflow/version-checker'; |
| 18 | + import JobStatusIcon from '$lib/components/jobs/JobStatusIcon.svelte'; |
| 19 | +
|
| 20 | + /** @type {import('$lib/types').Workflow} */ |
| 21 | + let workflow = $page.data.workflow; |
| 22 | + /** @type {import('$lib/types').Project} */ |
| 23 | + let project = $page.data.project; |
| 24 | + /** @type {import('$lib/types').Dataset[]} */ |
| 25 | + let datasets = $page.data.datasets; |
| 26 | + // List of available tasks to be inserted into workflow |
| 27 | + let availableTasks = []; |
| 28 | +
|
| 29 | + /** @type {number|undefined} */ |
| 30 | + let selectedInputDatasetId; |
| 31 | + /** @type {number|undefined} */ |
| 32 | + let selectedOutputDatasetId; |
17 | 33 |
|
18 | | - // Workflow |
19 | | - /** @type {import('$lib/types').Workflow|undefined} */ |
20 | | - let workflow = undefined; |
21 | 34 | /** @type {import('$lib/components/common/StandardErrorAlert.svelte').default|undefined} */ |
22 | 35 | let workflowErrorAlert = undefined; |
23 | | - // Project context properties |
24 | | - let project = undefined; |
25 | | - let datasets = []; |
26 | | - // List of available tasks to be inserted into workflow |
27 | | - let availableTasks = []; |
28 | 36 |
|
29 | 37 | /** @type {import('svelte/types/runtime/store').Writable<import('$lib/types').WorkflowTask|undefined>} */ |
30 | 38 | let workflowTaskContext = writable(undefined); |
|
34 | 42 | let selectedWorkflowTask = undefined; |
35 | 43 | let originalMetaProperties = {}; |
36 | 44 | let checkingConfiguration = false; |
37 | | - let inputDatasetControl = ''; |
38 | | - let outputDatasetControl = ''; |
39 | 45 | let workerInitControl = ''; |
40 | 46 | let firstTaskIndexControl = ''; |
41 | 47 | let lastTaskIndexControl = ''; |
|
69 | 75 | /** @type {{ [id: string]: import('$lib/types').Task[] }} */ |
70 | 76 | let newVersionsMap = {}; |
71 | 77 |
|
72 | | - $: updatableWorkflowList = workflow?.task_list || []; |
| 78 | + $: updatableWorkflowList = workflow.task_list || []; |
73 | 79 |
|
74 | 80 | workflowTaskContext.subscribe((value) => { |
75 | 81 | selectedWorkflowTask = value; |
|
81 | 87 | } |
82 | 88 | }); |
83 | 89 |
|
| 90 | + const updateJobsInterval = env.PUBLIC_UPDATE_JOBS_INTERVAL |
| 91 | + ? parseInt(env.PUBLIC_UPDATE_JOBS_INTERVAL) |
| 92 | + : 3000; |
| 93 | +
|
84 | 94 | onMount(async () => { |
85 | | - workflow = $page.data.workflow; |
86 | | - project = $page.data.project; |
87 | | - datasets = $page.data.datasets; |
88 | | - checkNewVersions(); |
| 95 | + await loadJobsStatus(); |
| 96 | + await checkNewVersions(); |
89 | 97 | }); |
90 | 98 |
|
91 | 99 | beforeNavigate((navigation) => { |
|
401 | 409 | if (!workflow) { |
402 | 410 | return; |
403 | 411 | } |
404 | | - if (inputDatasetControl === '') { |
| 412 | + if (selectedInputDatasetId === undefined) { |
405 | 413 | // Preliminary check: if inputDatasetControl is not set, raise an error |
406 | 414 | let message = 'Input dataset is required. Select one from the list.'; |
407 | 415 | console.error(message); |
408 | 416 | runWorkflowModal.displayErrorAlert(message); |
409 | | - } else if (outputDatasetControl === '') { |
| 417 | + } else if (selectedOutputDatasetId === undefined) { |
410 | 418 | // Preliminary check: if outputDatasetControl is not set, raise an error |
411 | 419 | let message = 'Output dataset is required. Select one from the list.'; |
412 | 420 | console.error(message); |
|
423 | 431 | headers.set('Content-Type', 'application/json'); |
424 | 432 |
|
425 | 433 | const response = await fetch( |
426 | | - `/api/v1/project/${project.id}/workflow/${workflow.id}/apply?input_dataset_id=${inputDatasetControl}&output_dataset_id=${outputDatasetControl}`, |
| 434 | + `/api/v1/project/${project.id}/workflow/${workflow.id}/apply?input_dataset_id=${selectedInputDatasetId}&output_dataset_id=${selectedOutputDatasetId}`, |
427 | 435 | { |
428 | 436 | method: 'POST', |
429 | 437 | credentials: 'include', |
|
438 | 446 | // @ts-ignore |
439 | 447 | // eslint-disable-next-line |
440 | 448 | runWorkflowModal.toggle(); |
441 | | - // Navigate to project jobs page |
442 | | - // Define URL to navigate to |
443 | | - const jobsUrl = new URL(`projects/${project.id}/workflows/${workflow.id}/jobs`, window.location.origin); |
444 | | - // Trigger navigation |
445 | | - await goto(jobsUrl); |
| 449 | + await loadJobsStatus(); |
446 | 450 | } else { |
447 | 451 | console.error(response); |
448 | 452 | // Set an error message on the component |
|
517 | 521 | async function updateNewVersionsCount(count) { |
518 | 522 | newVersionsCount = count; |
519 | 523 | } |
| 524 | +
|
| 525 | + /** @type {{[key: number]: import('$lib/types').JobStatus}} */ |
| 526 | + let statuses = {}; |
| 527 | +
|
| 528 | + /** @type {NodeJS.Timer|undefined} */ |
| 529 | + let statusWatcherTimer; |
| 530 | +
|
| 531 | + async function loadJobsStatus() { |
| 532 | + if (selectedInputDatasetId === undefined || selectedOutputDatasetId === undefined) { |
| 533 | + return; |
| 534 | + } |
| 535 | + if (workflowErrorAlert) { |
| 536 | + workflowErrorAlert.hide(); |
| 537 | + } |
| 538 | + const outputStatusResponse = await fetch( |
| 539 | + `/api/v1/project/${project.id}/dataset/${selectedOutputDatasetId}/status`, |
| 540 | + { |
| 541 | + method: 'GET', |
| 542 | + credentials: 'include' |
| 543 | + } |
| 544 | + ); |
| 545 | + const outputStatus = await outputStatusResponse.json(); |
| 546 | + if (!outputStatusResponse.ok) { |
| 547 | + workflowErrorAlert = displayStandardErrorAlert(outputStatus, 'workflowErrorAlert'); |
| 548 | + return; |
| 549 | + } |
| 550 | + statuses = outputStatus.status; |
| 551 | + const runningOrSubmitted = Object.values(statuses).filter(s => s === 'running' || s === 'submitted'); |
| 552 | + if (statusWatcherTimer) { |
| 553 | + if (runningOrSubmitted.length === 0) { |
| 554 | + clearTimeout(statusWatcherTimer); |
| 555 | + statusWatcherTimer = undefined; |
| 556 | + } |
| 557 | + } else if (runningOrSubmitted.length > 0) { |
| 558 | + statusWatcherTimer = setInterval(loadJobsStatus, updateJobsInterval); |
| 559 | + } |
| 560 | + } |
520 | 561 | </script> |
521 | 562 |
|
522 | | -<div class="d-flex justify-content-between align-items-center mb-4"> |
| 563 | +<div class="row"> |
523 | 564 | <nav aria-label="breadcrumb"> |
524 | 565 | <ol class="breadcrumb"> |
525 | 566 | <li class="breadcrumb-item" aria-current="page"> |
|
538 | 579 | {/if} |
539 | 580 | </ol> |
540 | 581 | </nav> |
541 | | - <div> |
542 | | - <a href="/projects/{project?.id}/workflows/{workflow?.id}/jobs" class="btn btn-light"> |
543 | | - <i class="bi-journal-code" /> List jobs |
544 | | - </a> |
545 | | - <button class="btn btn-light" on:click|preventDefault={handleExportWorkflow}> |
546 | | - <i class="bi-box-arrow-up" /> |
547 | | - </button> |
548 | | - <a id="downloadWorkflowButton" class="d-none">Download workflow link</a> |
549 | | - <button |
550 | | - class="btn btn-light" |
551 | | - data-bs-toggle="modal" |
552 | | - data-bs-target="#editWorkflowModal" |
553 | | - on:click={resetWorkflowUpdateModal} |
554 | | - > |
555 | | - <i class="bi-gear-wide-connected" /> |
556 | | - </button> |
557 | | - <button |
558 | | - class="btn btn-success" |
559 | | - on:click|preventDefault={() => { |
560 | | - if (argumentsWithUnsavedChanges === false) { |
561 | | - runWorkflowModal.toggle(); |
562 | | - } else { |
563 | | - toggleUnsavedChangesModal(); |
564 | | - } |
565 | | - }} |
566 | | - ><i class="bi-play-fill" /> Run workflow |
567 | | - </button> |
| 582 | +</div> |
| 583 | +<div class="row mt-2"> |
| 584 | + <div class="col-lg-9"> |
| 585 | + <div class="row"> |
| 586 | + <div class="col-lg-4 col-md-6"> |
| 587 | + <div class="input-group mb-3"> |
| 588 | + <label for="input-dataset" class="input-group-text">Input dataset</label> |
| 589 | + <select |
| 590 | + class="form-control" |
| 591 | + id="input-dataset" |
| 592 | + bind:value={selectedInputDatasetId} |
| 593 | + on:change={loadJobsStatus} |
| 594 | + > |
| 595 | + <option value={undefined}>Select...</option> |
| 596 | + {#each datasets as dataset} |
| 597 | + <option value={dataset.id}>{dataset.name}</option> |
| 598 | + {/each} |
| 599 | + </select> |
| 600 | + </div> |
| 601 | + </div> |
| 602 | + <div class="col-lg-4 col-md-6"> |
| 603 | + <div class="input-group mb-3"> |
| 604 | + <label for="output-dataset" class="input-group-text">Output dataset</label> |
| 605 | + <select |
| 606 | + class="form-control" |
| 607 | + id="output-dataset" |
| 608 | + bind:value={selectedOutputDatasetId} |
| 609 | + on:change={loadJobsStatus} |
| 610 | + > |
| 611 | + <option value={undefined}>Select...</option> |
| 612 | + {#each datasets as dataset} |
| 613 | + <option value={dataset.id}>{dataset.name}</option> |
| 614 | + {/each} |
| 615 | + </select> |
| 616 | + </div> |
| 617 | + </div> |
| 618 | + <div class="col-lg-4 col-md-12"> |
| 619 | + <button |
| 620 | + class="btn btn-success" |
| 621 | + on:click|preventDefault={() => { |
| 622 | + if (argumentsWithUnsavedChanges === false) { |
| 623 | + runWorkflowModal.toggle(); |
| 624 | + } else { |
| 625 | + toggleUnsavedChangesModal(); |
| 626 | + } |
| 627 | + }} |
| 628 | + ><i class="bi-play-fill" /> Run workflow |
| 629 | + </button> |
| 630 | + </div> |
| 631 | + </div> |
| 632 | + </div> |
| 633 | + |
| 634 | + <div class="col-lg-3"> |
| 635 | + <div class="float-end"> |
| 636 | + <a href="/projects/{project?.id}/workflows/{workflow?.id}/jobs" class="btn btn-light"> |
| 637 | + <i class="bi-journal-code" /> List jobs |
| 638 | + </a> |
| 639 | + <button class="btn btn-light" on:click|preventDefault={handleExportWorkflow}> |
| 640 | + <i class="bi-box-arrow-up" /> |
| 641 | + </button> |
| 642 | + <a id="downloadWorkflowButton" class="d-none">Download workflow link</a> |
| 643 | + <button |
| 644 | + class="btn btn-light" |
| 645 | + data-bs-toggle="modal" |
| 646 | + data-bs-target="#editWorkflowModal" |
| 647 | + on:click={resetWorkflowUpdateModal} |
| 648 | + > |
| 649 | + <i class="bi-gear-wide-connected" /> |
| 650 | + </button> |
| 651 | + </div> |
568 | 652 | </div> |
569 | 653 | </div> |
570 | 654 |
|
|
573 | 657 |
|
574 | 658 | <div id="workflowErrorAlert" /> |
575 | 659 |
|
576 | | - <div class="container mt-4 px-0"> |
| 660 | + <div class="container mt-3 px-0"> |
577 | 661 | <div class="row"> |
578 | 662 | <div class="col-4"> |
579 | 663 | <div class="card"> |
|
616 | 700 | > |
617 | 701 | {workflowTask.task.name} |
618 | 702 |
|
| 703 | + <span class="float-end ps-2"> |
| 704 | + <JobStatusIcon status={statuses[workflowTask.id]} /> |
| 705 | + </span> |
619 | 706 | {#if newVersionsMap[workflowTask.task.id]?.length > 0} |
620 | 707 | <span class="float-end text-warning" title="new version available"> |
621 | 708 | <i class="bi bi-exclamation-triangle" /> |
|
951 | 1038 | id="inputDataset" |
952 | 1039 | class="form-control" |
953 | 1040 | disabled={checkingConfiguration} |
954 | | - bind:value={inputDatasetControl} |
| 1041 | + bind:value={selectedInputDatasetId} |
955 | 1042 | > |
956 | | - <option value="">Select an input dataset</option> |
| 1043 | + <option value={undefined}>Select an input dataset</option> |
957 | 1044 | {#each datasets as dataset} |
958 | 1045 | <option value={dataset.id}>{dataset.name}</option> |
959 | 1046 | {/each} |
|
966 | 1053 | id="outputDataset" |
967 | 1054 | class="form-control" |
968 | 1055 | disabled={checkingConfiguration} |
969 | | - bind:value={outputDatasetControl} |
| 1056 | + bind:value={selectedOutputDatasetId} |
970 | 1057 | > |
971 | | - <option value="">Select an output dataset</option> |
| 1058 | + <option value={undefined}>Select an output dataset</option> |
972 | 1059 | {#each datasets as dataset} |
973 | 1060 | <option value={dataset.id}>{dataset.name}</option> |
974 | 1061 | {/each} |
|
0 commit comments