@morscherlab/mint-sdk 1.0.63 → 1.1.0-beta.2
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 +101 -2
- package/dist/EmptyState-D1c4RdC7.js +638 -0
- package/dist/EmptyState-D1c4RdC7.js.map +1 -0
- package/dist/{ExperimentPopover-BbPkIFsI.js → ExperimentPopover-BFw3QX8L.js} +10 -9
- package/dist/{ExperimentPopover-BbPkIFsI.js.map → ExperimentPopover-BFw3QX8L.js.map} +1 -1
- package/dist/{ExperimentSelectorModal-DZeSd-yr.js → ExperimentSelectorModal-B7w6k3Y8.js} +12 -216
- package/dist/ExperimentSelectorModal-B7w6k3Y8.js.map +1 -0
- package/dist/{SettingsModal-CJgLadsN.js → SettingsModal-DPDwCbXK.js} +12 -9
- package/dist/{SettingsModal-CJgLadsN.js.map → SettingsModal-DPDwCbXK.js.map} +1 -1
- package/dist/__tests__/components/GeneratedPlotlyResult.test.d.ts +1 -0
- package/dist/__tests__/components/GeneratedPluginView.test.d.ts +1 -0
- package/dist/__tests__/components/GeneratedResultRenderer.test.d.ts +1 -0
- package/dist/__tests__/components/InstrumentAlertLog.test.d.ts +1 -0
- package/dist/__tests__/composables/useGeneratedAnalysis.test.d.ts +1 -0
- package/dist/__tests__/generatedUi.test.d.ts +1 -0
- package/dist/{analysisArtifactTypes-Cu40dzSG.js → analysisArtifactTypes-ejqR8MX3.js} +1 -1
- package/dist/analysisArtifactTypes-ejqR8MX3.js.map +1 -0
- package/dist/{auth-DZAwEeis.js → auth-B87xM--s.js} +1 -1
- package/dist/{auth-DZAwEeis.js.map → auth-B87xM--s.js.map} +1 -1
- package/dist/chunk-BoAXSpZd.js +33 -0
- package/dist/components/AppSidebar.vue.d.ts +1 -1
- package/dist/components/AutoGroupModal.vue.d.ts +2 -2
- package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +2 -2
- package/dist/components/CollapsibleCard.vue.d.ts +1 -1
- package/dist/components/ComponentBindingRenderer.vue.d.ts +1 -1
- package/dist/components/ConfirmDialog.vue.d.ts +1 -1
- package/dist/components/ExperimentDataViewer.vue.d.ts +1 -1
- package/dist/components/ExperimentTimeline.vue.d.ts +1 -1
- package/dist/components/FileUploader.vue.d.ts +4 -2
- package/dist/components/FitPanel.vue.d.ts +1 -1
- package/dist/components/FormActions.vue.d.ts +1 -1
- package/dist/components/FormBuilder.vue.d.ts +1 -1
- package/dist/components/GeneratedPathInput.vue.d.ts +12 -0
- package/dist/components/GeneratedPlotlyResult.vue.d.ts +11 -0
- package/dist/components/GeneratedPluginView.vue.d.ts +10 -0
- package/dist/components/GeneratedResultRenderer.vue.d.ts +6 -0
- package/dist/components/JobsStatusTray.vue.d.ts +5 -1
- package/dist/components/LcmsSequenceTable.vue.d.ts +1 -1
- package/dist/components/SampleLegend.vue.d.ts +1 -1
- package/dist/components/SampleSelector.vue.d.ts +2 -2
- package/dist/components/SettingsModal.vue.d.ts +1 -1
- package/dist/components/SmartGroupFieldRecipe.vue.d.ts +2 -2
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.js +8 -9
- package/dist/components/internal/PlateMapEditorToolbarInternal.vue.d.ts +2 -2
- package/dist/{components-DdzHh3a_.js → components-UfkPN1bK.js} +2255 -2303
- package/dist/components-UfkPN1bK.js.map +1 -0
- package/dist/composables/index.d.ts +2 -3
- package/dist/composables/index.js +10 -11
- package/dist/composables/platformContextHelpers.d.ts +0 -2
- package/dist/composables/useAnalysisArtifacts.d.ts +1 -3
- package/dist/composables/useGeneratedAnalysis.d.ts +62 -0
- package/dist/composables/useJobsStatusTray.d.ts +48 -4
- package/dist/composables/usePlatformContext.d.ts +3 -0
- package/dist/composables/usePluginClient.d.ts +6 -3
- package/dist/{composables-BUUphBqg.js → composables-B512bWJP.js} +155 -74
- package/dist/composables-B512bWJP.js.map +1 -0
- package/dist/experiment-utils-CxkruZ3G.js +392 -0
- package/dist/experiment-utils-CxkruZ3G.js.map +1 -0
- package/dist/generated/schema.d.ts +7 -0
- package/dist/generated-ui/main.d.ts +0 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -18
- package/dist/index.js.map +1 -1
- package/dist/install.js +7 -8
- package/dist/install.js.map +1 -1
- package/dist/plotly.min-BR53hja4.js +180634 -0
- package/dist/plotly.min-BR53hja4.js.map +1 -0
- package/dist/stores/auth.d.ts +2 -2
- package/dist/stores/index.js +1 -1
- package/dist/styles.css +10543 -10404
- package/dist/templates/index.js +4 -3
- package/dist/templates/packs.d.ts +0 -1
- package/dist/{templates-CNbPQNID.js → templates-Cttbjbyc.js} +2 -4
- package/dist/templates-Cttbjbyc.js.map +1 -0
- package/dist/types/analysisArtifactTypes.d.ts +0 -8
- package/dist/types/form-builder.d.ts +1 -1
- package/dist/types/generated-ui.d.ts +170 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.js +1 -1
- package/dist/types/instrument.d.ts +1 -0
- package/dist/types/jobs.d.ts +7 -1
- package/dist/types/platform.d.ts +2 -0
- package/dist/{useControlSchema-BZNdalmL.js → useControlSchema-Dallso-L.js} +1852 -171
- package/dist/useControlSchema-Dallso-L.js.map +1 -0
- package/dist/{useDropdownState-Bb-c_PeE.js → useDropdownState-gVaMXtwW.js} +2 -2
- package/dist/{useDropdownState-Bb-c_PeE.js.map → useDropdownState-gVaMXtwW.js.map} +1 -1
- package/dist/{useExperimentSelector-DRbj6vse.js → useExperimentSelector-BMjzNcf2.js} +4 -255
- package/dist/useExperimentSelector-BMjzNcf2.js.map +1 -0
- package/dist/{useFormBuilder-DKekvXRs.js → useFormBuilder-BXaaDI41.js} +2 -2
- package/dist/{useFormBuilder-DKekvXRs.js.map → useFormBuilder-BXaaDI41.js.map} +1 -1
- package/dist/{useAnalysisArtifacts-C3l0a0vu.js → useProtocolTemplates-BP7_4RGQ.js} +307 -475
- package/dist/useProtocolTemplates-BP7_4RGQ.js.map +1 -0
- package/package.json +7 -3
- package/src/__tests__/components/GeneratedPathInput.test.ts +132 -0
- package/src/__tests__/components/GeneratedPlotlyResult.test.ts +117 -0
- package/src/__tests__/components/GeneratedPluginView.test.ts +230 -0
- package/src/__tests__/components/GeneratedResultRenderer.test.ts +93 -0
- package/src/__tests__/components/InstrumentAlertLog.test.ts +59 -0
- package/src/__tests__/composables/formBuilderRegistry.test.ts +5 -1
- package/src/__tests__/composables/useAnalysisArtifacts.test.ts +1 -74
- package/src/__tests__/composables/useApi.test.ts +8 -0
- package/src/__tests__/composables/useGeneratedAnalysis.test.ts +1740 -0
- package/src/__tests__/composables/useJobsStatusTray.test.ts +179 -1
- package/src/__tests__/composables/usePlatformContext.test.ts +28 -0
- package/src/__tests__/composables/usePluginClient.test.ts +366 -20
- package/src/__tests__/composables/usePluginConfig.test.ts +323 -7
- package/src/__tests__/docs/extractDocsTemplates.test.ts +0 -1
- package/src/__tests__/generatedUi.test.ts +119 -0
- package/src/__tests__/templates/templates.test.ts +1 -1
- package/src/components/FileUploader.vue +49 -11
- package/src/components/GeneratedPathInput.vue +218 -0
- package/src/components/GeneratedPlotlyResult.vue +87 -0
- package/src/components/GeneratedPluginView.vue +279 -0
- package/src/components/GeneratedResultRenderer.vue +137 -0
- package/src/components/InstrumentAlertLog.vue +2 -2
- package/src/components/JobsStatusTray.vue +14 -8
- package/src/components/index.ts +4 -0
- package/src/composables/formBuilderRegistry.ts +2 -0
- package/src/composables/index.ts +18 -5
- package/src/composables/platformContextHelpers.ts +3 -10
- package/src/composables/useAnalysisArtifacts.ts +0 -48
- package/src/composables/useApi.ts +1 -0
- package/src/composables/useGeneratedAnalysis.ts +1252 -0
- package/src/composables/useJobsStatusTray.ts +234 -45
- package/src/composables/usePluginClient.ts +152 -25
- package/src/composables/usePluginConfig.ts +156 -20
- package/src/generated/schema.ts +130 -0
- package/src/generated-ui/main.ts +21 -0
- package/src/index.ts +3 -0
- package/src/jobs.ts +5 -0
- package/src/plotly-dist.d.ts +3 -0
- package/src/templates/packs.ts +0 -3
- package/src/types/analysisArtifactTypes.ts +0 -9
- package/src/types/form-builder.ts +1 -0
- package/src/types/generated-ui.ts +176 -0
- package/src/types/index.ts +2 -1
- package/src/types/instrument.ts +1 -0
- package/src/types/jobs.ts +10 -1
- package/src/types/platform.ts +2 -0
- package/dist/BaseButton-Dgqrze41.js +0 -74
- package/dist/BaseButton-Dgqrze41.js.map +0 -1
- package/dist/BaseModal-B9UA8Y_I.js +0 -165
- package/dist/BaseModal-B9UA8Y_I.js.map +0 -1
- package/dist/BaseSelect-ekgr9fDo.js +0 -179
- package/dist/BaseSelect-ekgr9fDo.js.map +0 -1
- package/dist/ExperimentPopover-Ryusjrhv.js +0 -4
- package/dist/ExperimentSelectorModal-Cg4-LXBD.js +0 -5
- package/dist/ExperimentSelectorModal-DZeSd-yr.js.map +0 -1
- package/dist/SettingsModal-Bl_9w2Y3.js +0 -5
- package/dist/analysisArtifactTypes-Cu40dzSG.js.map +0 -1
- package/dist/components-DdzHh3a_.js.map +0 -1
- package/dist/composables/analysisArtifactTarget.d.ts +0 -5
- package/dist/composables-BUUphBqg.js.map +0 -1
- package/dist/experiment-utils-Bfa7CwPU.js +0 -108
- package/dist/experiment-utils-Bfa7CwPU.js.map +0 -1
- package/dist/templates-CNbPQNID.js.map +0 -1
- package/dist/useAnalysisArtifacts-C3l0a0vu.js.map +0 -1
- package/dist/useControlSchema-BZNdalmL.js.map +0 -1
- package/dist/useEventListener-CfVkP9Xz.js +0 -57
- package/dist/useEventListener-CfVkP9Xz.js.map +0 -1
- package/dist/useExperimentSelector-DRbj6vse.js.map +0 -1
- package/src/__tests__/composables/analysisArtifactTarget.test.ts +0 -77
- package/src/composables/analysisArtifactTarget.ts +0 -53
- /package/dist/__tests__/{composables/analysisArtifactTarget.test.d.ts → components/GeneratedPathInput.test.d.ts} +0 -0
|
@@ -38,16 +38,24 @@ type FakeResponse = Record<string, unknown>
|
|
|
38
38
|
|
|
39
39
|
let getResponse: FakeResponse = { plugin_name: 'drp', config: {} }
|
|
40
40
|
let patchResponse: FakeResponse = { plugin_name: 'drp', config: {} }
|
|
41
|
+
let putResponse: FakeResponse = { plugin_name: 'drp', config: {} }
|
|
41
42
|
let getError: Error | null = null
|
|
42
43
|
let patchError: Error | null = null
|
|
43
|
-
let
|
|
44
|
+
let recordedBaseUrls: Array<string | undefined> = []
|
|
45
|
+
let recordedCalls: {
|
|
46
|
+
method: string
|
|
47
|
+
url: string
|
|
48
|
+
data?: unknown
|
|
49
|
+
headers?: AxiosRequestConfig['headers']
|
|
50
|
+
}[] = []
|
|
44
51
|
|
|
45
52
|
function mockAxios() {
|
|
46
53
|
vi.spyOn(axios.Axios.prototype, 'get').mockImplementation(async function (
|
|
47
54
|
this: unknown,
|
|
48
55
|
url: string,
|
|
49
|
-
|
|
56
|
+
config?: AxiosRequestConfig,
|
|
50
57
|
) {
|
|
58
|
+
recordedBaseUrls.push(config?.baseURL)
|
|
51
59
|
recordedCalls.push({ method: 'GET', url })
|
|
52
60
|
if (getError) throw getError
|
|
53
61
|
return { data: getResponse }
|
|
@@ -56,12 +64,23 @@ function mockAxios() {
|
|
|
56
64
|
this: unknown,
|
|
57
65
|
url: string,
|
|
58
66
|
data?: unknown,
|
|
59
|
-
|
|
67
|
+
config?: AxiosRequestConfig,
|
|
60
68
|
) {
|
|
69
|
+
recordedBaseUrls.push(config?.baseURL)
|
|
61
70
|
recordedCalls.push({ method: 'PATCH', url, data })
|
|
62
71
|
if (patchError) throw patchError
|
|
63
72
|
return { data: patchResponse }
|
|
64
73
|
})
|
|
74
|
+
vi.spyOn(axios.Axios.prototype, 'put').mockImplementation(async function (
|
|
75
|
+
this: unknown,
|
|
76
|
+
url: string,
|
|
77
|
+
data?: unknown,
|
|
78
|
+
config?: AxiosRequestConfig,
|
|
79
|
+
) {
|
|
80
|
+
recordedBaseUrls.push(config?.baseURL)
|
|
81
|
+
recordedCalls.push({ method: 'PUT', url, data, headers: config?.headers })
|
|
82
|
+
return { data: putResponse }
|
|
83
|
+
})
|
|
65
84
|
}
|
|
66
85
|
|
|
67
86
|
describe('usePluginConfig', () => {
|
|
@@ -70,13 +89,17 @@ describe('usePluginConfig', () => {
|
|
|
70
89
|
autoLoadOnMount = false
|
|
71
90
|
getResponse = { plugin_name: 'drp', config: {} }
|
|
72
91
|
patchResponse = { plugin_name: 'drp', config: {} }
|
|
92
|
+
putResponse = { plugin_name: 'drp', config: {} }
|
|
73
93
|
getError = null
|
|
74
94
|
patchError = null
|
|
75
95
|
recordedCalls = []
|
|
96
|
+
recordedBaseUrls = []
|
|
97
|
+
delete (window as unknown as { __MINT_PLATFORM__?: unknown }).__MINT_PLATFORM__
|
|
76
98
|
mockAxios()
|
|
77
99
|
})
|
|
78
100
|
|
|
79
101
|
afterEach(() => {
|
|
102
|
+
delete (window as unknown as { __MINT_PLATFORM__?: unknown }).__MINT_PLATFORM__
|
|
80
103
|
vi.restoreAllMocks()
|
|
81
104
|
})
|
|
82
105
|
|
|
@@ -106,12 +129,148 @@ describe('usePluginConfig', () => {
|
|
|
106
129
|
expect(recordedCalls[0]!.url).toBe('/plugins/my%2Fplugin/config')
|
|
107
130
|
})
|
|
108
131
|
|
|
109
|
-
it(
|
|
110
|
-
|
|
111
|
-
|
|
132
|
+
it.each([
|
|
133
|
+
'platform',
|
|
134
|
+
undefined,
|
|
135
|
+
] as const)(
|
|
136
|
+
'should use the platform config facade when settings_api is %s',
|
|
137
|
+
async (settingsApi) => {
|
|
138
|
+
;(window as unknown as { __MINT_PLATFORM__?: unknown }).__MINT_PLATFORM__ = {
|
|
139
|
+
isIntegrated: true,
|
|
140
|
+
theme: 'light',
|
|
141
|
+
platformApiUrl: '/api',
|
|
142
|
+
plugin: {
|
|
143
|
+
id: 'drp',
|
|
144
|
+
name: 'drp',
|
|
145
|
+
version: '1.0.0',
|
|
146
|
+
route_prefix: '/drp',
|
|
147
|
+
api_prefix: '/api/drp',
|
|
148
|
+
...(settingsApi ? { settings_api: settingsApi } : {}),
|
|
149
|
+
},
|
|
150
|
+
}
|
|
151
|
+
getResponse = {
|
|
152
|
+
plugin_name: 'drp',
|
|
153
|
+
config: { n: 1 },
|
|
154
|
+
revision: 'revision-1',
|
|
155
|
+
}
|
|
156
|
+
const hook = usePluginConfig('drp')
|
|
157
|
+
|
|
158
|
+
await hook.load()
|
|
159
|
+
|
|
160
|
+
expect(recordedCalls).toEqual([
|
|
161
|
+
{ method: 'GET', url: '/plugins/drp/config' },
|
|
162
|
+
])
|
|
163
|
+
},
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
it('should use the plugin settings router for an integrated plugin capability', async () => {
|
|
167
|
+
;(window as unknown as { __MINT_PLATFORM__?: unknown }).__MINT_PLATFORM__ = {
|
|
168
|
+
isIntegrated: true,
|
|
169
|
+
theme: 'light',
|
|
170
|
+
platformApiUrl: '/api',
|
|
171
|
+
plugin: {
|
|
172
|
+
id: 'drp',
|
|
173
|
+
name: 'drp',
|
|
174
|
+
version: '1.0.0',
|
|
175
|
+
route_prefix: '/drp',
|
|
176
|
+
api_prefix: '/api/drp',
|
|
177
|
+
settings_api: 'plugin',
|
|
178
|
+
},
|
|
179
|
+
}
|
|
180
|
+
getResponse = {
|
|
181
|
+
settings: { n: 1 },
|
|
182
|
+
mode: 'integrated',
|
|
183
|
+
revision: 'revision-1',
|
|
184
|
+
}
|
|
185
|
+
putResponse = {
|
|
186
|
+
settings: { n: 2 },
|
|
187
|
+
mode: 'integrated',
|
|
188
|
+
revision: 'revision-2',
|
|
189
|
+
}
|
|
112
190
|
const hook = usePluginConfig('drp')
|
|
113
191
|
await hook.load()
|
|
114
192
|
hook.config.value = { n: 2 }
|
|
193
|
+
|
|
194
|
+
const saved = await hook.save()
|
|
195
|
+
|
|
196
|
+
expect({
|
|
197
|
+
saved,
|
|
198
|
+
calls: recordedCalls,
|
|
199
|
+
baseUrls: recordedBaseUrls,
|
|
200
|
+
config: hook.config.value,
|
|
201
|
+
}).toEqual({
|
|
202
|
+
saved: true,
|
|
203
|
+
baseUrls: ['/api/drp', '/api/drp'],
|
|
204
|
+
calls: [
|
|
205
|
+
{ method: 'GET', url: '/settings' },
|
|
206
|
+
{
|
|
207
|
+
method: 'PUT',
|
|
208
|
+
url: '/settings',
|
|
209
|
+
data: { n: 2 },
|
|
210
|
+
headers: { 'If-Match': '"revision-1"' },
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
config: { n: 2 },
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('should use the platform facade for an explicit different plugin name', async () => {
|
|
218
|
+
;(window as unknown as { __MINT_PLATFORM__?: unknown }).__MINT_PLATFORM__ = {
|
|
219
|
+
isIntegrated: true,
|
|
220
|
+
theme: 'light',
|
|
221
|
+
platformApiUrl: '/api',
|
|
222
|
+
plugin: {
|
|
223
|
+
id: 'current-plugin',
|
|
224
|
+
name: 'current-plugin',
|
|
225
|
+
version: '1.0.0',
|
|
226
|
+
route_prefix: '/current-plugin',
|
|
227
|
+
api_prefix: '/api/current-plugin',
|
|
228
|
+
settings_api: 'plugin',
|
|
229
|
+
},
|
|
230
|
+
}
|
|
231
|
+
getResponse = {
|
|
232
|
+
plugin_name: 'other-plugin',
|
|
233
|
+
config: { n: 1 },
|
|
234
|
+
revision: 'revision-1',
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const hook = usePluginConfig('other-plugin')
|
|
238
|
+
await hook.load()
|
|
239
|
+
|
|
240
|
+
expect({
|
|
241
|
+
calls: recordedCalls,
|
|
242
|
+
config: hook.config.value,
|
|
243
|
+
}).toEqual({
|
|
244
|
+
calls: [
|
|
245
|
+
{ method: 'GET', url: '/plugins/other-plugin/config' },
|
|
246
|
+
],
|
|
247
|
+
config: { n: 1 },
|
|
248
|
+
})
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
it('should PATCH only dirty typed fields through a versioned platform facade', async () => {
|
|
252
|
+
getResponse = {
|
|
253
|
+
plugin_name: 'drp',
|
|
254
|
+
config: {
|
|
255
|
+
n: 1,
|
|
256
|
+
allowed_experiment_types: ['metabolomics'],
|
|
257
|
+
},
|
|
258
|
+
revision: 'revision-1',
|
|
259
|
+
}
|
|
260
|
+
patchResponse = {
|
|
261
|
+
plugin_name: 'drp',
|
|
262
|
+
config: {
|
|
263
|
+
n: 2,
|
|
264
|
+
allowed_experiment_types: ['metabolomics'],
|
|
265
|
+
},
|
|
266
|
+
revision: 'revision-2',
|
|
267
|
+
}
|
|
268
|
+
const hook = usePluginConfig('drp')
|
|
269
|
+
await hook.load()
|
|
270
|
+
hook.config.value = {
|
|
271
|
+
n: 2,
|
|
272
|
+
allowed_experiment_types: ['metabolomics'],
|
|
273
|
+
}
|
|
115
274
|
await nextTick()
|
|
116
275
|
expect(hook.isDirty.value).toBe(true)
|
|
117
276
|
|
|
@@ -120,11 +279,168 @@ describe('usePluginConfig', () => {
|
|
|
120
279
|
const patchCall = recordedCalls.find((c) => c.method === 'PATCH')!
|
|
121
280
|
expect(patchCall.url).toBe('/plugins/drp/config')
|
|
122
281
|
expect(patchCall.data).toEqual({ config: { n: 2 } })
|
|
123
|
-
expect(
|
|
282
|
+
expect(patchCall.headers).toBeUndefined()
|
|
283
|
+
expect(hook.config.value).toEqual({
|
|
284
|
+
n: 2,
|
|
285
|
+
allowed_experiment_types: ['metabolomics'],
|
|
286
|
+
})
|
|
124
287
|
expect(hook.isDirty.value).toBe(false)
|
|
125
288
|
expect(hook.lastSavedAt.value).toBeInstanceOf(Date)
|
|
126
289
|
})
|
|
127
290
|
|
|
291
|
+
it('should split changed platform-owned config from a versioned typed save', async () => {
|
|
292
|
+
getResponse = {
|
|
293
|
+
plugin_name: 'drp',
|
|
294
|
+
config: {
|
|
295
|
+
n: 1,
|
|
296
|
+
allowed_experiment_types: ['metabolomics'],
|
|
297
|
+
},
|
|
298
|
+
revision: 'revision-1',
|
|
299
|
+
}
|
|
300
|
+
patchResponse = {
|
|
301
|
+
plugin_name: 'drp',
|
|
302
|
+
config: {
|
|
303
|
+
n: 2,
|
|
304
|
+
allowed_experiment_types: ['proteomics'],
|
|
305
|
+
},
|
|
306
|
+
revision: 'untrusted-platform-revision',
|
|
307
|
+
}
|
|
308
|
+
const hook = usePluginConfig('drp')
|
|
309
|
+
await hook.load()
|
|
310
|
+
hook.config.value = {
|
|
311
|
+
n: 2,
|
|
312
|
+
allowed_experiment_types: ['proteomics'],
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const saved = await hook.save()
|
|
316
|
+
patchResponse = {
|
|
317
|
+
plugin_name: 'drp',
|
|
318
|
+
config: {
|
|
319
|
+
n: 3,
|
|
320
|
+
allowed_experiment_types: ['proteomics'],
|
|
321
|
+
},
|
|
322
|
+
revision: 'revision-3',
|
|
323
|
+
}
|
|
324
|
+
hook.config.value = {
|
|
325
|
+
n: 3,
|
|
326
|
+
allowed_experiment_types: ['proteomics'],
|
|
327
|
+
}
|
|
328
|
+
const savedAgain = await hook.save()
|
|
329
|
+
|
|
330
|
+
expect({
|
|
331
|
+
saved,
|
|
332
|
+
savedAgain,
|
|
333
|
+
calls: recordedCalls,
|
|
334
|
+
config: hook.config.value,
|
|
335
|
+
isDirty: hook.isDirty.value,
|
|
336
|
+
}).toEqual({
|
|
337
|
+
saved: true,
|
|
338
|
+
savedAgain: true,
|
|
339
|
+
calls: [
|
|
340
|
+
{ method: 'GET', url: '/plugins/drp/config' },
|
|
341
|
+
{
|
|
342
|
+
method: 'PATCH',
|
|
343
|
+
url: '/plugins/drp/config',
|
|
344
|
+
data: { config: { n: 2 } },
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
method: 'PATCH',
|
|
348
|
+
url: '/plugins/drp/config',
|
|
349
|
+
data: { config: { allowed_experiment_types: ['proteomics'] } },
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
method: 'PATCH',
|
|
353
|
+
url: '/plugins/drp/config',
|
|
354
|
+
data: { config: { n: 3 } },
|
|
355
|
+
},
|
|
356
|
+
],
|
|
357
|
+
config: {
|
|
358
|
+
n: 3,
|
|
359
|
+
allowed_experiment_types: ['proteomics'],
|
|
360
|
+
},
|
|
361
|
+
isDirty: false,
|
|
362
|
+
})
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
it('should avoid a typed PUT when only the platform-owned config changes', async () => {
|
|
366
|
+
getResponse = {
|
|
367
|
+
plugin_name: 'drp',
|
|
368
|
+
config: {
|
|
369
|
+
n: 1,
|
|
370
|
+
allowed_experiment_types: ['metabolomics'],
|
|
371
|
+
},
|
|
372
|
+
revision: 'revision-1',
|
|
373
|
+
}
|
|
374
|
+
patchResponse = {
|
|
375
|
+
plugin_name: 'drp',
|
|
376
|
+
config: {
|
|
377
|
+
n: 1,
|
|
378
|
+
allowed_experiment_types: ['proteomics'],
|
|
379
|
+
},
|
|
380
|
+
}
|
|
381
|
+
const hook = usePluginConfig('drp')
|
|
382
|
+
await hook.load()
|
|
383
|
+
hook.config.value = {
|
|
384
|
+
n: 1,
|
|
385
|
+
allowed_experiment_types: ['proteomics'],
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const saved = await hook.save()
|
|
389
|
+
|
|
390
|
+
expect({
|
|
391
|
+
saved,
|
|
392
|
+
calls: recordedCalls,
|
|
393
|
+
config: hook.config.value,
|
|
394
|
+
}).toEqual({
|
|
395
|
+
saved: true,
|
|
396
|
+
calls: [
|
|
397
|
+
{ method: 'GET', url: '/plugins/drp/config' },
|
|
398
|
+
{
|
|
399
|
+
method: 'PATCH',
|
|
400
|
+
url: '/plugins/drp/config',
|
|
401
|
+
data: { config: { allowed_experiment_types: ['proteomics'] } },
|
|
402
|
+
},
|
|
403
|
+
],
|
|
404
|
+
config: {
|
|
405
|
+
n: 1,
|
|
406
|
+
allowed_experiment_types: ['proteomics'],
|
|
407
|
+
},
|
|
408
|
+
})
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
it('should detect nested config edits and restore the saved JSON snapshot', async () => {
|
|
412
|
+
getResponse = {
|
|
413
|
+
plugin_name: 'drp',
|
|
414
|
+
config: {
|
|
415
|
+
rules: {
|
|
416
|
+
thresholds: [1, 2],
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
}
|
|
420
|
+
const hook = usePluginConfig('drp')
|
|
421
|
+
await hook.load()
|
|
422
|
+
const rules = hook.config.value.rules as { thresholds: number[] }
|
|
423
|
+
rules.thresholds[0] = 9
|
|
424
|
+
await nextTick()
|
|
425
|
+
|
|
426
|
+
const dirtyAfterMutation = hook.isDirty.value
|
|
427
|
+
hook.reset()
|
|
428
|
+
|
|
429
|
+
expect({
|
|
430
|
+
dirtyAfterMutation,
|
|
431
|
+
config: hook.config.value,
|
|
432
|
+
isDirty: hook.isDirty.value,
|
|
433
|
+
}).toEqual({
|
|
434
|
+
dirtyAfterMutation: true,
|
|
435
|
+
config: {
|
|
436
|
+
rules: {
|
|
437
|
+
thresholds: [1, 2],
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
isDirty: false,
|
|
441
|
+
})
|
|
442
|
+
})
|
|
443
|
+
|
|
128
444
|
it('isDirty reflects edits vs last saved snapshot', async () => {
|
|
129
445
|
getResponse = { plugin_name: 'drp', config: { k: 'v' } }
|
|
130
446
|
const hook = usePluginConfig('drp')
|
|
@@ -47,7 +47,6 @@ describe('extract docs template helpers', () => {
|
|
|
47
47
|
group: packEntry.category,
|
|
48
48
|
source: 'packages/sdk-frontend/src/templates/packs.ts',
|
|
49
49
|
add_command: packEntry.add_command,
|
|
50
|
-
init_command: packEntry.init_command,
|
|
51
50
|
docs_command: packEntry.docs_command,
|
|
52
51
|
})
|
|
53
52
|
})
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
generatedJobDefaults,
|
|
5
|
+
generatedJobFormSchema,
|
|
6
|
+
normalizeGeneratedJobInput,
|
|
7
|
+
} from '../generated/schema'
|
|
8
|
+
import type { GeneratedJobDefinition } from '../types/generated-ui'
|
|
9
|
+
|
|
10
|
+
const definition: GeneratedJobDefinition = {
|
|
11
|
+
id: 'summarize',
|
|
12
|
+
title: 'Summarize',
|
|
13
|
+
description: 'Summarize a dataset',
|
|
14
|
+
cpu: 2,
|
|
15
|
+
input_schema: {
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
threshold: { type: 'number', title: 'Threshold', default: 2.5, minimum: 0, maximum: 10 },
|
|
19
|
+
method: { type: 'string', title: 'Method', enum: ['fast', 'robust'], default: 'fast' },
|
|
20
|
+
normalize: { type: 'boolean', title: 'Normalize', default: true },
|
|
21
|
+
source: {
|
|
22
|
+
anyOf: [{ type: 'string', format: 'path' }, { type: 'null' }],
|
|
23
|
+
default: null,
|
|
24
|
+
'x-mint-component': 'path',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
required: ['threshold'],
|
|
28
|
+
},
|
|
29
|
+
output_schema: {},
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe('generated UI schema adapter', () => {
|
|
33
|
+
it('maps the supported JSON Schema subset to standard FormBuilder fields', () => {
|
|
34
|
+
expect(generatedJobFormSchema(definition).sections[0]?.fields).toMatchObject([
|
|
35
|
+
{ name: 'threshold', type: 'number', validation: { required: true, min: 0, max: 10 } },
|
|
36
|
+
{ name: 'method', type: 'select', props: { options: ['fast', 'robust'] } },
|
|
37
|
+
{ name: 'normalize', type: 'toggle' },
|
|
38
|
+
{ name: 'source', type: 'path' },
|
|
39
|
+
])
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('extracts defaults without sharing mutable input state', () => {
|
|
43
|
+
const defaults = generatedJobDefaults(definition)
|
|
44
|
+
const second = generatedJobDefaults(definition)
|
|
45
|
+
defaults.threshold = 9
|
|
46
|
+
|
|
47
|
+
expect(second).toEqual({ threshold: 2.5, method: 'fast', normalize: true, source: null })
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('preserves explicit null defaults without inventing values for missing defaults', () => {
|
|
51
|
+
const nullableDefinition: GeneratedJobDefinition = {
|
|
52
|
+
...definition,
|
|
53
|
+
input_schema: {
|
|
54
|
+
type: 'object',
|
|
55
|
+
properties: {
|
|
56
|
+
optional_count: {
|
|
57
|
+
anyOf: [{ type: 'integer' }, { type: 'null' }],
|
|
58
|
+
default: null,
|
|
59
|
+
},
|
|
60
|
+
required_count: { type: 'integer' },
|
|
61
|
+
},
|
|
62
|
+
required: ['required_count'],
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
expect({
|
|
67
|
+
defaults: generatedJobDefaults(nullableDefinition),
|
|
68
|
+
fields: generatedJobFormSchema(nullableDefinition).sections[0]?.fields.map(field => ({
|
|
69
|
+
name: field.name,
|
|
70
|
+
defaultValue: field.defaultValue,
|
|
71
|
+
})),
|
|
72
|
+
}).toEqual({
|
|
73
|
+
defaults: { optional_count: null },
|
|
74
|
+
fields: [
|
|
75
|
+
{ name: 'optional_count', defaultValue: null },
|
|
76
|
+
{ name: 'required_count', defaultValue: undefined },
|
|
77
|
+
],
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('keeps a selected path for SDK staging during submission', () => {
|
|
82
|
+
const selection = {
|
|
83
|
+
kind: 'path' as const,
|
|
84
|
+
files: [new File(['data'], 'data.csv')],
|
|
85
|
+
relativePaths: ['data.csv'],
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
expect(normalizeGeneratedJobInput(definition, {
|
|
89
|
+
threshold: 2.5,
|
|
90
|
+
source: selection,
|
|
91
|
+
})).toEqual({
|
|
92
|
+
threshold: 2.5,
|
|
93
|
+
source: selection,
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('marks path arrays as multiple path inputs', () => {
|
|
98
|
+
const listDefinition: GeneratedJobDefinition = {
|
|
99
|
+
...definition,
|
|
100
|
+
input_schema: {
|
|
101
|
+
type: 'object',
|
|
102
|
+
properties: {
|
|
103
|
+
sources: {
|
|
104
|
+
type: 'array',
|
|
105
|
+
items: { type: 'string', format: 'path' },
|
|
106
|
+
'x-mint-component': 'path',
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
required: ['sources'],
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
expect(generatedJobFormSchema(listDefinition).sections[0]?.fields[0]).toMatchObject({
|
|
114
|
+
name: 'sources',
|
|
115
|
+
type: 'path',
|
|
116
|
+
props: { multiple: true },
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
})
|
|
@@ -166,7 +166,7 @@ describe('bio data templates', () => {
|
|
|
166
166
|
expect(getBioTemplatePackInfo('metabolism')?.name).toBe('omics-assay')
|
|
167
167
|
expect(getBioTemplatePackInfo('metabolite profiling')?.name).toBe('omics-assay')
|
|
168
168
|
expect(getBioTemplatePackInfo('metabolomics')?.components).toContain('ExperimentTimeline')
|
|
169
|
-
expect(getBioTemplatePackInfo('longitudinal')?.
|
|
169
|
+
expect(getBioTemplatePackInfo('longitudinal')?.add_command).toBe('mint add data-template-pack longitudinal-study --page')
|
|
170
170
|
expect(getBioTemplatePackInfo('gene expression')?.templates.at(-1)).toBe('qpcr-plate')
|
|
171
171
|
expect(getBioTemplatePackInfo('gene-expression')?.templates.at(-1)).toBe('qpcr-plate')
|
|
172
172
|
expect(searchBioTemplatePacks('WellPlate').map(entry => entry.name)).toEqual([
|
|
@@ -19,7 +19,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
19
19
|
})
|
|
20
20
|
|
|
21
21
|
const emit = defineEmits<{
|
|
22
|
-
upload: [files: File[]]
|
|
22
|
+
upload: [files: File[], rootName?: string, relativePaths?: string[]]
|
|
23
|
+
remove: [files: File[]]
|
|
23
24
|
error: [message: string]
|
|
24
25
|
}>()
|
|
25
26
|
|
|
@@ -116,20 +117,31 @@ function validateFiles(files: FileList | File[]): File[] {
|
|
|
116
117
|
return validFiles
|
|
117
118
|
}
|
|
118
119
|
|
|
119
|
-
function handleFiles(
|
|
120
|
+
function handleFiles(
|
|
121
|
+
files: FileList | File[],
|
|
122
|
+
folder?: string,
|
|
123
|
+
explicitRelativePaths?: string[],
|
|
124
|
+
) {
|
|
125
|
+
const candidates = Array.from(files)
|
|
120
126
|
const validFiles = validateFiles(files)
|
|
121
127
|
if (validFiles.length === 0) return
|
|
128
|
+
const relativePaths = validFiles.map((file) => {
|
|
129
|
+
const candidateIndex = candidates.indexOf(file)
|
|
130
|
+
return explicitRelativePaths?.[candidateIndex]
|
|
131
|
+
|| file.webkitRelativePath
|
|
132
|
+
|| (folder ? `${folder}/${file.name}` : file.name)
|
|
133
|
+
})
|
|
122
134
|
|
|
123
135
|
if (isFolder.value) {
|
|
124
136
|
folderName.value = folder || extractFolderName(validFiles)
|
|
125
137
|
selectedFiles.value = validFiles
|
|
126
|
-
emit('upload', validFiles)
|
|
138
|
+
emit('upload', validFiles, folderName.value ?? undefined, relativePaths)
|
|
127
139
|
} else if (!props.multiple) {
|
|
128
140
|
selectedFiles.value = [validFiles[0]]
|
|
129
|
-
emit('upload', [validFiles[0]])
|
|
141
|
+
emit('upload', [validFiles[0]], undefined, [relativePaths[0]])
|
|
130
142
|
} else {
|
|
131
143
|
selectedFiles.value = [...selectedFiles.value, ...validFiles]
|
|
132
|
-
emit('upload', validFiles)
|
|
144
|
+
emit('upload', validFiles, undefined, relativePaths)
|
|
133
145
|
}
|
|
134
146
|
}
|
|
135
147
|
|
|
@@ -156,25 +168,39 @@ function handleDrop(event: DragEvent) {
|
|
|
156
168
|
|
|
157
169
|
async function handleFolderDrop(items: DataTransferItemList) {
|
|
158
170
|
const files: File[] = []
|
|
171
|
+
const relativePaths: string[] = []
|
|
159
172
|
let folderNameFromDrop: string | undefined
|
|
160
173
|
|
|
161
174
|
for (const item of Array.from(items)) {
|
|
162
175
|
const entry = item.webkitGetAsEntry?.()
|
|
163
176
|
if (entry?.isDirectory) {
|
|
164
177
|
folderNameFromDrop = entry.name
|
|
165
|
-
await readDirectory(
|
|
178
|
+
await readDirectory(
|
|
179
|
+
entry as FileSystemDirectoryEntry,
|
|
180
|
+
files,
|
|
181
|
+
relativePaths,
|
|
182
|
+
entry.name,
|
|
183
|
+
)
|
|
166
184
|
} else if (entry?.isFile) {
|
|
167
185
|
const file = await getFile(entry as FileSystemFileEntry)
|
|
168
|
-
if (file)
|
|
186
|
+
if (file) {
|
|
187
|
+
files.push(file)
|
|
188
|
+
relativePaths.push(file.name)
|
|
189
|
+
}
|
|
169
190
|
}
|
|
170
191
|
}
|
|
171
192
|
|
|
172
193
|
if (files.length > 0) {
|
|
173
|
-
handleFiles(files, folderNameFromDrop)
|
|
194
|
+
handleFiles(files, folderNameFromDrop, relativePaths)
|
|
174
195
|
}
|
|
175
196
|
}
|
|
176
197
|
|
|
177
|
-
async function readDirectory(
|
|
198
|
+
async function readDirectory(
|
|
199
|
+
directory: FileSystemDirectoryEntry,
|
|
200
|
+
files: File[],
|
|
201
|
+
relativePaths: string[],
|
|
202
|
+
prefix: string,
|
|
203
|
+
): Promise<void> {
|
|
178
204
|
const reader = directory.createReader()
|
|
179
205
|
// readEntries returns at most 100 entries per call per spec — must loop
|
|
180
206
|
let batch: FileSystemEntry[]
|
|
@@ -185,9 +211,17 @@ async function readDirectory(directory: FileSystemDirectoryEntry, files: File[])
|
|
|
185
211
|
for (const entry of batch) {
|
|
186
212
|
if (entry.isFile) {
|
|
187
213
|
const file = await getFile(entry as FileSystemFileEntry)
|
|
188
|
-
if (file)
|
|
214
|
+
if (file) {
|
|
215
|
+
files.push(file)
|
|
216
|
+
relativePaths.push(`${prefix}/${file.name}`)
|
|
217
|
+
}
|
|
189
218
|
} else if (entry.isDirectory) {
|
|
190
|
-
await readDirectory(
|
|
219
|
+
await readDirectory(
|
|
220
|
+
entry as FileSystemDirectoryEntry,
|
|
221
|
+
files,
|
|
222
|
+
relativePaths,
|
|
223
|
+
`${prefix}/${entry.name}`,
|
|
224
|
+
)
|
|
191
225
|
}
|
|
192
226
|
}
|
|
193
227
|
} while (batch.length > 0)
|
|
@@ -225,15 +259,19 @@ function openFilePicker() {
|
|
|
225
259
|
}
|
|
226
260
|
|
|
227
261
|
function removeFile(index: number) {
|
|
262
|
+
const removed = selectedFiles.value[index]
|
|
228
263
|
selectedFiles.value = selectedFiles.value.filter((_, i) => i !== index)
|
|
229
264
|
if (isFolder.value && selectedFiles.value.length === 0) {
|
|
230
265
|
folderName.value = null
|
|
231
266
|
}
|
|
267
|
+
if (removed) emit('remove', [removed])
|
|
232
268
|
}
|
|
233
269
|
|
|
234
270
|
function clearAll() {
|
|
271
|
+
const removed = [...selectedFiles.value]
|
|
235
272
|
selectedFiles.value = []
|
|
236
273
|
folderName.value = null
|
|
274
|
+
if (removed.length) emit('remove', removed)
|
|
237
275
|
}
|
|
238
276
|
|
|
239
277
|
function formatFileSize(bytes: number): string {
|