@jbrowse/plugin-jobs-management 4.2.1 → 5.0.0-beta.1

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.
@@ -1,5 +1,5 @@
1
- import type { NewJob } from '../model.ts';
1
+ import type { JobModel } from '../jobModel.ts';
2
2
  declare const CurrentJobCard: ({ job, }: {
3
- job: NewJob;
4
- }) => import("react/jsx-runtime").JSX.Element;
3
+ job: JobModel;
4
+ }) => import("react").JSX.Element;
5
5
  export default CurrentJobCard;
@@ -1,19 +1,21 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState } from 'react';
3
- import { Box, Button, Card, CardActions, CardContent, LinearProgress, Typography, } from '@mui/material';
3
+ import { StatusProgressBar } from '@jbrowse/core/ui';
4
+ import { Box, Button, Card, CardActions, CardContent, Typography, } from '@mui/material';
4
5
  import { observer } from 'mobx-react';
5
6
  const CurrentJobCard = observer(function CurrentJobCard({ job, }) {
6
7
  const [clicked, setClicked] = useState(false);
7
- return (_jsxs(Card, { variant: "outlined", children: [_jsxs(CardContent, { children: [_jsxs(Typography, { variant: "body1", children: [_jsx("strong", { children: 'Name: ' }), job.name] }), _jsxs(Typography, { variant: "body1", children: [_jsx("strong", { children: 'Message: ' }), job.statusMessage || 'No message provided'] }), _jsx(Box, { sx: {
8
+ const { progressPct } = job;
9
+ return (_jsxs(Card, { variant: "outlined", children: [_jsxs(CardContent, { children: [_jsxs(Typography, { variant: "body1", children: [_jsx("strong", { children: 'Name: ' }), job.name] }), _jsxs(Typography, { variant: "body1", children: [_jsx("strong", { children: 'Message: ' }), job.statusMessage || 'No message provided'] }), _jsxs(Box, { sx: {
8
10
  display: 'flex',
9
11
  alignItems: 'center',
10
- marginTop: 10,
11
- marginBottom: 10,
12
- marginLeft: 10,
13
- }, children: job.progressPct === 0 || job.progressPct === 100 ? (_jsx(Box, { sx: { width: '100%' }, children: _jsx(LinearProgress, { variant: "indeterminate" }) })) : (_jsxs(_Fragment, { children: [_jsx(Box, { sx: { width: '100%' }, children: _jsx(LinearProgress, { variant: "determinate", value: job.progressPct }) }), _jsx(Box, { sx: { m: 1 }, children: _jsx(Typography, { children: `${Math.round(job.progressPct || 0)}%` }) })] })) })] }), _jsx(CardActions, { children: _jsx(Button, { variant: "contained", color: "inherit", disabled: clicked || job.progressPct === 0, onClick: () => {
14
- job.setStatusMessage('Aborted via cancel button');
12
+ marginTop: 1,
13
+ marginBottom: 1,
14
+ marginLeft: 1,
15
+ }, children: [_jsx(Box, { sx: { width: '100%' }, "data-testid": "job-progress", children: _jsx(StatusProgressBar, { fraction: progressPct === undefined ? undefined : progressPct / 100 }) }), progressPct === undefined ? null : (_jsx(Box, { sx: { m: 1 }, children: _jsx(Typography, { children: `${Math.round(progressPct)}%` }) }))] })] }), _jsx(CardActions, { children: _jsx(Button, { variant: "contained", color: "inherit", disabled: clicked, onClick: () => {
16
+ job.update({ statusMessage: 'Cancelling…' });
15
17
  job.cancelCallback();
16
18
  setClicked(true);
17
- }, children: "Cancel" }) })] }));
19
+ }, children: clicked ? 'Cancelling…' : 'Cancel' }) })] }));
18
20
  });
19
21
  export default CurrentJobCard;
@@ -1,5 +1,5 @@
1
- import type { NewJob } from '../model.ts';
1
+ import type { JobModel } from '../jobModel.ts';
2
2
  declare const JobCard: ({ job }: {
3
- job: NewJob;
4
- }) => import("react/jsx-runtime").JSX.Element;
3
+ job: JobModel;
4
+ }) => import("react").JSX.Element;
5
5
  export default JobCard;
@@ -1,5 +1,5 @@
1
1
  import type { JobsListModel } from '../model.ts';
2
2
  declare const JobsListWidget: ({ model, }: {
3
3
  model: JobsListModel;
4
- }) => import("react/jsx-runtime").JSX.Element;
4
+ }) => import("react").JSX.Element;
5
5
  export default JobsListWidget;
@@ -1,21 +1,21 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { makeStyles } from '@jbrowse/core/util/tss-react';
3
- import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
4
- import { Accordion, AccordionSummary, Card, CardContent, Typography, } from '@mui/material';
5
3
  import { observer } from 'mobx-react';
6
- import CurrentJobCard from "./CurrentJobCard.js";
7
- import JobCard from "./JobCard.js";
4
+ import CurrentJobCard from './CurrentJobCard.js';
5
+ import JobCard from './JobCard.js';
6
+ import JobsSection from './JobsSection.js';
8
7
  const useStyles = makeStyles()(theme => ({
9
8
  root: {
10
9
  margin: theme.spacing(1),
11
10
  },
12
- expandIcon: {
13
- color: theme.palette.tertiary.contrastText,
14
- },
15
11
  }));
16
12
  const JobsListWidget = observer(function JobsListWidget({ model, }) {
17
13
  const { classes } = useStyles();
18
- const { jobs, finished, queued, aborted } = model;
19
- return (_jsxs("div", { className: classes.root, children: [_jsxs(Accordion, { defaultExpanded: true, children: [_jsx(AccordionSummary, { expandIcon: _jsx(ExpandMoreIcon, { className: classes.expandIcon }), children: _jsx(Typography, { variant: "h5", children: "Running jobs" }) }), jobs.length ? (jobs.map((job, index) => (_jsx(CurrentJobCard, { job: job }, `${JSON.stringify(job)}-${index}`)))) : (_jsx(Card, { variant: "outlined", children: _jsx(CardContent, { children: _jsx(Typography, { variant: "body1", children: "No running jobs" }) }) }))] }), _jsxs(Accordion, { defaultExpanded: true, children: [_jsx(AccordionSummary, { expandIcon: _jsx(ExpandMoreIcon, { className: classes.expandIcon }), children: _jsx(Typography, { variant: "h5", children: "Queued jobs" }) }), queued.length ? (queued.map((job, index) => (_jsx(JobCard, { job: job }, `${JSON.stringify(job)}-${index}`)))) : (_jsx(Card, { variant: "outlined", children: _jsx(CardContent, { children: _jsx(Typography, { variant: "body1", children: "No queued jobs" }) }) }))] }), _jsxs(Accordion, { defaultExpanded: true, children: [_jsx(AccordionSummary, { expandIcon: _jsx(ExpandMoreIcon, { className: classes.expandIcon }), children: _jsx(Typography, { variant: "h5", children: "Completed jobs" }) }), finished.length ? (finished.map((job, index) => (_jsx(JobCard, { job: job }, `${JSON.stringify(job)}-${index}`)))) : (_jsx(Card, { variant: "outlined", children: _jsx(CardContent, { children: _jsx(Typography, { variant: "body1", children: "No completed jobs" }) }) }))] }), _jsxs(Accordion, { defaultExpanded: true, children: [_jsx(AccordionSummary, { expandIcon: _jsx(ExpandMoreIcon, { className: classes.expandIcon }), children: _jsx(Typography, { variant: "h5", children: "Aborted jobs" }) }), aborted.length ? (aborted.map((job, index) => (_jsx(JobCard, { job: job }, `${JSON.stringify(job)}-${index}`)))) : (_jsx(Card, { variant: "outlined", children: _jsx(CardContent, { children: _jsx(Typography, { variant: "body1", children: "No aborted jobs" }) }) }))] })] }));
14
+ const { jobs } = model;
15
+ return (_jsxs("div", { className: classes.root, children: [_jsx(JobsSection, { title: "Running jobs", jobs: jobs.filter(j => j.state === 'running'), emptyText: "No running jobs", renderCard: job => _jsx(CurrentJobCard, { job: job }, job.name) }), _jsx(JobsSection, { title: "Queued jobs", jobs: jobs.filter(j => j.state === 'queued'), emptyText: "No queued jobs", renderCard: job => _jsx(JobCard, { job: job }, job.name) }), _jsx(JobsSection, { title: "Completed jobs", jobs: jobs.filter(j => j.state === 'finished'), emptyText: "No completed jobs", renderCard: job => _jsx(JobCard, { job: job }, job.name), onClear: () => {
16
+ model.clearJobs('finished');
17
+ } }), _jsx(JobsSection, { title: "Aborted jobs", jobs: jobs.filter(j => j.state === 'aborted'), emptyText: "No aborted jobs", renderCard: job => _jsx(JobCard, { job: job }, job.name), onClear: () => {
18
+ model.clearJobs('aborted');
19
+ } })] }));
20
20
  });
21
21
  export default JobsListWidget;
@@ -0,0 +1,10 @@
1
+ import type { JobModel } from '../jobModel.ts';
2
+ import type { ReactNode } from 'react';
3
+ declare const JobsSection: ({ title, jobs, emptyText, renderCard, onClear, }: {
4
+ title: string;
5
+ jobs: JobModel[];
6
+ emptyText: string;
7
+ renderCard: (job: JobModel) => ReactNode;
8
+ onClear?: () => void;
9
+ }) => import("react").JSX.Element;
10
+ export default JobsSection;
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { makeStyles } from '@jbrowse/core/util/tss-react';
3
+ import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
4
+ import { Accordion, AccordionDetails, AccordionSummary, Button, Card, CardContent, Typography, } from '@mui/material';
5
+ import { observer } from 'mobx-react';
6
+ const useStyles = makeStyles()(theme => ({
7
+ expandIcon: {
8
+ color: theme.palette.tertiary.contrastText,
9
+ },
10
+ summaryTitle: {
11
+ flexGrow: 1,
12
+ },
13
+ clearRow: {
14
+ display: 'flex',
15
+ justifyContent: 'flex-end',
16
+ },
17
+ }));
18
+ const JobsSection = observer(function JobsSection({ title, jobs, emptyText, renderCard, onClear, }) {
19
+ const { classes } = useStyles();
20
+ return (_jsxs(Accordion, { defaultExpanded: true, children: [_jsx(AccordionSummary, { expandIcon: _jsx(ExpandMoreIcon, { className: classes.expandIcon }), children: _jsx(Typography, { variant: "h5", className: classes.summaryTitle, children: title }) }), _jsxs(AccordionDetails, { children: [onClear && jobs.length ? (_jsx("div", { className: classes.clearRow, children: _jsx(Button, { size: "small", onClick: () => {
21
+ onClear();
22
+ }, children: "Clear" }) })) : null, jobs.length ? (jobs.map(job => renderCard(job))) : (_jsx(Card, { variant: "outlined", children: _jsx(CardContent, { children: _jsx(Typography, { variant: "body1", children: emptyText }) }) }))] })] }));
23
+ });
24
+ export default JobsSection;
@@ -1,7 +1,7 @@
1
1
  import { lazy } from 'react';
2
2
  import { ConfigurationSchema } from '@jbrowse/core/configuration';
3
3
  import { WidgetType } from '@jbrowse/core/pluggableElementTypes';
4
- import { stateModelFactory } from "./model.js";
4
+ import { stateModelFactory } from './model.js';
5
5
  export const configSchema = ConfigurationSchema('JobsListWidget', {});
6
6
  export default function JobsListWidgetF(pluginManager) {
7
7
  pluginManager.addWidgetType(() => {
@@ -10,7 +10,7 @@ export default function JobsListWidgetF(pluginManager) {
10
10
  heading: 'Jobs list',
11
11
  configSchema,
12
12
  stateModel: stateModelFactory(pluginManager),
13
- ReactComponent: lazy(() => import("./components/JobsListWidget.js")),
13
+ ReactComponent: lazy(() => import('./components/JobsListWidget.js')),
14
14
  });
15
15
  });
16
16
  }
@@ -1,11 +1,30 @@
1
+ import type { Instance } from '@jbrowse/mobx-state-tree';
2
+ export type JobState = 'running' | 'queued' | 'finished' | 'aborted';
3
+ export interface JobFields {
4
+ state: JobState;
5
+ statusMessage?: string;
6
+ progressPct?: number;
7
+ cancelCallback: () => void;
8
+ }
9
+ /**
10
+ * #stateModel
11
+ * #internal desktop text-indexing queue internals — kept out of the website docs
12
+ * #category widget
13
+ *
14
+ * Created standalone by `JobsListModel` and held in its volatile list rather
15
+ * than attached under it — nothing here belongs in a saved session.
16
+ */
1
17
  export declare const Job: import("@jbrowse/mobx-state-tree").IModelType<{
18
+ /**
19
+ * #property
20
+ */
2
21
  name: import("@jbrowse/mobx-state-tree").ISimpleType<string>;
3
- statusMessage: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
4
- progressPct: import("@jbrowse/mobx-state-tree").ISimpleType<number>;
5
- }, {
6
- cancelCallback(): void;
7
- } & {
8
- setCancelCallback(cancelCallback: () => void): void;
9
- setStatusMessage(message?: string): void;
10
- setProgressPct(pct: number): void;
22
+ }, JobFields & {
23
+ /**
24
+ * #action
25
+ * A key that is present is written, so `{statusMessage: undefined}` clears
26
+ * the message and `{}` touches nothing.
27
+ */
28
+ update(fields: Partial<JobFields>): void;
11
29
  }, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>;
30
+ export type JobModel = Instance<typeof Job>;
@@ -1,21 +1,48 @@
1
1
  import { types } from '@jbrowse/mobx-state-tree';
2
+ /**
3
+ * #stateModel
4
+ * #internal desktop text-indexing queue internals — kept out of the website docs
5
+ * #category widget
6
+ *
7
+ * Created standalone by `JobsListModel` and held in its volatile list rather
8
+ * than attached under it — nothing here belongs in a saved session.
9
+ */
2
10
  export const Job = types
3
11
  .model('Job', {
12
+ /**
13
+ * #property
14
+ */
4
15
  name: types.string,
5
- statusMessage: types.maybe(types.string),
6
- progressPct: types.number,
7
16
  })
8
17
  .volatile(() => ({
9
- cancelCallback() { },
18
+ /**
19
+ * #volatile
20
+ * which of the widget's four sections the job is filed under. One field
21
+ * rather than membership of one of four lists, so it cannot be in two.
22
+ */
23
+ state: 'queued',
24
+ /**
25
+ * #volatile
26
+ */
27
+ statusMessage: undefined,
28
+ /**
29
+ * #volatile
30
+ * undefined when the current phase has no fraction to report, which the
31
+ * card draws as an indeterminate bar
32
+ */
33
+ progressPct: undefined,
34
+ /**
35
+ * #volatile
36
+ */
37
+ cancelCallback: () => { },
10
38
  }))
11
39
  .actions(self => ({
12
- setCancelCallback(cancelCallback) {
13
- self.cancelCallback = cancelCallback;
14
- },
15
- setStatusMessage(message) {
16
- self.statusMessage = message;
17
- },
18
- setProgressPct(pct) {
19
- self.progressPct = pct;
40
+ /**
41
+ * #action
42
+ * A key that is present is written, so `{statusMessage: undefined}` clears
43
+ * the message and `{}` touches nothing.
44
+ */
45
+ update(fields) {
46
+ Object.assign(self, fields);
20
47
  },
21
48
  }));
@@ -1,186 +1,66 @@
1
- import { Job } from './jobModel.ts';
1
+ import type { JobFields, JobState } from './jobModel.ts';
2
2
  import type PluginManager from '@jbrowse/core/PluginManager';
3
- import type { Instance, SnapshotIn } from '@jbrowse/mobx-state-tree';
4
- export interface NewJob extends SnapshotIn<typeof Job> {
5
- cancelCallback(): void;
6
- setStatusMessage(msg?: string): void;
3
+ import type { Instance } from '@jbrowse/mobx-state-tree';
4
+ export interface JobInput extends Partial<JobFields> {
5
+ name: string;
6
+ state: JobState;
7
7
  }
8
+ /**
9
+ * #stateModel JobsListModel
10
+ * #internal desktop text-indexing queue internals — kept out of the website docs
11
+ * #category widget
12
+ */
8
13
  export declare function stateModelFactory(_pluginManager: PluginManager): import("@jbrowse/mobx-state-tree").IModelType<{
14
+ /**
15
+ * #property
16
+ */
9
17
  id: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<string>, [undefined]>;
10
- type: import("@jbrowse/mobx-state-tree").ISimpleType<"JobsListWidget">;
11
- jobs: import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IModelType<{
12
- name: import("@jbrowse/mobx-state-tree").ISimpleType<string>;
13
- statusMessage: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
14
- progressPct: import("@jbrowse/mobx-state-tree").ISimpleType<number>;
15
- }, {
16
- cancelCallback(): void;
17
- } & {
18
- setCancelCallback(cancelCallback: () => void): void;
19
- setStatusMessage(message?: string): void;
20
- setProgressPct(pct: number): void;
21
- }, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>>;
22
- finished: import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IModelType<{
23
- name: import("@jbrowse/mobx-state-tree").ISimpleType<string>;
24
- statusMessage: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
25
- progressPct: import("@jbrowse/mobx-state-tree").ISimpleType<number>;
26
- }, {
27
- cancelCallback(): void;
28
- } & {
29
- setCancelCallback(cancelCallback: () => void): void;
30
- setStatusMessage(message?: string): void;
31
- setProgressPct(pct: number): void;
32
- }, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>>;
33
- queued: import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IModelType<{
34
- name: import("@jbrowse/mobx-state-tree").ISimpleType<string>;
35
- statusMessage: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
36
- progressPct: import("@jbrowse/mobx-state-tree").ISimpleType<number>;
37
- }, {
38
- cancelCallback(): void;
39
- } & {
40
- setCancelCallback(cancelCallback: () => void): void;
41
- setStatusMessage(message?: string): void;
42
- setProgressPct(pct: number): void;
43
- }, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>>;
44
- aborted: import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IModelType<{
45
- name: import("@jbrowse/mobx-state-tree").ISimpleType<string>;
46
- statusMessage: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
47
- progressPct: import("@jbrowse/mobx-state-tree").ISimpleType<number>;
48
- }, {
49
- cancelCallback(): void;
50
- } & {
51
- setCancelCallback(cancelCallback: () => void): void;
52
- setStatusMessage(message?: string): void;
53
- setProgressPct(pct: number): void;
54
- }, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>>;
18
+ /**
19
+ * #property
20
+ */
21
+ type: import("@jbrowse/mobx-state-tree").ILiteralType<"JobsListWidget">;
55
22
  }, {
56
- addJob(job: NewJob): {
57
- name: string;
58
- statusMessage: string | undefined;
59
- progressPct: number;
60
- } & import("@jbrowse/mobx-state-tree/dist/internal").NonEmptyObject & {
61
- cancelCallback(): void;
62
- } & {
63
- setCancelCallback(cancelCallback: () => void): void;
64
- setStatusMessage(message?: string): void;
65
- setProgressPct(pct: number): void;
66
- } & import("@jbrowse/mobx-state-tree").IStateTreeNode<import("@jbrowse/mobx-state-tree").IModelType<{
67
- name: import("@jbrowse/mobx-state-tree").ISimpleType<string>;
68
- statusMessage: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
69
- progressPct: import("@jbrowse/mobx-state-tree").ISimpleType<number>;
70
- }, {
71
- cancelCallback(): void;
72
- } & {
73
- setCancelCallback(cancelCallback: () => void): void;
74
- setStatusMessage(message?: string): void;
75
- setProgressPct(pct: number): void;
76
- }, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>>;
77
- removeJob(jobName: string): ({
78
- name: string;
79
- statusMessage: string | undefined;
80
- progressPct: number;
81
- } & import("@jbrowse/mobx-state-tree/dist/internal").NonEmptyObject & {
82
- cancelCallback(): void;
83
- } & {
84
- setCancelCallback(cancelCallback: () => void): void;
85
- setStatusMessage(message?: string): void;
86
- setProgressPct(pct: number): void;
87
- } & import("@jbrowse/mobx-state-tree").IStateTreeNode<import("@jbrowse/mobx-state-tree").IModelType<{
23
+ /**
24
+ * #volatile
25
+ * Volatile like the `jobsQueue` it mirrors: persisted, a session saved
26
+ * mid-index came back with a permanent "Running" card whose Cancel called
27
+ * the default no-op, and a "Queued" card in no queue.
28
+ */
29
+ jobs: import("mobx").IObservableArray<import("@jbrowse/mobx-state-tree").ModelInstanceTypeProps<{
88
30
  name: import("@jbrowse/mobx-state-tree").ISimpleType<string>;
89
- statusMessage: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
90
- progressPct: import("@jbrowse/mobx-state-tree").ISimpleType<number>;
91
- }, {
92
- cancelCallback(): void;
93
- } & {
94
- setCancelCallback(cancelCallback: () => void): void;
95
- setStatusMessage(message?: string): void;
96
- setProgressPct(pct: number): void;
97
- }, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>>) | undefined;
98
- addFinishedJob(job: NewJob): ({
99
- name: string;
100
- statusMessage: string | undefined;
101
- progressPct: number;
102
- } & import("@jbrowse/mobx-state-tree/dist/internal").NonEmptyObject & {
103
- cancelCallback(): void;
104
- } & {
105
- setCancelCallback(cancelCallback: () => void): void;
106
- setStatusMessage(message?: string): void;
107
- setProgressPct(pct: number): void;
31
+ }> & JobFields & {
32
+ update(fields: Partial<JobFields>): void;
108
33
  } & import("@jbrowse/mobx-state-tree").IStateTreeNode<import("@jbrowse/mobx-state-tree").IModelType<{
109
34
  name: import("@jbrowse/mobx-state-tree").ISimpleType<string>;
110
- statusMessage: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
111
- progressPct: import("@jbrowse/mobx-state-tree").ISimpleType<number>;
112
- }, {
113
- cancelCallback(): void;
114
- } & {
115
- setCancelCallback(cancelCallback: () => void): void;
116
- setStatusMessage(message?: string): void;
117
- setProgressPct(pct: number): void;
118
- }, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>>) | undefined;
119
- addQueuedJob(job: NewJob): ({
120
- name: string;
121
- statusMessage: string | undefined;
122
- progressPct: number;
123
- } & import("@jbrowse/mobx-state-tree/dist/internal").NonEmptyObject & {
124
- cancelCallback(): void;
125
- } & {
126
- setCancelCallback(cancelCallback: () => void): void;
127
- setStatusMessage(message?: string): void;
128
- setProgressPct(pct: number): void;
129
- } & import("@jbrowse/mobx-state-tree").IStateTreeNode<import("@jbrowse/mobx-state-tree").IModelType<{
130
- name: import("@jbrowse/mobx-state-tree").ISimpleType<string>;
131
- statusMessage: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
132
- progressPct: import("@jbrowse/mobx-state-tree").ISimpleType<number>;
133
- }, {
134
- cancelCallback(): void;
135
- } & {
136
- setCancelCallback(cancelCallback: () => void): void;
137
- setStatusMessage(message?: string): void;
138
- setProgressPct(pct: number): void;
139
- }, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>>) | undefined;
140
- addAbortedJob(job: NewJob): ({
141
- name: string;
142
- statusMessage: string | undefined;
143
- progressPct: number;
144
- } & import("@jbrowse/mobx-state-tree/dist/internal").NonEmptyObject & {
145
- cancelCallback(): void;
146
- } & {
147
- setCancelCallback(cancelCallback: () => void): void;
148
- setStatusMessage(message?: string): void;
149
- setProgressPct(pct: number): void;
150
- } & import("@jbrowse/mobx-state-tree").IStateTreeNode<import("@jbrowse/mobx-state-tree").IModelType<{
35
+ }, JobFields & {
36
+ update(fields: Partial<JobFields>): void;
37
+ }, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>>>;
38
+ } & {
39
+ /**
40
+ * #action
41
+ * Files a job under `state`, or moves the one already named that and
42
+ * refreshes whatever fields came with it. A job runs, finishes and is
43
+ * retried under one name, so this is the only way a card is filed.
44
+ */
45
+ addJob({ name, ...fields }: JobInput): import("@jbrowse/mobx-state-tree").ModelInstanceTypeProps<{
151
46
  name: import("@jbrowse/mobx-state-tree").ISimpleType<string>;
152
- statusMessage: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
153
- progressPct: import("@jbrowse/mobx-state-tree").ISimpleType<number>;
154
- }, {
155
- cancelCallback(): void;
156
- } & {
157
- setCancelCallback(cancelCallback: () => void): void;
158
- setStatusMessage(message?: string): void;
159
- setProgressPct(pct: number): void;
160
- }, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>>) | undefined;
161
- removeQueuedJob(jobName: string): ({
162
- name: string;
163
- statusMessage: string | undefined;
164
- progressPct: number;
165
- } & import("@jbrowse/mobx-state-tree/dist/internal").NonEmptyObject & {
166
- cancelCallback(): void;
167
- } & {
168
- setCancelCallback(cancelCallback: () => void): void;
169
- setStatusMessage(message?: string): void;
170
- setProgressPct(pct: number): void;
47
+ }> & JobFields & {
48
+ update(fields: Partial<JobFields>): void;
171
49
  } & import("@jbrowse/mobx-state-tree").IStateTreeNode<import("@jbrowse/mobx-state-tree").IModelType<{
172
50
  name: import("@jbrowse/mobx-state-tree").ISimpleType<string>;
173
- statusMessage: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
174
- progressPct: import("@jbrowse/mobx-state-tree").ISimpleType<number>;
175
- }, {
176
- cancelCallback(): void;
177
- } & {
178
- setCancelCallback(cancelCallback: () => void): void;
179
- setStatusMessage(message?: string): void;
180
- setProgressPct(pct: number): void;
181
- }, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>>) | undefined;
182
- updateJobStatusMessage(jobName: string, message?: string): void;
183
- updateJobProgressPct(jobName: string, pct: number): void;
51
+ }, JobFields & {
52
+ update(fields: Partial<JobFields>): void;
53
+ }, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>>;
54
+ /**
55
+ * #action
56
+ * A phase carrying no fraction clears the old one rather than leaving
57
+ * the bar where the last phase left it.
58
+ */
59
+ updateJobStatus(name: string, statusMessage?: string, pct?: number): void;
60
+ /**
61
+ * #action
62
+ */
63
+ clearJobs(state: JobState): void;
184
64
  }, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>;
185
65
  export type JobsListStateModel = ReturnType<typeof stateModelFactory>;
186
66
  export type JobsListModel = Instance<JobsListStateModel>;
@@ -1,81 +1,65 @@
1
1
  import { ElementId } from '@jbrowse/core/util/types/mst';
2
2
  import { types } from '@jbrowse/mobx-state-tree';
3
- import { Job } from "./jobModel.js";
3
+ import { observable } from 'mobx';
4
+ import { Job } from './jobModel.js';
5
+ /**
6
+ * #stateModel JobsListModel
7
+ * #internal desktop text-indexing queue internals — kept out of the website docs
8
+ * #category widget
9
+ */
4
10
  export function stateModelFactory(_pluginManager) {
5
11
  return types
6
12
  .model('JobsListModel', {
13
+ /**
14
+ * #property
15
+ */
7
16
  id: ElementId,
17
+ /**
18
+ * #property
19
+ */
8
20
  type: types.literal('JobsListWidget'),
9
- jobs: types.array(Job),
10
- finished: types.array(Job),
11
- queued: types.array(Job),
12
- aborted: types.array(Job),
13
21
  })
22
+ .volatile(() => ({
23
+ /**
24
+ * #volatile
25
+ * Volatile like the `jobsQueue` it mirrors: persisted, a session saved
26
+ * mid-index came back with a permanent "Running" card whose Cancel called
27
+ * the default no-op, and a "Queued" card in no queue.
28
+ */
29
+ jobs: observable.array([], { deep: false }),
30
+ }))
14
31
  .actions(self => ({
15
- addJob(job) {
16
- const { cancelCallback, name } = job;
17
- const existing = self.jobs.find(job => job.name === name);
18
- if (existing) {
19
- return existing;
20
- }
21
- const length = self.jobs.push(job);
22
- const addedJob = self.jobs[length - 1];
23
- addedJob.setCancelCallback(cancelCallback);
24
- return addedJob;
25
- },
26
- removeJob(jobName) {
27
- const index = self.jobs.findIndex(job => job.name === jobName);
28
- if (index === -1) {
29
- return undefined;
30
- }
31
- const removed = self.jobs.splice(index, 1);
32
- return removed[0];
33
- },
34
- addFinishedJob(job) {
35
- const existing = self.finished.find(finishedJob => finishedJob.name === job.name);
36
- if (existing) {
37
- return existing;
38
- }
39
- const length = self.finished.push(job);
40
- return self.finished[length - 1];
41
- },
42
- addQueuedJob(job) {
43
- const existing = self.queued.find(queuedJob => queuedJob.name === job.name);
44
- if (existing) {
45
- return existing;
46
- }
47
- const length = self.queued.push(job);
48
- return self.queued[length - 1];
49
- },
50
- addAbortedJob(job) {
51
- const existing = self.aborted.find(abortedJob => abortedJob.name === job.name);
52
- if (existing) {
53
- return existing;
54
- }
55
- const length = self.aborted.push(job);
56
- return self.aborted[length - 1];
57
- },
58
- removeQueuedJob(jobName) {
59
- const index = self.queued.findIndex(job => job.name === jobName);
60
- if (index === -1) {
61
- return undefined;
62
- }
63
- const removed = self.queued.splice(index, 1);
64
- return removed[0];
65
- },
66
- updateJobStatusMessage(jobName, message) {
67
- const job = self.jobs.find(job => job.name === jobName);
32
+ /**
33
+ * #action
34
+ * Files a job under `state`, or moves the one already named that and
35
+ * refreshes whatever fields came with it. A job runs, finishes and is
36
+ * retried under one name, so this is the only way a card is filed.
37
+ */
38
+ addJob({ name, ...fields }) {
39
+ let job = self.jobs.find(j => j.name === name);
68
40
  if (!job) {
69
- throw new Error(`No job found with name ${jobName}`);
41
+ job = Job.create({ name });
42
+ self.jobs.push(job);
70
43
  }
71
- job.setStatusMessage(message);
44
+ job.update(fields);
45
+ return job;
72
46
  },
73
- updateJobProgressPct(jobName, pct) {
74
- const job = self.jobs.find(job => job.name === jobName);
75
- if (!job) {
76
- throw new Error(`No job found with name ${jobName}`);
77
- }
78
- job.setProgressPct(pct);
47
+ /**
48
+ * #action
49
+ * A phase carrying no fraction clears the old one rather than leaving
50
+ * the bar where the last phase left it.
51
+ */
52
+ updateJobStatus(name, statusMessage, pct) {
53
+ // absent if the widget was rebuilt while a status callback was in flight
54
+ self.jobs
55
+ .find(j => j.name === name)
56
+ ?.update({ statusMessage, progressPct: pct });
57
+ },
58
+ /**
59
+ * #action
60
+ */
61
+ clearJobs(state) {
62
+ self.jobs.replace(self.jobs.filter(j => j.state !== state));
79
63
  },
80
64
  }));
81
65
  }
@@ -0,0 +1,8 @@
1
+ import type { JobsListModel } from './JobsListWidget/model.ts';
2
+ import type { SessionWithWidgets } from '@jbrowse/core/util';
3
+ /**
4
+ * Get the singleton JobsList widget for a session, creating it if absent. The
5
+ * widget map is untyped (`Widget`), so the result is cast to the concrete
6
+ * model that this plugin registers.
7
+ */
8
+ export declare function getOrCreateJobsListWidget(session: SessionWithWidgets): JobsListModel;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Get the singleton JobsList widget for a session, creating it if absent. The
3
+ * widget map is untyped (`Widget`), so the result is cast to the concrete
4
+ * model that this plugin registers.
5
+ */
6
+ export function getOrCreateJobsListWidget(session) {
7
+ const existing = session.widgets.get('JobsList');
8
+ return (existing ??
9
+ session.addWidget('JobsListWidget', 'JobsList'));
10
+ }
package/esm/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import Plugin from '@jbrowse/core/Plugin';
2
2
  import type PluginManager from '@jbrowse/core/PluginManager';
3
- export type { JobsListModel } from './JobsListWidget/model.ts';
3
+ export { getOrCreateJobsListWidget } from './getOrCreateJobsListWidget.ts';
4
+ export type { JobInput, JobsListModel } from './JobsListWidget/model.ts';
5
+ export type { JobState } from './JobsListWidget/jobModel.ts';
4
6
  export default class JobsManagementPlugin extends Plugin {
5
7
  name: string;
6
8
  install(pluginManager: PluginManager): void;
package/esm/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import Plugin from '@jbrowse/core/Plugin';
2
2
  import { Indexing } from '@jbrowse/core/ui/Icons';
3
3
  import { isAbstractMenuManager, isSessionModelWithWidgets, } from '@jbrowse/core/util';
4
- import JobsListWidgetF from "./JobsListWidget/index.js";
4
+ import JobsListWidgetF from './JobsListWidget/index.js';
5
+ import { getOrCreateJobsListWidget } from './getOrCreateJobsListWidget.js';
6
+ export { getOrCreateJobsListWidget } from './getOrCreateJobsListWidget.js';
5
7
  export default class JobsManagementPlugin extends Plugin {
6
8
  name = 'JobsManagementPlugin';
7
9
  install(pluginManager) {
@@ -11,18 +13,17 @@ export default class JobsManagementPlugin extends Plugin {
11
13
  if (isAbstractMenuManager(pluginManager.rootModel)) {
12
14
  pluginManager.rootModel.appendToMenu('Tools', {
13
15
  label: 'Jobs list',
14
- icon: Indexing,
16
+ icon: Indexing, // TODO: pick a better icon
17
+ // `unknown`, not SessionWithWidgets: MenuItemClickHandler is
18
+ // `(...args: any[]) => void`, so annotating the parameter asserts a
19
+ // shape rather than checking one, and it made the guard below read as
20
+ // redundant with its own signature. It is not — it is the only check
21
+ // there is. Every JBrowse app happens to pass an AppSession, which has
22
+ // widgets, but that is a fact about the app on the other side of a
23
+ // package boundary and an untyped callback contract.
15
24
  onClick: (session) => {
16
25
  if (isSessionModelWithWidgets(session)) {
17
- const { widgets } = session;
18
- let jobStatusWidget = widgets.get('JobsList');
19
- if (!jobStatusWidget) {
20
- jobStatusWidget = session.addWidget('JobsListWidget', 'JobsList');
21
- session.showWidget(jobStatusWidget);
22
- }
23
- else {
24
- session.showWidget(jobStatusWidget);
25
- }
26
+ session.showWidget(getOrCreateJobsListWidget(session));
26
27
  }
27
28
  },
28
29
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbrowse/plugin-jobs-management",
3
- "version": "4.2.1",
3
+ "version": "5.0.0-beta.1",
4
4
  "type": "module",
5
5
  "description": "JBrowse 2 jobs management",
6
6
  "keywords": [
@@ -21,15 +21,15 @@
21
21
  "esm"
22
22
  ],
23
23
  "dependencies": {
24
- "@jbrowse/mobx-state-tree": "^5.6.0",
25
- "@mui/icons-material": "^7.3.10",
26
- "@mui/material": "^7.3.10",
27
- "mobx": "^6.15.0",
28
- "mobx-react": "^9.2.1",
29
- "@jbrowse/core": "^4.2.1"
24
+ "@jbrowse/mobx-state-tree": "^6.5.1",
25
+ "@mui/icons-material": "^9.3.1",
26
+ "@mui/material": "^9.3.1",
27
+ "mobx": "^7.0.3",
28
+ "mobx-react": "^10.0.2",
29
+ "@jbrowse/core": "^5.0.0-beta.1"
30
30
  },
31
31
  "peerDependencies": {
32
- "react": ">=18.0.0"
32
+ "react": ">=19.0.0"
33
33
  },
34
34
  "publishConfig": {
35
35
  "access": "public"
@@ -39,14 +39,11 @@
39
39
  "build": "pnpm run /^build:/",
40
40
  "test": "cd ../..; jest --passWithNoTests plugins/jobs-management",
41
41
  "prebuild": "pnpm clean",
42
- "build:esm": "tsc -p tsconfig.build.esm.json",
42
+ "build:esm": "node ../../node_modules/typescript7/bin/tsc --build tsconfig.build.esm.json",
43
43
  "clean": "rimraf esm *.tsbuildinfo"
44
44
  },
45
45
  "types": "esm/index.d.ts",
46
46
  "exports": {
47
- ".": {
48
- "types": "./esm/index.d.ts",
49
- "import": "./esm/index.js"
50
- }
47
+ ".": "./esm/index.js"
51
48
  }
52
49
  }