@morscherlab/mint-sdk 1.1.24 → 1.2.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.
- package/README.md +9 -7
- package/dist/{ExperimentSelectorModal-B6P2msT4.js → ExperimentSelectorModal-DyPAUCLw.js} +3 -3
- package/dist/{ExperimentSelectorModal-B6P2msT4.js.map → ExperimentSelectorModal-DyPAUCLw.js.map} +1 -1
- package/dist/{SettingsModal-DoS2fzyz.js → SettingsModal-BgKY93zh.js} +5 -4
- package/dist/SettingsModal-BgKY93zh.js.map +1 -0
- package/dist/__tests__/composables/useEventStream.test.d.ts +1 -0
- package/dist/components/AutoGroupModal.vue.d.ts +1 -1
- package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +1 -1
- package/dist/components/JobsStatusTray.vue.d.ts +29 -10
- package/dist/components/SampleLegend.vue.d.ts +1 -1
- package/dist/components/SampleSelector.vue.d.ts +1 -1
- package/dist/components/SettingsModal.vue.d.ts +1 -1
- package/dist/components/SmartGroupFieldRecipe.vue.d.ts +1 -1
- package/dist/components/index.js +4 -4
- package/dist/{components-CW3hE1T3.js → components-DkHddRMl.js} +39 -40
- package/dist/components-DkHddRMl.js.map +1 -0
- package/dist/composables/index.d.ts +2 -3
- package/dist/composables/index.js +8 -8
- package/dist/composables/useEventStream.d.ts +43 -0
- package/dist/composables/useGeneratedAnalysis.d.ts +1 -3
- package/dist/composables/useJobsStatusTray.d.ts +1 -25
- package/dist/composables/usePluginClient.d.ts +3 -18
- package/dist/{composables-CkXDe7nm.js → composables-C_i90sGS.js} +32 -308
- package/dist/composables-C_i90sGS.js.map +1 -0
- package/dist/{experiment-Y9Iz7KYi.js → experiment-Cq145oEW.js} +2 -2
- package/dist/{experiment-Y9Iz7KYi.js.map → experiment-Cq145oEW.js.map} +1 -1
- package/dist/index.js +12 -12
- package/dist/install.js +4 -4
- package/dist/stores/auth.d.ts +0 -6
- package/dist/stores/index.js +2 -2
- package/dist/templates/index.js +2 -2
- package/dist/{templates-CovvRZ-9.js → templates-DsP51i-S.js} +2 -2
- package/dist/{templates-CovvRZ-9.js.map → templates-DsP51i-S.js.map} +1 -1
- package/dist/types/auth.d.ts +0 -1
- package/dist/{useApi-CDBq6z23.js → useApi-J6zFkHmn.js} +1 -2
- package/dist/useApi-J6zFkHmn.js.map +1 -0
- package/dist/{useControlSchema-BhoJzWzx.js → useControlSchema-BDx2Olha.js} +239 -103
- package/dist/useControlSchema-BDx2Olha.js.map +1 -0
- package/dist/{useExperimentSelector-D0pjj9WH.js → useExperimentSelector-CV4iNfMS.js} +2 -2
- package/dist/{useExperimentSelector-D0pjj9WH.js.map → useExperimentSelector-CV4iNfMS.js.map} +1 -1
- package/dist/{useFormBuilder-DiEcDis2.js → useFormBuilder-BLxG3BOX.js} +2 -2
- package/dist/{useFormBuilder-DiEcDis2.js.map → useFormBuilder-BLxG3BOX.js.map} +1 -1
- package/dist/{useProtocolTemplates-DHwSveE6.js → useProtocolTemplates-CS_SQl6U.js} +22 -113
- package/dist/useProtocolTemplates-CS_SQl6U.js.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/components/GeneratedPluginView.test.ts +0 -5
- package/src/__tests__/components/JobsStatusTray.test.ts +43 -19
- package/src/__tests__/composables/useAuth.test.ts +23 -0
- package/src/__tests__/composables/useEventStream.test.ts +140 -0
- package/src/__tests__/composables/useGeneratedAnalysis.test.ts +103 -0
- package/src/__tests__/composables/useJobsStatusTray.test.ts +25 -161
- package/src/__tests__/composables/usePluginClient.test.ts +8 -13
- package/src/__tests__/docs/frontendDocsCatalog.test.ts +22 -1
- package/src/components/JobsStatusTray.story.vue +16 -10
- package/src/components/JobsStatusTray.vue +22 -28
- package/src/components/SettingsModal.schema.ts +3 -0
- package/src/composables/index.ts +0 -7
- package/src/composables/useAuth.ts +0 -3
- package/src/composables/useEventStream.ts +314 -0
- package/src/composables/useGeneratedAnalysis.ts +40 -109
- package/src/composables/useJobsStatusTray.ts +17 -184
- package/src/composables/usePluginClient.ts +45 -249
- package/src/stores/auth.ts +0 -1
- package/src/types/auth.ts +0 -1
- package/dist/SettingsModal-DoS2fzyz.js.map +0 -1
- package/dist/__tests__/composables/usePluginConfig.test.d.ts +0 -15
- package/dist/components-CW3hE1T3.js.map +0 -1
- package/dist/composables/usePluginConfig.d.ts +0 -25
- package/dist/composables-CkXDe7nm.js.map +0 -1
- package/dist/useApi-CDBq6z23.js.map +0 -1
- package/dist/useControlSchema-BhoJzWzx.js.map +0 -1
- package/dist/useProtocolTemplates-DHwSveE6.js.map +0 -1
- package/src/__tests__/composables/usePluginConfig.test.ts +0 -497
- package/src/composables/usePluginConfig.ts +0 -250
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { mount } from '@vue/test-utils'
|
|
2
|
-
import { defineComponent, nextTick,
|
|
2
|
+
import { defineComponent, nextTick, shallowRef } from 'vue'
|
|
3
3
|
import { describe, expect, it, vi } from 'vitest'
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
combinePluginJobSources,
|
|
7
|
-
useJobsStatusTray,
|
|
8
7
|
usePluginJobCenter,
|
|
9
|
-
type
|
|
8
|
+
type UsePluginJobCenterOptions,
|
|
10
9
|
} from '../../composables/useJobsStatusTray'
|
|
11
|
-
import type {
|
|
10
|
+
import type { JobStateInput, JobStatus } from '../../types/jobs'
|
|
12
11
|
|
|
13
12
|
function job(status: JobStatus, id: string = status): JobStateInput {
|
|
14
13
|
return {
|
|
@@ -44,29 +43,18 @@ function job(status: JobStatus, id: string = status): JobStateInput {
|
|
|
44
43
|
}
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
function mountComposable(options:
|
|
48
|
-
let tray!: ReturnType<typeof
|
|
46
|
+
function mountComposable(options: UsePluginJobCenterOptions) {
|
|
47
|
+
let tray!: ReturnType<typeof usePluginJobCenter>
|
|
49
48
|
const wrapper = mount(defineComponent({
|
|
50
49
|
setup() {
|
|
51
|
-
tray =
|
|
50
|
+
tray = usePluginJobCenter(options)
|
|
52
51
|
return () => null
|
|
53
52
|
},
|
|
54
53
|
}))
|
|
55
54
|
return { tray, wrapper }
|
|
56
55
|
}
|
|
57
56
|
|
|
58
|
-
|
|
59
|
-
promise: Promise<T>
|
|
60
|
-
resolve: (value: T) => void
|
|
61
|
-
} {
|
|
62
|
-
let resolve!: (value: T) => void
|
|
63
|
-
const promise = new Promise<T>((done) => {
|
|
64
|
-
resolve = done
|
|
65
|
-
})
|
|
66
|
-
return { promise, resolve }
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
describe('useJobsStatusTray', () => {
|
|
57
|
+
describe('usePluginJobCenter', () => {
|
|
70
58
|
it('combines live source jobs and fans out or routes source actions', async () => {
|
|
71
59
|
const firstJobs = shallowRef([job('running', 'first')])
|
|
72
60
|
const secondJobs = shallowRef([job('completed', 'second')])
|
|
@@ -242,37 +230,20 @@ describe('useJobsStatusTray', () => {
|
|
|
242
230
|
|
|
243
231
|
it('keeps awaiting input jobs in the active group', () => {
|
|
244
232
|
const { tray } = mountComposable({
|
|
245
|
-
jobs: [job('awaiting_input')],
|
|
233
|
+
source: { jobs: [job('awaiting_input')] },
|
|
246
234
|
loadOnMount: false,
|
|
247
235
|
})
|
|
248
236
|
|
|
249
237
|
expect(tray.activeJobs.value.map(item => item.job_id)).toEqual(['awaiting_input'])
|
|
250
238
|
})
|
|
251
239
|
|
|
252
|
-
it('
|
|
253
|
-
const { tray } = mountComposable({ jobs: [job('running', 'old')], loadOnMount: false })
|
|
254
|
-
|
|
255
|
-
tray.applyStreamMessage({
|
|
256
|
-
event: 'snapshot',
|
|
257
|
-
data: { jobs: [job('queued', 'new')] },
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
expect(tray.jobs.value.map(item => item.job_id)).toEqual(['new'])
|
|
261
|
-
})
|
|
262
|
-
|
|
263
|
-
it('moves an SSE terminal update into history', () => {
|
|
264
|
-
const { tray } = mountComposable({ jobs: [job('running', 'fit')], loadOnMount: false })
|
|
265
|
-
|
|
266
|
-
tray.applyStreamMessage({ event: 'terminal', data: job('completed', 'fit') })
|
|
267
|
-
|
|
268
|
-
expect(tray.historyJobs.value.map(item => item.job_id)).toEqual(['fit'])
|
|
269
|
-
})
|
|
270
|
-
|
|
271
|
-
it('does not call a cancel adapter when the capability is disabled', async () => {
|
|
240
|
+
it('does not call a source cancel action when the capability is disabled', async () => {
|
|
272
241
|
const cancelJob = vi.fn()
|
|
273
242
|
const { tray } = mountComposable({
|
|
274
|
-
|
|
275
|
-
|
|
243
|
+
source: {
|
|
244
|
+
jobs: [{ ...job('running'), can_cancel: false }],
|
|
245
|
+
cancelJob,
|
|
246
|
+
},
|
|
276
247
|
loadOnMount: false,
|
|
277
248
|
})
|
|
278
249
|
|
|
@@ -281,139 +252,32 @@ describe('useJobsStatusTray', () => {
|
|
|
281
252
|
expect(cancelJob).not.toHaveBeenCalled()
|
|
282
253
|
})
|
|
283
254
|
|
|
284
|
-
it('
|
|
285
|
-
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const { tray } = mountComposable({
|
|
290
|
-
adapter: { listJobs },
|
|
291
|
-
loadOnMount: false,
|
|
255
|
+
it('refreshes through the source without mirroring its jobs', async () => {
|
|
256
|
+
const sourceJobs = shallowRef([job('running', 'old')])
|
|
257
|
+
const refreshJobs = vi.fn(async () => {
|
|
258
|
+
sourceJobs.value = [job('queued', 'new')]
|
|
259
|
+
return sourceJobs.value
|
|
292
260
|
})
|
|
293
|
-
|
|
294
|
-
const refresh = tray.refresh()
|
|
295
|
-
tray.applyStreamMessage({ event: 'job', data: job('running', 'live') })
|
|
296
|
-
resolveList([job('queued', 'stale')])
|
|
297
|
-
await refresh
|
|
298
|
-
await nextTick()
|
|
299
|
-
|
|
300
|
-
expect(tray.jobs.value.map(item => item.job_id)).toEqual(['live'])
|
|
301
|
-
})
|
|
302
|
-
|
|
303
|
-
it('passes only explicitly deletable jobs to the bulk clear adapter', async () => {
|
|
304
|
-
const clearFinishedJobs = vi.fn(async (_jobs: readonly JobState[]) => undefined)
|
|
305
261
|
const { tray } = mountComposable({
|
|
306
|
-
jobs:
|
|
307
|
-
job('completed', 'legacy-deletable'),
|
|
308
|
-
{ ...job('failed', 'retained'), can_delete: false },
|
|
309
|
-
],
|
|
310
|
-
adapter: { clearFinishedJobs },
|
|
262
|
+
source: { jobs: sourceJobs, refreshJobs },
|
|
311
263
|
loadOnMount: false,
|
|
312
264
|
})
|
|
313
265
|
|
|
314
|
-
await tray.
|
|
266
|
+
await tray.refresh()
|
|
267
|
+
await nextTick()
|
|
315
268
|
|
|
316
|
-
expect(
|
|
317
|
-
|
|
269
|
+
expect([refreshJobs.mock.calls.length, tray.jobs.value.map(item => item.job_id)]).toEqual([
|
|
270
|
+
1,
|
|
271
|
+
['new'],
|
|
318
272
|
])
|
|
319
273
|
})
|
|
320
274
|
|
|
321
275
|
it('does not include active deletable jobs in bulk clear targets', () => {
|
|
322
276
|
const { tray } = mountComposable({
|
|
323
|
-
jobs: [{ ...job('awaiting_input'), can_delete: true }],
|
|
277
|
+
source: { jobs: [{ ...job('awaiting_input'), can_delete: true }] },
|
|
324
278
|
loadOnMount: false,
|
|
325
279
|
})
|
|
326
280
|
|
|
327
281
|
expect(tray.deletableJobs.value).toEqual([])
|
|
328
282
|
})
|
|
329
|
-
|
|
330
|
-
it('does not let a late cancel response overwrite a newer SSE state', async () => {
|
|
331
|
-
const pending = deferred<JobStateInput>()
|
|
332
|
-
const { tray } = mountComposable({
|
|
333
|
-
jobs: [job('running', 'fit')],
|
|
334
|
-
adapter: { cancelJob: async () => pending.promise },
|
|
335
|
-
loadOnMount: false,
|
|
336
|
-
})
|
|
337
|
-
|
|
338
|
-
const cancellation = tray.cancelJob(tray.jobs.value[0]!)
|
|
339
|
-
tray.applyStreamMessage({ event: 'terminal', data: job('completed', 'fit') })
|
|
340
|
-
pending.resolve(job('cancelled', 'fit'))
|
|
341
|
-
await cancellation
|
|
342
|
-
|
|
343
|
-
expect(tray.jobs.value[0]!.status).toBe('completed')
|
|
344
|
-
})
|
|
345
|
-
|
|
346
|
-
it('applies a cancel response after an unrelated job update', async () => {
|
|
347
|
-
const pending = deferred<JobStateInput>()
|
|
348
|
-
const { tray } = mountComposable({
|
|
349
|
-
jobs: [job('running', 'fit'), job('running', 'export')],
|
|
350
|
-
adapter: { cancelJob: async () => pending.promise },
|
|
351
|
-
loadOnMount: false,
|
|
352
|
-
})
|
|
353
|
-
|
|
354
|
-
const cancellation = tray.cancelJob(tray.jobs.value[0]!)
|
|
355
|
-
tray.applyStreamMessage({ event: 'job', data: job('running', 'export') })
|
|
356
|
-
pending.resolve(job('cancelled', 'fit'))
|
|
357
|
-
await cancellation
|
|
358
|
-
|
|
359
|
-
expect(tray.jobs.value.find(item => item.job_id === 'fit')?.status).toBe('cancelled')
|
|
360
|
-
})
|
|
361
|
-
|
|
362
|
-
it('refreshes instead of removing every target after a partial bulk clear', async () => {
|
|
363
|
-
const listJobs = vi.fn(async () => [job('completed', 'retained')])
|
|
364
|
-
const clearFinishedJobs = vi.fn(async () => 1)
|
|
365
|
-
const { tray } = mountComposable({
|
|
366
|
-
jobs: [job('completed', 'deleted'), job('completed', 'retained')],
|
|
367
|
-
adapter: { listJobs, clearFinishedJobs },
|
|
368
|
-
loadOnMount: false,
|
|
369
|
-
})
|
|
370
|
-
|
|
371
|
-
const cleared = await tray.clearFinished()
|
|
372
|
-
|
|
373
|
-
expect([
|
|
374
|
-
cleared,
|
|
375
|
-
listJobs.mock.calls.length,
|
|
376
|
-
tray.jobs.value.map(item => item.job_id),
|
|
377
|
-
]).toEqual([false, 1, ['retained']])
|
|
378
|
-
})
|
|
379
|
-
|
|
380
|
-
it('uses a replacement reactive adapter for later actions', async () => {
|
|
381
|
-
const first = vi.fn(async () => undefined)
|
|
382
|
-
const second = vi.fn(async () => undefined)
|
|
383
|
-
const adapter = shallowRef({ cancelJob: first })
|
|
384
|
-
const { tray } = mountComposable({
|
|
385
|
-
jobs: [job('running')],
|
|
386
|
-
adapter,
|
|
387
|
-
loadOnMount: false,
|
|
388
|
-
})
|
|
389
|
-
adapter.value = { cancelJob: second }
|
|
390
|
-
|
|
391
|
-
await tray.cancelJob(tray.jobs.value[0]!)
|
|
392
|
-
|
|
393
|
-
expect([first.mock.calls.length, second.mock.calls.length]).toEqual([0, 1])
|
|
394
|
-
})
|
|
395
|
-
|
|
396
|
-
it('applies the latest message from a replacement stream immediately', async () => {
|
|
397
|
-
const eventStream = shallowRef({
|
|
398
|
-
isConnected: ref(true),
|
|
399
|
-
isConnecting: ref(false),
|
|
400
|
-
error: ref<string | null>(null),
|
|
401
|
-
lastMessage: shallowRef({
|
|
402
|
-
event: 'snapshot',
|
|
403
|
-
data: { jobs: [job('queued', 'replacement')] },
|
|
404
|
-
raw: '',
|
|
405
|
-
}),
|
|
406
|
-
lastEventId: ref<string | null>(null),
|
|
407
|
-
start: vi.fn(),
|
|
408
|
-
stop: vi.fn(),
|
|
409
|
-
})
|
|
410
|
-
|
|
411
|
-
const { tray } = mountComposable({
|
|
412
|
-
eventStream,
|
|
413
|
-
loadOnMount: false,
|
|
414
|
-
})
|
|
415
|
-
await nextTick()
|
|
416
|
-
|
|
417
|
-
expect(tray.jobs.value.map(item => item.job_id)).toEqual(['replacement'])
|
|
418
|
-
})
|
|
419
283
|
})
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
uploadPluginEndpoint,
|
|
15
15
|
useCurrentExperiment,
|
|
16
16
|
usePluginEventStream,
|
|
17
|
-
usePluginClient,
|
|
18
17
|
usePluginSettings,
|
|
19
18
|
type PluginContract,
|
|
20
19
|
} from '../../composables/usePluginClient'
|
|
@@ -80,7 +79,13 @@ function pendingEventStreamResponse(): {
|
|
|
80
79
|
})
|
|
81
80
|
return {
|
|
82
81
|
response: new Response(body, { status: 200 }),
|
|
83
|
-
close: () =>
|
|
82
|
+
close: () => {
|
|
83
|
+
try {
|
|
84
|
+
streamController.close()
|
|
85
|
+
} catch {
|
|
86
|
+
// stop() may already have cancelled the reader.
|
|
87
|
+
}
|
|
88
|
+
},
|
|
84
89
|
}
|
|
85
90
|
}
|
|
86
91
|
|
|
@@ -1396,11 +1401,7 @@ describe('usePluginClient', () => {
|
|
|
1396
1401
|
|
|
1397
1402
|
try {
|
|
1398
1403
|
stream.start()
|
|
1399
|
-
await
|
|
1400
|
-
await Promise.resolve()
|
|
1401
|
-
await Promise.resolve()
|
|
1402
|
-
|
|
1403
|
-
expect(onOpen).toHaveBeenCalledOnce()
|
|
1404
|
+
await vi.waitFor(() => expect(onOpen).toHaveBeenCalledOnce())
|
|
1404
1405
|
} finally {
|
|
1405
1406
|
stopWatching()
|
|
1406
1407
|
stream.stop()
|
|
@@ -1684,12 +1685,6 @@ describe('usePluginClient', () => {
|
|
|
1684
1685
|
expect(requestConfigs).toHaveLength(0)
|
|
1685
1686
|
})
|
|
1686
1687
|
|
|
1687
|
-
it('returns the provided client identity from usePluginClient', () => {
|
|
1688
|
-
const client = createPluginClient(contract, { endpoints: {} })
|
|
1689
|
-
|
|
1690
|
-
expect(usePluginClient(client)).toBe(client)
|
|
1691
|
-
})
|
|
1692
|
-
|
|
1693
1688
|
it('loads the current experiment from URL conventions', async () => {
|
|
1694
1689
|
window.history.replaceState({}, '', '/analysis?experimentId=42')
|
|
1695
1690
|
const current = useCurrentExperiment<{ called: string }>({ immediate: false })
|
|
@@ -271,6 +271,18 @@ describe('frontend docs catalog coverage', () => {
|
|
|
271
271
|
})
|
|
272
272
|
})
|
|
273
273
|
|
|
274
|
+
it('documents only the source-driven JobsStatusTray props', () => {
|
|
275
|
+
const manifest = extractFrontendManifest()
|
|
276
|
+
const jobsStatusTray = manifest.categories.components.find(item => item.name === 'JobsStatusTray')
|
|
277
|
+
|
|
278
|
+
expect(jobsStatusTray?.props?.map(prop => prop.name).sort()).toEqual([
|
|
279
|
+
'loadOnMount',
|
|
280
|
+
'source',
|
|
281
|
+
'teleportTo',
|
|
282
|
+
'title',
|
|
283
|
+
])
|
|
284
|
+
})
|
|
285
|
+
|
|
274
286
|
it('re-exports every public component from the root SDK entrypoint', () => {
|
|
275
287
|
const publicComponents = parseComponentExports()
|
|
276
288
|
const rootComponents = parseRootComponentExports()
|
|
@@ -295,6 +307,7 @@ describe('frontend docs catalog coverage', () => {
|
|
|
295
307
|
const rootIndex = readFileSync(join(SDK_ROOT, 'src', 'index.ts'), 'utf8')
|
|
296
308
|
const componentsIndex = readFileSync(join(SDK_ROOT, 'src', 'components', 'index.ts'), 'utf8')
|
|
297
309
|
const composablesIndex = readFileSync(join(SDK_ROOT, 'src', 'composables', 'index.ts'), 'utf8')
|
|
310
|
+
const composableExports = composablesIndex.replace(/from\s+['"][^'"]+['"]/g, '')
|
|
298
311
|
|
|
299
312
|
const retiredNames = [
|
|
300
313
|
'ToastNotification',
|
|
@@ -306,12 +319,20 @@ describe('frontend docs catalog coverage', () => {
|
|
|
306
319
|
'AppPillNav',
|
|
307
320
|
'usePluginApi',
|
|
308
321
|
'UsePluginApiOptions',
|
|
322
|
+
'useJobsStatusTray',
|
|
323
|
+
'UseJobsStatusTrayOptions',
|
|
324
|
+
'UseJobsStatusTrayReturn',
|
|
325
|
+
'JobsStatusTrayAdapter',
|
|
326
|
+
'JobStreamMessage',
|
|
327
|
+
'usePluginConfig',
|
|
328
|
+
'UsePluginConfigReturn',
|
|
329
|
+
'usePluginClient',
|
|
309
330
|
]
|
|
310
331
|
|
|
311
332
|
for (const name of retiredNames) {
|
|
312
333
|
expect(rootIndex).not.toMatch(new RegExp(`\\b${name}\\b`))
|
|
313
334
|
expect(componentsIndex).not.toMatch(new RegExp(`\\b${name}\\b`))
|
|
314
|
-
expect(
|
|
335
|
+
expect(composableExports).not.toMatch(new RegExp(`\\b${name}\\b`))
|
|
315
336
|
}
|
|
316
337
|
})
|
|
317
338
|
|
|
@@ -54,9 +54,11 @@ const explicitActionJobs = [
|
|
|
54
54
|
job('awaiting_input', 'J-201', { can_cancel: true }),
|
|
55
55
|
job('completed', 'J-200', { can_delete: false }),
|
|
56
56
|
]
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
const cancelJob = async () => undefined
|
|
58
|
+
const deleteJob = async () => true
|
|
59
|
+
|
|
60
|
+
function openJob(jobId: string) {
|
|
61
|
+
console.info('open', jobId)
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
function initState() {
|
|
@@ -74,11 +76,16 @@ function initState() {
|
|
|
74
76
|
<div style="padding: 2rem; min-height: 520px;">
|
|
75
77
|
<JobsStatusTray
|
|
76
78
|
:title="state.title"
|
|
77
|
-
:
|
|
78
|
-
:adapter="adapter"
|
|
79
|
+
:source="{ jobs: state.jobs, cancelJob, deleteJob }"
|
|
79
80
|
:load-on-mount="false"
|
|
80
81
|
:teleport-to="false"
|
|
81
|
-
|
|
82
|
+
>
|
|
83
|
+
<template #job-actions="{ job }">
|
|
84
|
+
<button type="button" class="mint-jobs-tray__action" @click="() => openJob(job.job_id)">
|
|
85
|
+
Open
|
|
86
|
+
</button>
|
|
87
|
+
</template>
|
|
88
|
+
</JobsStatusTray>
|
|
82
89
|
</div>
|
|
83
90
|
</template>
|
|
84
91
|
<template #controls="{ state }">
|
|
@@ -89,7 +96,7 @@ function initState() {
|
|
|
89
96
|
<Variant title="Awaiting input">
|
|
90
97
|
<div style="padding: 2rem; min-height: 520px;">
|
|
91
98
|
<JobsStatusTray
|
|
92
|
-
:
|
|
99
|
+
:source="{ jobs: [job('awaiting_input', 'J-301')] }"
|
|
93
100
|
:load-on-mount="false"
|
|
94
101
|
:teleport-to="false"
|
|
95
102
|
/>
|
|
@@ -99,8 +106,7 @@ function initState() {
|
|
|
99
106
|
<Variant title="Explicit actions">
|
|
100
107
|
<div style="padding: 2rem; min-height: 520px;">
|
|
101
108
|
<JobsStatusTray
|
|
102
|
-
:
|
|
103
|
-
:adapter="adapter"
|
|
109
|
+
:source="{ jobs: explicitActionJobs, cancelJob, deleteJob }"
|
|
104
110
|
:load-on-mount="false"
|
|
105
111
|
:teleport-to="false"
|
|
106
112
|
/>
|
|
@@ -110,7 +116,7 @@ function initState() {
|
|
|
110
116
|
<Variant title="Empty">
|
|
111
117
|
<div style="padding: 2rem; min-height: 520px;">
|
|
112
118
|
<JobsStatusTray
|
|
113
|
-
:
|
|
119
|
+
:source="{ jobs: [] }"
|
|
114
120
|
:load-on-mount="false"
|
|
115
121
|
:teleport-to="false"
|
|
116
122
|
/>
|
|
@@ -15,24 +15,12 @@ import {
|
|
|
15
15
|
} from '../jobs'
|
|
16
16
|
import {
|
|
17
17
|
usePluginJobCenter,
|
|
18
|
-
type JobsStatusTrayAdapter,
|
|
19
18
|
type PluginJobCenterSource,
|
|
20
19
|
} from '../composables/useJobsStatusTray'
|
|
21
|
-
import type {
|
|
22
|
-
import type {
|
|
23
|
-
JobState,
|
|
24
|
-
JobStateInput,
|
|
25
|
-
JobStreamData,
|
|
26
|
-
} from '../types/jobs'
|
|
20
|
+
import type { JobState } from '../types/jobs'
|
|
27
21
|
|
|
28
22
|
interface Props {
|
|
29
23
|
source?: PluginJobCenterSource
|
|
30
|
-
/** @deprecated Pass a source instead. */
|
|
31
|
-
jobs?: readonly JobStateInput[]
|
|
32
|
-
/** @deprecated Pass source actions instead. */
|
|
33
|
-
adapter?: JobsStatusTrayAdapter
|
|
34
|
-
/** @deprecated Event streams belong to the source runtime. */
|
|
35
|
-
eventStream?: UsePluginEventStreamReturn<JobStreamData>
|
|
36
24
|
loadOnMount?: boolean
|
|
37
25
|
title?: string
|
|
38
26
|
teleportTo?: string | HTMLElement | false
|
|
@@ -49,6 +37,11 @@ const emit = defineEmits<{
|
|
|
49
37
|
error: [message: string]
|
|
50
38
|
}>()
|
|
51
39
|
|
|
40
|
+
defineSlots<{
|
|
41
|
+
/** Extra per-row actions, rendered before the built-in Cancel / Delete. */
|
|
42
|
+
'job-actions'?: (props: { job: JobState }) => unknown
|
|
43
|
+
}>()
|
|
44
|
+
|
|
52
45
|
const panelId = `mint-jobs-tray-${useId().replace(/:/g, '')}`
|
|
53
46
|
const titleId = `${panelId}-title`
|
|
54
47
|
const triggerRef = ref<HTMLButtonElement | null>(null)
|
|
@@ -56,9 +49,6 @@ const panelRef = ref<HTMLElement | null>(null)
|
|
|
56
49
|
const teleportTarget = computed(() => props.teleportTo || 'body')
|
|
57
50
|
const tray = usePluginJobCenter({
|
|
58
51
|
source: toRef(props, 'source'),
|
|
59
|
-
jobs: toRef(props, 'jobs'),
|
|
60
|
-
adapter: toRef(props, 'adapter'),
|
|
61
|
-
eventStream: toRef(props, 'eventStream'),
|
|
62
52
|
loadOnMount: props.loadOnMount,
|
|
63
53
|
})
|
|
64
54
|
const canClear = computed(() => (
|
|
@@ -309,6 +299,7 @@ async function clearFinished(): Promise<void> {
|
|
|
309
299
|
<div class="mint-jobs-tray__job-footer">
|
|
310
300
|
<span>{{ progressText(job) }}</span>
|
|
311
301
|
<div class="mint-jobs-tray__job-actions">
|
|
302
|
+
<slot name="job-actions" :job="job" />
|
|
312
303
|
<button
|
|
313
304
|
v-if="job.can_cancel && tray.canCancel.value"
|
|
314
305
|
type="button"
|
|
@@ -371,18 +362,21 @@ async function clearFinished(): Promise<void> {
|
|
|
371
362
|
</div>
|
|
372
363
|
<div class="mint-jobs-tray__job-footer">
|
|
373
364
|
<span>{{ formatTimestamp(job.completed_at || job.created_at) }}</span>
|
|
374
|
-
<
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
365
|
+
<div class="mint-jobs-tray__job-actions">
|
|
366
|
+
<slot name="job-actions" :job="job" />
|
|
367
|
+
<button
|
|
368
|
+
v-if="job.can_delete && tray.canDelete.value"
|
|
369
|
+
type="button"
|
|
370
|
+
class="mint-jobs-tray__icon-button mint-jobs-tray__icon-button--danger"
|
|
371
|
+
aria-label="Delete job"
|
|
372
|
+
:disabled="tray.isPending(job)"
|
|
373
|
+
@click="deleteJob(job)"
|
|
374
|
+
>
|
|
375
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true">
|
|
376
|
+
<path d="M4 7h16M9 7V4h6v3m-8 0 1 13h8l1-13M10 11v5M14 11v5" />
|
|
377
|
+
</svg>
|
|
378
|
+
</button>
|
|
379
|
+
</div>
|
|
386
380
|
</div>
|
|
387
381
|
</article>
|
|
388
382
|
</template>
|
|
@@ -14,6 +14,9 @@ export const APPEARANCE_TAB_ID = 'appearance'
|
|
|
14
14
|
export const APPEARANCE_TAB: SettingsTab = {
|
|
15
15
|
id: APPEARANCE_TAB_ID,
|
|
16
16
|
label: 'Appearance',
|
|
17
|
+
// Heroicons "swatch" — the built-in tab sits beside plugin tabs that all
|
|
18
|
+
// carry icons, so it needs one of its own to keep the rail consistent.
|
|
19
|
+
icon: '<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4.098 19.902a3.75 3.75 0 0 0 5.304 0l6.401-6.402M6.75 21A3.75 3.75 0 0 1 3 17.25V4.125C3 3.504 3.504 3 4.125 3h5.25c.621 0 1.125.504 1.125 1.125v4.072M6.75 21a3.75 3.75 0 0 0 3.75-3.75V8.197M6.75 21h13.125c.621 0 1.125-.504 1.125-1.125v-5.25c0-.621-.504-1.125-1.125-1.125h-4.072M10.5 8.197l2.88-2.88c.438-.439 1.15-.439 1.59 0l3.712 3.713c.44.44.44 1.152 0 1.59l-2.879 2.88M6.75 17.25h.008v.008H6.75v-.008Z"></path></svg>',
|
|
17
20
|
description: 'Theme, color palette, and table density',
|
|
18
21
|
}
|
|
19
22
|
|
package/src/composables/index.ts
CHANGED
|
@@ -162,7 +162,6 @@ export {
|
|
|
162
162
|
type DilutionPreset,
|
|
163
163
|
type UseReagentSeriesReturn,
|
|
164
164
|
} from './useReagentSeries'
|
|
165
|
-
export { usePluginConfig, type UsePluginConfigReturn } from './usePluginConfig'
|
|
166
165
|
export {
|
|
167
166
|
useExperimentSelector,
|
|
168
167
|
type UseExperimentSelectorOptions,
|
|
@@ -488,7 +487,6 @@ export {
|
|
|
488
487
|
pluginFormDataFromPayload,
|
|
489
488
|
resolvePluginBaseUrl,
|
|
490
489
|
uploadPluginEndpoint,
|
|
491
|
-
usePluginClient,
|
|
492
490
|
usePluginEventStream,
|
|
493
491
|
usePluginSettings,
|
|
494
492
|
useCurrentExperiment,
|
|
@@ -517,14 +515,9 @@ export {
|
|
|
517
515
|
export {
|
|
518
516
|
combinePluginJobSources,
|
|
519
517
|
usePluginJobCenter,
|
|
520
|
-
useJobsStatusTray,
|
|
521
518
|
type CombinedPluginJobCenterSource,
|
|
522
519
|
type PluginJobCenterSource,
|
|
523
|
-
type JobsStatusTrayAdapter,
|
|
524
520
|
type JobsStatusTrayTab,
|
|
525
|
-
type JobStreamMessage,
|
|
526
521
|
type UsePluginJobCenterOptions,
|
|
527
522
|
type UsePluginJobCenterReturn,
|
|
528
|
-
type UseJobsStatusTrayOptions,
|
|
529
|
-
type UseJobsStatusTrayReturn,
|
|
530
523
|
} from './useJobsStatusTray'
|
|
@@ -93,7 +93,6 @@ export function useAuth(): UseAuthReturn {
|
|
|
93
93
|
passkey_enabled: boolean
|
|
94
94
|
passkey_registered?: boolean
|
|
95
95
|
registration_enabled?: boolean
|
|
96
|
-
database_mode?: string
|
|
97
96
|
access?: {
|
|
98
97
|
experiment_visibility_mode?: 'open' | 'restricted'
|
|
99
98
|
experimentVisibilityMode?: 'open' | 'restricted'
|
|
@@ -105,7 +104,6 @@ export function useAuth(): UseAuthReturn {
|
|
|
105
104
|
passkeyEnabled: response.data.passkey_enabled,
|
|
106
105
|
passkeyRegistered: response.data.passkey_registered ?? false,
|
|
107
106
|
registrationEnabled: response.data.registration_enabled ?? false,
|
|
108
|
-
databaseMode: response.data.database_mode ?? 'none',
|
|
109
107
|
experimentVisibilityMode:
|
|
110
108
|
response.data.access?.experiment_visibility_mode
|
|
111
109
|
?? response.data.access?.experimentVisibilityMode
|
|
@@ -121,7 +119,6 @@ export function useAuth(): UseAuthReturn {
|
|
|
121
119
|
passkeyEnabled: false,
|
|
122
120
|
passkeyRegistered: false,
|
|
123
121
|
registrationEnabled: false,
|
|
124
|
-
databaseMode: 'none',
|
|
125
122
|
experimentVisibilityMode: 'open',
|
|
126
123
|
}
|
|
127
124
|
}
|