@morscherlab/mint-sdk 1.0.51 → 1.0.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/__tests__/components/JobsStatusTray.test.d.ts +1 -0
  2. package/dist/__tests__/composables/useJobsStatusTray.test.d.ts +1 -0
  3. package/dist/__tests__/utils/jobs.test.d.ts +1 -0
  4. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +1 -1
  5. package/dist/components/JobsStatusTray.vue.d.ts +26 -0
  6. package/dist/components/index.d.ts +1 -0
  7. package/dist/components/index.js +2 -2
  8. package/dist/{components-C7UFkNIp.js → components-Lk_h_rON.js} +1663 -1339
  9. package/dist/components-Lk_h_rON.js.map +1 -0
  10. package/dist/composables/index.d.ts +1 -0
  11. package/dist/composables/index.js +3 -3
  12. package/dist/composables/useJobsStatusTray.d.ts +43 -0
  13. package/dist/composables/usePluginClient.d.ts +4 -1
  14. package/dist/{composables-CpBhNKHf.js → composables-DyfGpYZw.js} +50 -17
  15. package/dist/{composables-CpBhNKHf.js.map → composables-DyfGpYZw.js.map} +1 -1
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +4 -4
  18. package/dist/install.js +1 -1
  19. package/dist/jobs.d.ts +19 -0
  20. package/dist/styles.css +731 -0
  21. package/dist/types/index.d.ts +1 -0
  22. package/dist/types/jobs.d.ts +49 -0
  23. package/dist/{useProtocolTemplates-C8-YlHj1.js → useJobsStatusTray-DAH999_N.js} +250 -2
  24. package/dist/useJobsStatusTray-DAH999_N.js.map +1 -0
  25. package/package.json +1 -1
  26. package/src/__tests__/components/JobsStatusTray.test.ts +141 -0
  27. package/src/__tests__/composables/useJobsStatusTray.test.ts +241 -0
  28. package/src/__tests__/composables/usePluginClient.test.ts +243 -0
  29. package/src/__tests__/utils/jobs.test.ts +95 -0
  30. package/src/components/JobsStatusTray.story.vue +120 -0
  31. package/src/components/JobsStatusTray.vue +385 -0
  32. package/src/components/index.ts +1 -0
  33. package/src/composables/index.ts +8 -0
  34. package/src/composables/useJobsStatusTray.ts +304 -0
  35. package/src/composables/usePluginClient.ts +65 -15
  36. package/src/index.ts +3 -0
  37. package/src/jobs.ts +75 -0
  38. package/src/styles/components/jobs-status-tray.css +420 -0
  39. package/src/styles/index.css +1 -0
  40. package/src/types/index.ts +10 -0
  41. package/src/types/jobs.ts +61 -0
  42. package/dist/components-C7UFkNIp.js.map +0 -1
  43. package/dist/useProtocolTemplates-C8-YlHj1.js.map +0 -1
@@ -1,4 +1,5 @@
1
1
  export type { ContainerDirection, ButtonVariant, ButtonSize, InputType, ModalSize, ModalVariant, AlertType, Toast, TabItem, TabItemInput, OptionPrimitive, SelectOption, SelectOptionInput, RadioOption, RadioOptionInput, FormFieldProps, SidebarBadgeTone, SidebarToolSection, SidebarToolSectionAction, CollapsibleState, TopBarVariant, TopBarSettingsConfig, PillNavOption, PillNavOptionInput, PillNavItem, PillNavItemInput, PageSelectorItem, PageSelectorItemInput, PluginSwitcherPlugin, PluginSwitcherInfo, AccountMenuItem, WellPlateFormat, WellState, WellPlateSelectionMode, WellPlateSize, WellShape, Well, HeatmapColorScale, HeatmapConfig, SlotPosition, WellExtendedData, WellEditData, WellEditField, WellSampleDropData, WellSampleDropContext, WellSampleDropParser, WellLegendItem, PlateCondition, ColumnCondition, RowCondition, Rack, RackSampleDropContext, RackSampleDropMapper, SampleType, PlateMap, PlateMapEditorState, ProtocolStepType, ProtocolStepStatus, ProtocolStep, SampleGroup, GroupItem, FileUploaderMode, SegmentedOption, SegmentedOptionInput, SegmentedControlVariant, SegmentedControlSize, MultiSelectOption, MultiSelectOptionInput, MultiSelectSize, PillVariant, PillColor, PillSize, CalendarSelectionMode, CalendarMarker, CalendarDayContext, SortDirection, SortState, DataFrameColumn, PaginationState, SpinnerSize, SpinnerVariant, DividerSpacing, StatusType, ProgressVariant, ProgressSize, AvatarSize, EmptyStateColor, EmptyStateSize, BreadcrumbItem, BreadcrumbItemInput, TooltipPosition, ConfirmVariant, SettingsTab, SettingsTabInput, SettingsModalLayout, SettingsGroup, SettingsModalSchema, SettingsUserType, SettingsAudience, SettingsAudienceInput, NumberNotation, UnitOption, WizardStep, WizardStepState, AuditEntryType, AuditEntry, BatchItemStatus, BatchItem, BatchSummary, TimePickerFormat, TimeRange, ScheduleView, ScheduleEventStatus, ScheduleEvent, ScheduleBlockedSlot, ScheduleSlotContext, ScheduleEventCreateContext, ScheduleEventUpdateContext, SummarySectionItem, SummarySection, SummaryData, ResourceStatus, ResourceSpec, MoleculeData, ExperimentStatus, DatePreset, ExperimentSortField, ExperimentTypeOption, ExperimentSummary, ExperimentListResponse, ExperimentFilters, FitState, FitResultSummary, StorageCondition, ReagentColumn, Reagent, TreeNodeType, BadgeVariant, TreeNode, } from './components';
2
+ export type { JobListPayload, JobProgress, JobSnapshotPayload, JobState, JobStateInput, JobStatus, JobStreamData, } from './jobs';
2
3
  export type { InstrumentAlert, InstrumentAlertBody, InstrumentAlertLevel, InstrumentState, InstrumentStatus, SampleInfo, SampleInfo as InstrumentSampleInfo, SequenceProgress, } from './instrument';
3
4
  export type { LcmsContainerType, LcmsPolarity, LcmsSequenceItem, LcmsSequenceTableColumn, } from '../lcms';
4
5
  export type { AccessAudience, AccessAudienceInput, AccessPolicy, AccessControlled, PermissionUser, RoleInfo, } from '../permissions';
@@ -0,0 +1,49 @@
1
+ /** Canonical lifecycle values emitted by the MINT Python SDK job contract. */
2
+ export type JobStatus = 'awaiting_input' | 'queued' | 'running' | 'completed' | 'failed' | 'cancelled';
3
+ export interface JobProgress {
4
+ current_file: number;
5
+ total_files: number;
6
+ current_filename: string | null;
7
+ percent: number;
8
+ message: string;
9
+ stage: string;
10
+ }
11
+ /** Normalized job state used by SDK job UI. */
12
+ export interface JobState {
13
+ id: string;
14
+ job_id: string;
15
+ kind: string;
16
+ name: string;
17
+ status: JobStatus;
18
+ progress: JobProgress;
19
+ queue_position: number;
20
+ warnings: string[];
21
+ error: string | null;
22
+ output_path: string | null;
23
+ save_path: string | null;
24
+ session_id: string | null;
25
+ folders: string[];
26
+ folder_path: string | null;
27
+ user_id: string | null;
28
+ owner_user_id: string | null;
29
+ experiment_id: string | null;
30
+ extraction_result_id: string | null;
31
+ metadata: Record<string, unknown>;
32
+ created_at: string;
33
+ started_at: string | null;
34
+ completed_at: string | null;
35
+ can_cancel: boolean;
36
+ can_delete: boolean;
37
+ }
38
+ /** Backward-compatible transport shape accepted from pre-capability backends. */
39
+ export type JobStateInput = Omit<JobState, 'can_cancel' | 'can_delete'> & {
40
+ can_cancel?: boolean | null;
41
+ can_delete?: boolean | null;
42
+ };
43
+ export interface JobListPayload {
44
+ jobs: JobStateInput[];
45
+ }
46
+ export interface JobSnapshotPayload {
47
+ jobs: JobStateInput[];
48
+ }
49
+ export type JobStreamData = JobStateInput | JobSnapshotPayload;
@@ -4488,6 +4488,254 @@ function useProtocolTemplates() {
4488
4488
  };
4489
4489
  }
4490
4490
  //#endregion
4491
- export { readFileAsText as A, useWellPlateEditor as B, useExpansionSet as C, hslToHex as D, hexToHsl as E, parseCSV as F, useAppExperiment as G, DEFAULT_MOBILE_VIEWPORT_QUERY as H, extractSampleNamesFromDesignData as I, candidateMatchesSearch as J, useTheme as K, extractSampleOptionsFromDesignData as L, validateImportFile as M, extractSamplesFromDesignData as N, detectDelimiter as O, useAutoGroup as P, useSortedItems as Q, unwrapExperimentDesignData as R, resolveCurrentExperimentId as S, deriveShade as T, useMobileSupportGate as U, useDoseCalculator as V, APP_EXPERIMENT_KEY as W, useTextSearch as X, normalizeSearchQuery as Y, compareSortValues as Z, useScheduleDrag as _, useReagentSeries as a, currentExperimentFromContext as b, useBioTemplatePresetWorkspace as c, getBioTemplateComponentProps as d, toBioTemplateComponentPropsByComponent as f, useExperimentSave as g, useTemplateCollection as h, generateDilutionSeries as i, useFileImport as j, parseDelimitedText as k, useBioTemplatePackWorkspace as l, useBioTemplateControls as m, DEFAULT_PRESETS as n, useGroupAssignment as o, useBioTemplateComponents as p, useToast as q, DEFAULT_UNITS as r, useRackEditor as s, useProtocolTemplates as t, useBioTemplateWorkspace as u, useExperimentSamples as v, useSampleGroups as w, getInjectedPlatformContext as x, useExperimentData as y, DEFAULT_COLORS as z };
4491
+ //#region src/jobs.ts
4492
+ var ACTIVE_JOB_STATUSES = new Set([
4493
+ "awaiting_input",
4494
+ "queued",
4495
+ "running"
4496
+ ]);
4497
+ var TERMINAL_JOB_STATUSES = new Set([
4498
+ "completed",
4499
+ "failed",
4500
+ "cancelled"
4501
+ ]);
4502
+ /** Resolve action support while retaining one-cycle legacy status inference. */
4503
+ function resolveJobCapabilities(job) {
4504
+ const legacyCanCancel = job.status === "queued" || job.status === "running";
4505
+ const legacyCanDelete = TERMINAL_JOB_STATUSES.has(job.status);
4506
+ return {
4507
+ can_cancel: typeof job.can_cancel === "boolean" ? job.can_cancel : legacyCanCancel,
4508
+ can_delete: typeof job.can_delete === "boolean" ? job.can_delete : legacyCanDelete
4509
+ };
4510
+ }
4511
+ /** Clamp invalid or out-of-range progress values for safe display. */
4512
+ function normalizeJobPercent(value) {
4513
+ if (!Number.isFinite(value)) return 0;
4514
+ return Math.min(100, Math.max(0, value));
4515
+ }
4516
+ /** Create a detached canonical state without changing the transport input. */
4517
+ function normalizeJobState(job) {
4518
+ const capabilities = resolveJobCapabilities(job);
4519
+ return {
4520
+ ...job,
4521
+ progress: {
4522
+ ...job.progress,
4523
+ percent: normalizeJobPercent(job.progress.percent)
4524
+ },
4525
+ warnings: [...job.warnings],
4526
+ folders: [...job.folders],
4527
+ metadata: { ...job.metadata },
4528
+ ...capabilities
4529
+ };
4530
+ }
4531
+ function isActiveJobStatus(status) {
4532
+ return ACTIVE_JOB_STATUSES.has(status);
4533
+ }
4534
+ function isTerminalJobStatus(status) {
4535
+ return TERMINAL_JOB_STATUSES.has(status);
4536
+ }
4537
+ function jobStatusLabel(status) {
4538
+ return {
4539
+ awaiting_input: "Waiting for input",
4540
+ queued: "Queued",
4541
+ running: "Running",
4542
+ completed: "Completed",
4543
+ failed: "Failed",
4544
+ cancelled: "Cancelled"
4545
+ }[status];
4546
+ }
4547
+ //#endregion
4548
+ //#region src/composables/useJobsStatusTray.ts
4549
+ function responseJobs(response) {
4550
+ return "jobs" in response ? response.jobs : response;
4551
+ }
4552
+ function isSnapshotPayload(data) {
4553
+ return "jobs" in data && Array.isArray(data.jobs);
4554
+ }
4555
+ function errorMessage(error, fallback) {
4556
+ return error instanceof Error ? error.message : fallback;
4557
+ }
4558
+ /** Headless state and action controller for the standard SDK jobs tray. */
4559
+ function useJobsStatusTray(options = {}) {
4560
+ const jobs = shallowRef([]);
4561
+ const isOpen = ref(false);
4562
+ const activeTab = ref("active");
4563
+ const isLoading = ref(false);
4564
+ const error = shallowRef(null);
4565
+ const pendingJobIds = ref(/* @__PURE__ */ new Set());
4566
+ const jobRevisions = /* @__PURE__ */ new Map();
4567
+ let stateRevision = 0;
4568
+ let requestGeneration = 0;
4569
+ let active = true;
4570
+ const adapter = computed(() => toValue(options.adapter));
4571
+ const activeJobs = computed(() => jobs.value.filter((job) => isActiveJobStatus(job.status)));
4572
+ const historyJobs = computed(() => jobs.value.filter((job) => !isActiveJobStatus(job.status)));
4573
+ const deletableJobs = computed(() => historyJobs.value.filter((job) => job.can_delete));
4574
+ const featuredJob = computed(() => activeJobs.value[0] ?? historyJobs.value[0] ?? null);
4575
+ function bumpJobRevision(jobId) {
4576
+ jobRevisions.set(jobId, (jobRevisions.get(jobId) ?? 0) + 1);
4577
+ }
4578
+ function replaceJobs(nextJobs) {
4579
+ const normalized = nextJobs.map(normalizeJobState);
4580
+ new Set([...jobs.value.map((job) => job.job_id), ...normalized.map((job) => job.job_id)]).forEach(bumpJobRevision);
4581
+ jobs.value = normalized;
4582
+ stateRevision += 1;
4583
+ }
4584
+ function upsertJob(nextJob) {
4585
+ const normalized = normalizeJobState(nextJob);
4586
+ const existingIndex = jobs.value.findIndex((job) => job.job_id === normalized.job_id);
4587
+ if (existingIndex === -1) jobs.value = [normalized, ...jobs.value];
4588
+ else {
4589
+ const nextJobs = [...jobs.value];
4590
+ nextJobs[existingIndex] = normalized;
4591
+ jobs.value = nextJobs;
4592
+ }
4593
+ bumpJobRevision(normalized.job_id);
4594
+ stateRevision += 1;
4595
+ }
4596
+ function removeJobs(jobIds) {
4597
+ jobs.value = jobs.value.filter((job) => !jobIds.has(job.job_id));
4598
+ jobIds.forEach(bumpJobRevision);
4599
+ stateRevision += 1;
4600
+ }
4601
+ function setPending(jobId, pending) {
4602
+ const next = new Set(pendingJobIds.value);
4603
+ if (pending) next.add(jobId);
4604
+ else next.delete(jobId);
4605
+ pendingJobIds.value = next;
4606
+ }
4607
+ function applyStreamMessage(message) {
4608
+ if (!active) return;
4609
+ if (message.event === "snapshot" && isSnapshotPayload(message.data)) {
4610
+ replaceJobs(message.data.jobs);
4611
+ return;
4612
+ }
4613
+ if ((message.event === "job" || message.event === "terminal") && !isSnapshotPayload(message.data)) upsertJob(message.data);
4614
+ }
4615
+ async function refresh() {
4616
+ const listJobs = adapter.value?.listJobs;
4617
+ if (!listJobs) return false;
4618
+ const generation = ++requestGeneration;
4619
+ const revision = stateRevision;
4620
+ isLoading.value = true;
4621
+ error.value = null;
4622
+ try {
4623
+ const response = await listJobs();
4624
+ if (!active || generation !== requestGeneration || revision !== stateRevision) return false;
4625
+ replaceJobs(responseJobs(response));
4626
+ return true;
4627
+ } catch (cause) {
4628
+ if (active && generation === requestGeneration) error.value = errorMessage(cause, "Failed to load jobs");
4629
+ return false;
4630
+ } finally {
4631
+ if (active && generation === requestGeneration) isLoading.value = false;
4632
+ }
4633
+ }
4634
+ async function cancelJob(job) {
4635
+ const cancel = adapter.value?.cancelJob;
4636
+ if (!job.can_cancel || !cancel || pendingJobIds.value.has(job.job_id)) return false;
4637
+ const revision = jobRevisions.get(job.job_id) ?? 0;
4638
+ setPending(job.job_id, true);
4639
+ error.value = null;
4640
+ try {
4641
+ const updated = await cancel(job);
4642
+ if (!active) return false;
4643
+ if (revision === (jobRevisions.get(job.job_id) ?? 0)) if (updated) upsertJob(updated);
4644
+ else await refresh();
4645
+ return true;
4646
+ } catch (cause) {
4647
+ if (active) error.value = errorMessage(cause, "Failed to cancel job");
4648
+ return false;
4649
+ } finally {
4650
+ if (active) setPending(job.job_id, false);
4651
+ }
4652
+ }
4653
+ async function deleteJob(job) {
4654
+ const deleteJobWithAdapter = adapter.value?.deleteJob;
4655
+ if (!job.can_delete || !deleteJobWithAdapter || pendingJobIds.value.has(job.job_id)) return false;
4656
+ setPending(job.job_id, true);
4657
+ error.value = null;
4658
+ try {
4659
+ const deleted = await deleteJobWithAdapter(job);
4660
+ if (!active) return false;
4661
+ if (deleted === false) await refresh();
4662
+ else removeJobs(new Set([job.job_id]));
4663
+ return deleted !== false;
4664
+ } catch (cause) {
4665
+ if (active) error.value = errorMessage(cause, "Failed to delete job");
4666
+ return false;
4667
+ } finally {
4668
+ if (active) setPending(job.job_id, false);
4669
+ }
4670
+ }
4671
+ async function clearFinished() {
4672
+ const targets = deletableJobs.value;
4673
+ if (targets.length === 0) return false;
4674
+ error.value = null;
4675
+ try {
4676
+ const currentAdapter = adapter.value;
4677
+ if (currentAdapter?.clearFinishedJobs) {
4678
+ const deleted = await currentAdapter.clearFinishedJobs(targets);
4679
+ if (!active) return false;
4680
+ if (typeof deleted === "number" && deleted !== targets.length) {
4681
+ await refresh();
4682
+ return false;
4683
+ }
4684
+ removeJobs(new Set(targets.map((job) => job.job_id)));
4685
+ return true;
4686
+ }
4687
+ if (!currentAdapter?.deleteJob) return false;
4688
+ return (await Promise.all(targets.map((job) => deleteJob(job)))).every(Boolean);
4689
+ } catch (cause) {
4690
+ if (active) error.value = errorMessage(cause, "Failed to clear jobs");
4691
+ return false;
4692
+ }
4693
+ }
4694
+ if (options.jobs !== void 0) watch(() => toValue(options.jobs), (value) => {
4695
+ if (value) replaceJobs(value);
4696
+ }, {
4697
+ immediate: true,
4698
+ deep: true
4699
+ });
4700
+ if (options.eventStream !== void 0) watch(() => toValue(options.eventStream)?.lastMessage.value ?? null, (message) => {
4701
+ if (message) applyStreamMessage(message);
4702
+ }, { immediate: true });
4703
+ onMounted(() => {
4704
+ if (options.loadOnMount !== false) refresh();
4705
+ });
4706
+ onUnmounted(() => {
4707
+ active = false;
4708
+ requestGeneration += 1;
4709
+ });
4710
+ return {
4711
+ jobs,
4712
+ activeJobs,
4713
+ historyJobs,
4714
+ deletableJobs,
4715
+ featuredJob,
4716
+ isOpen,
4717
+ activeTab,
4718
+ isLoading,
4719
+ error,
4720
+ pendingJobIds,
4721
+ open: () => {
4722
+ isOpen.value = true;
4723
+ },
4724
+ close: () => {
4725
+ isOpen.value = false;
4726
+ },
4727
+ toggle: () => {
4728
+ isOpen.value = !isOpen.value;
4729
+ },
4730
+ refresh,
4731
+ applyStreamMessage,
4732
+ cancelJob,
4733
+ deleteJob,
4734
+ clearFinished,
4735
+ isPending: (job) => pendingJobIds.value.has(job.job_id)
4736
+ };
4737
+ }
4738
+ //#endregion
4739
+ export { useTheme as $, useExpansionSet as A, extractSamplesFromDesignData as B, useExperimentSave as C, currentExperimentFromContext as D, useExperimentData as E, detectDelimiter as F, unwrapExperimentDesignData as G, parseCSV as H, parseDelimitedText as I, useDoseCalculator as J, DEFAULT_COLORS as K, readFileAsText as L, deriveShade as M, hexToHsl as N, getInjectedPlatformContext as O, hslToHex as P, useAppExperiment as Q, useFileImport as R, useTemplateCollection as S, useExperimentSamples as T, extractSampleNamesFromDesignData as U, useAutoGroup as V, extractSampleOptionsFromDesignData as W, useMobileSupportGate as X, DEFAULT_MOBILE_VIEWPORT_QUERY as Y, APP_EXPERIMENT_KEY as Z, useBioTemplateWorkspace as _, normalizeJobPercent as a, useSortedItems as at, useBioTemplateComponents as b, useProtocolTemplates as c, generateDilutionSeries as d, useToast as et, useReagentSeries as f, useBioTemplatePackWorkspace as g, useBioTemplatePresetWorkspace as h, jobStatusLabel as i, compareSortValues as it, useSampleGroups as j, resolveCurrentExperimentId as k, DEFAULT_PRESETS as l, useRackEditor as m, isActiveJobStatus as n, normalizeSearchQuery as nt, normalizeJobState as o, useGroupAssignment as p, useWellPlateEditor as q, isTerminalJobStatus as r, useTextSearch as rt, resolveJobCapabilities as s, useJobsStatusTray as t, candidateMatchesSearch as tt, DEFAULT_UNITS as u, getBioTemplateComponentProps as v, useScheduleDrag as w, useBioTemplateControls as x, toBioTemplateComponentPropsByComponent as y, validateImportFile as z };
4492
4740
 
4493
- //# sourceMappingURL=useProtocolTemplates-C8-YlHj1.js.map
4741
+ //# sourceMappingURL=useJobsStatusTray-DAH999_N.js.map