@morscherlab/mint-sdk 1.0.63 → 1.1.0-beta.10
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 +119 -6
- package/dist/EmptyState-DgBRoYAy.js +647 -0
- package/dist/EmptyState-DgBRoYAy.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-BBWXC7KW.js} +12 -216
- package/dist/ExperimentSelectorModal-BBWXC7KW.js.map +1 -0
- package/dist/{SettingsModal-CJgLadsN.js → SettingsModal-Dpg-X1OM.js} +12 -9
- package/dist/{SettingsModal-CJgLadsN.js.map → SettingsModal-Dpg-X1OM.js.map} +1 -1
- package/dist/__tests__/components/BaseToggle.test.d.ts +1 -0
- package/dist/__tests__/components/GeneratedPathInput.test.d.ts +1 -0
- 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__/components/SectionCard.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/__tests__/types/GeneratedJsonSchema.test.d.ts +1 -0
- package/dist/{analysisArtifactTypes-Cu40dzSG.js → analysisArtifactTypes-ejqR8MX3.js} +1 -1
- package/dist/{analysisArtifactTypes-Cu40dzSG.js.map → analysisArtifactTypes-ejqR8MX3.js.map} +1 -1
- 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/BaseSelect.vue.d.ts +1 -1
- package/dist/components/BaseToggle.vue.d.ts +2 -0
- 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 +2 -2
- 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/PluginWorkspaceView.props.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/SectionCard.vue.d.ts +47 -0
- package/dist/components/SettingsModal.vue.d.ts +1 -1
- package/dist/components/SmartGroupFieldRecipe.vue.d.ts +2 -2
- package/dist/components/index.d.ts +5 -0
- package/dist/components/index.js +8 -9
- package/dist/components/internal/PlateMapEditorToolbarInternal.vue.d.ts +2 -2
- package/dist/{components-DdzHh3a_.js → components-Ci_BTx00.js} +2322 -2292
- package/dist/components-Ci_BTx00.js.map +1 -0
- package/dist/composables/index.d.ts +3 -2
- package/dist/composables/index.js +10 -11
- 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 +17 -8
- package/dist/{composables-BUUphBqg.js → composables-C0VEJ73O.js} +154 -38
- package/dist/composables-C0VEJ73O.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 +8657 -8217
- package/dist/tailwind.preset.d.ts +5 -0
- package/dist/tailwind.preset.js +7 -2
- package/dist/tailwind.preset.js.map +1 -1
- package/dist/templates/index.js +4 -3
- package/dist/templates/packs.d.ts +0 -1
- package/dist/{templates-CNbPQNID.js → templates-Cdj93S9C.js} +2 -4
- package/dist/templates-Cdj93S9C.js.map +1 -0
- package/dist/types/components.d.ts +2 -0
- package/dist/types/form-builder.d.ts +1 -1
- package/dist/types/generated-ui.d.ts +183 -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-B_qgU4rW.js} +1882 -172
- package/dist/useControlSchema-B_qgU4rW.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-BuQZRgMt.js} +4 -255
- package/dist/useExperimentSelector-BuQZRgMt.js.map +1 -0
- package/dist/{useFormBuilder-DKekvXRs.js → useFormBuilder-D1G5J5Z5.js} +2 -2
- package/dist/{useFormBuilder-DKekvXRs.js.map → useFormBuilder-D1G5J5Z5.js.map} +1 -1
- package/dist/{useAnalysisArtifacts-C3l0a0vu.js → useProtocolTemplates-DeaOmMaw.js} +307 -475
- package/dist/useProtocolTemplates-DeaOmMaw.js.map +1 -0
- package/package.json +7 -3
- package/src/__tests__/components/BaseSelect.test.ts +52 -0
- package/src/__tests__/components/BaseToggle.test.ts +40 -0
- 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 +240 -0
- package/src/__tests__/components/GeneratedResultRenderer.test.ts +93 -0
- package/src/__tests__/components/InstrumentAlertLog.test.ts +59 -0
- package/src/__tests__/components/PluginWorkspaceView.test.ts +68 -14
- package/src/__tests__/components/SectionCard.test.ts +181 -0
- package/src/__tests__/composables/formBuilderRegistry.test.ts +5 -1
- package/src/__tests__/composables/useApi.test.ts +8 -0
- package/src/__tests__/composables/useGeneratedAnalysis.test.ts +1742 -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 +402 -21
- 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/__tests__/types/GeneratedJsonSchema.test.ts +62 -0
- package/src/components/BaseSelect.vue +16 -2
- package/src/components/BaseToggle.vue +3 -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/PluginWorkspaceView.props.ts +1 -1
- package/src/components/PluginWorkspaceView.vue +1 -5
- package/src/components/SectionCard.story.vue +163 -0
- package/src/components/SectionCard.vue +98 -0
- package/src/components/index.ts +5 -0
- package/src/composables/formBuilderRegistry.ts +2 -0
- package/src/composables/index.ts +19 -0
- package/src/composables/useApi.ts +1 -0
- package/src/composables/useGeneratedAnalysis.ts +1270 -0
- package/src/composables/useJobsStatusTray.ts +234 -45
- package/src/composables/usePluginClient.ts +164 -30
- 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/styles/components/section-card.css +161 -0
- package/src/styles/index.css +1 -0
- package/src/styles/variables.css +25 -0
- package/src/tailwind.preset.ts +5 -0
- package/src/templates/packs.ts +0 -3
- package/src/types/components.ts +12 -0
- package/src/types/form-builder.ts +1 -0
- package/src/types/generated-ui.ts +189 -0
- package/src/types/index.ts +4 -0
- 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/components-DdzHh3a_.js.map +0 -1
- 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
|
@@ -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([
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import type { GeneratedJsonSchema } from '../../types/generated-ui'
|
|
4
|
+
|
|
5
|
+
describe('GeneratedJsonSchema', () => {
|
|
6
|
+
it('should accept minimum array size constraints emitted by Pydantic', () => {
|
|
7
|
+
const schema: GeneratedJsonSchema = { minItems: 1 }
|
|
8
|
+
|
|
9
|
+
expect(schema.minItems).toBe(1)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('should accept array size constraints emitted by Pydantic', () => {
|
|
13
|
+
const schema: GeneratedJsonSchema = { maxItems: 100 }
|
|
14
|
+
|
|
15
|
+
expect(schema.maxItems).toBe(100)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('should accept write-only fields emitted by Pydantic', () => {
|
|
19
|
+
const schema: GeneratedJsonSchema = { writeOnly: true }
|
|
20
|
+
|
|
21
|
+
expect(schema.writeOnly).toBe(true)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('should accept tuple item schemas emitted by Pydantic', () => {
|
|
25
|
+
const schema: GeneratedJsonSchema = {
|
|
26
|
+
prefixItems: [{ type: 'string' }, { type: 'number' }],
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
expect(schema.prefixItems).toHaveLength(2)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('should accept exclusive numeric bounds emitted by Pydantic', () => {
|
|
33
|
+
const schema: GeneratedJsonSchema = { exclusiveMinimum: 0 }
|
|
34
|
+
|
|
35
|
+
expect(schema.exclusiveMinimum).toBe(0)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('should accept string patterns emitted by Pydantic', () => {
|
|
39
|
+
const schema: GeneratedJsonSchema = { pattern: '^[0-9a-f]{64}$' }
|
|
40
|
+
|
|
41
|
+
expect(schema.pattern).toBe('^[0-9a-f]{64}$')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('should accept discriminators emitted for Pydantic unions', () => {
|
|
45
|
+
const schema: GeneratedJsonSchema = {
|
|
46
|
+
discriminator: {
|
|
47
|
+
propertyName: 'kind',
|
|
48
|
+
mapping: { targeted: '#/$defs/TargetedResult' },
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
expect(schema.discriminator?.propertyName).toBe('kind')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('should accept exclusive union branches emitted by Pydantic', () => {
|
|
56
|
+
const schema: GeneratedJsonSchema = {
|
|
57
|
+
oneOf: [{ $ref: '#/$defs/TargetedResult' }],
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
expect(schema.oneOf).toHaveLength(1)
|
|
61
|
+
})
|
|
62
|
+
})
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/** Renders a native `<select>` element with size, error, and disabled states. */
|
|
3
|
-
import { computed } from 'vue'
|
|
3
|
+
import { computed, useAttrs } from 'vue'
|
|
4
|
+
import type { HTMLAttributes } from 'vue'
|
|
4
5
|
import type { SelectOption, SelectOptionInput } from '../types'
|
|
5
6
|
import { findOptionByDomValue, normalizeOptionInput } from '../utils/options'
|
|
6
7
|
|
|
8
|
+
defineOptions({ inheritAttrs: false })
|
|
9
|
+
|
|
7
10
|
interface Props {
|
|
8
11
|
modelValue?: string | number
|
|
9
12
|
options: SelectOptionInput<string | number>[]
|
|
@@ -19,6 +22,16 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
19
22
|
error: false,
|
|
20
23
|
size: 'md',
|
|
21
24
|
})
|
|
25
|
+
const attrs = useAttrs()
|
|
26
|
+
|
|
27
|
+
const controlAttrs = computed(() => {
|
|
28
|
+
const { class: _class, style: _style, ...nativeAttrs } = attrs
|
|
29
|
+
return nativeAttrs
|
|
30
|
+
})
|
|
31
|
+
const wrapperAttrs = computed<Pick<HTMLAttributes, 'class' | 'style'>>(() => ({
|
|
32
|
+
class: attrs.class as HTMLAttributes['class'],
|
|
33
|
+
style: attrs.style as HTMLAttributes['style'],
|
|
34
|
+
}))
|
|
22
35
|
|
|
23
36
|
const emit = defineEmits<{
|
|
24
37
|
'update:modelValue': [value: string | number]
|
|
@@ -36,8 +49,9 @@ function handleChange(event: Event) {
|
|
|
36
49
|
</script>
|
|
37
50
|
|
|
38
51
|
<template>
|
|
39
|
-
<div class="mint-select">
|
|
52
|
+
<div v-bind="wrapperAttrs" class="mint-select">
|
|
40
53
|
<select
|
|
54
|
+
v-bind="controlAttrs"
|
|
41
55
|
:value="modelValue"
|
|
42
56
|
:disabled="disabled"
|
|
43
57
|
:aria-invalid="error || undefined"
|
|
@@ -5,6 +5,8 @@ import Tooltip from './Tooltip.vue'
|
|
|
5
5
|
interface Props {
|
|
6
6
|
modelValue?: boolean
|
|
7
7
|
label?: string
|
|
8
|
+
/** Accessible switch name; defaults to the visual label, then "Toggle". */
|
|
9
|
+
ariaLabel?: string
|
|
8
10
|
description?: string
|
|
9
11
|
icon?: string | string[]
|
|
10
12
|
iconColor?: string
|
|
@@ -111,7 +113,7 @@ function iconStyle(): Record<string, string> {
|
|
|
111
113
|
role="switch"
|
|
112
114
|
:tabindex="disabled ? -1 : 0"
|
|
113
115
|
:aria-checked="modelValue"
|
|
114
|
-
:aria-label="label || 'Toggle'"
|
|
116
|
+
:aria-label="ariaLabel || label || 'Toggle'"
|
|
115
117
|
:class="[
|
|
116
118
|
'mint-toggle__track',
|
|
117
119
|
`mint-toggle__track--${size}`,
|