@morscherlab/mint-sdk 1.1.0-beta.1 → 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/dist/composables/index.js +1 -1
- package/dist/composables/usePlatformContext.d.ts +3 -0
- package/dist/{composables-F4JFK9K1.js → composables-B512bWJP.js} +133 -26
- package/dist/composables-B512bWJP.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/types/platform.d.ts +2 -0
- package/package.json +1 -1
- package/src/__tests__/composables/usePlatformContext.test.ts +28 -0
- package/src/__tests__/composables/usePluginClient.test.ts +329 -20
- package/src/__tests__/composables/usePluginConfig.test.ts +323 -7
- package/src/composables/usePluginClient.ts +118 -15
- package/src/composables/usePluginConfig.ts +156 -20
- package/src/types/platform.ts +2 -0
- package/dist/composables-F4JFK9K1.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')
|
|
@@ -546,8 +546,14 @@ function normalizePluginSettingsOptions(options: UsePluginSettingsOptions | stri
|
|
|
546
546
|
return typeof options === 'string' ? { pluginName: options } : options
|
|
547
547
|
}
|
|
548
548
|
|
|
549
|
+
const PLATFORM_OWNED_SETTINGS_KEY = 'allowed_experiment_types'
|
|
550
|
+
|
|
549
551
|
function cloneRecord(value: Record<string, unknown>): Record<string, unknown> {
|
|
550
|
-
return
|
|
552
|
+
return JSON.parse(JSON.stringify(value)) as Record<string, unknown>
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function hasOwnSetting(settings: Record<string, unknown>, key: string): boolean {
|
|
556
|
+
return Object.prototype.hasOwnProperty.call(settings, key)
|
|
551
557
|
}
|
|
552
558
|
|
|
553
559
|
function responseSettingsPayload(response: unknown): Record<string, unknown> {
|
|
@@ -565,6 +571,43 @@ function responseSettingsPayload(response: unknown): Record<string, unknown> {
|
|
|
565
571
|
return {}
|
|
566
572
|
}
|
|
567
573
|
|
|
574
|
+
function responseSettingsRevision(response: unknown): string | null {
|
|
575
|
+
if (!response || typeof response !== 'object') return null
|
|
576
|
+
const revision = (response as { revision?: unknown }).revision
|
|
577
|
+
return typeof revision === 'string' && revision ? revision : null
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function topLevelSettingsPatch(
|
|
581
|
+
current: Record<string, unknown>,
|
|
582
|
+
keys: Iterable<string>,
|
|
583
|
+
): Record<string, unknown> {
|
|
584
|
+
return Object.fromEntries(
|
|
585
|
+
[...keys].map(key => [
|
|
586
|
+
key,
|
|
587
|
+
hasOwnSetting(current, key) ? current[key] : null,
|
|
588
|
+
]),
|
|
589
|
+
)
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function changedTopLevelSettingKeys(
|
|
593
|
+
current: Record<string, unknown>,
|
|
594
|
+
saved: Record<string, unknown>,
|
|
595
|
+
): Set<string> {
|
|
596
|
+
return new Set(
|
|
597
|
+
[...new Set([...Object.keys(current), ...Object.keys(saved)])].filter(
|
|
598
|
+
key => JSON.stringify(current[key]) !== JSON.stringify(saved[key]),
|
|
599
|
+
),
|
|
600
|
+
)
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function platformOwnedSettingsPatch(settings: Record<string, unknown>): Record<string, unknown> {
|
|
604
|
+
return {
|
|
605
|
+
[PLATFORM_OWNED_SETTINGS_KEY]: hasOwnSetting(settings, PLATFORM_OWNED_SETTINGS_KEY)
|
|
606
|
+
? settings[PLATFORM_OWNED_SETTINGS_KEY]
|
|
607
|
+
: null,
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
568
611
|
/** Load, edit, and persist plugin settings from platform config or the plugin-local settings router. */
|
|
569
612
|
export function usePluginSettings<TSettings = Record<string, unknown>>(
|
|
570
613
|
options: UsePluginSettingsOptions | string = {},
|
|
@@ -575,8 +618,9 @@ export function usePluginSettings<TSettings = Record<string, unknown>>(
|
|
|
575
618
|
const platformApi = useApi({ baseUrl: resolvedOptions.platformApiBaseUrl ?? injectedContext?.platformApiUrl })
|
|
576
619
|
const pluginApi = useApi({ baseUrl: resolvedOptions.apiBaseUrl ?? injectedContext?.plugin?.api_prefix })
|
|
577
620
|
|
|
578
|
-
const values =
|
|
621
|
+
const values = ref({}) as Ref<PluginSettingsValues<TSettings>>
|
|
579
622
|
const savedValues = shallowRef<Record<string, unknown>>({})
|
|
623
|
+
const revision = ref<string | null>(null)
|
|
580
624
|
const request = useRequestSyncState('Plugin settings request failed.')
|
|
581
625
|
const isLoading = ref(false)
|
|
582
626
|
const isSaving = ref(false)
|
|
@@ -584,8 +628,17 @@ export function usePluginSettings<TSettings = Record<string, unknown>>(
|
|
|
584
628
|
const resolvedPluginName = computed(() =>
|
|
585
629
|
resolvedOptions.pluginName ?? plugin.value?.name ?? injectedContext?.plugin?.name ?? '',
|
|
586
630
|
)
|
|
631
|
+
const usesPluginSettingsApi = computed(() => {
|
|
632
|
+
const currentPlugin = injectedContext?.plugin ?? plugin.value
|
|
633
|
+
return currentPlugin?.settings_api === 'plugin'
|
|
634
|
+
&& resolvedPluginName.value === currentPlugin.name
|
|
635
|
+
})
|
|
587
636
|
const usesPlatformConfig = computed(() =>
|
|
588
|
-
Boolean(
|
|
637
|
+
Boolean(
|
|
638
|
+
(isIntegrated.value || injectedContext?.isIntegrated)
|
|
639
|
+
&& resolvedPluginName.value
|
|
640
|
+
&& !usesPluginSettingsApi.value,
|
|
641
|
+
),
|
|
589
642
|
)
|
|
590
643
|
const settings = computed(() => values.value)
|
|
591
644
|
const isDirty = computed(() => JSON.stringify(values.value) !== JSON.stringify(savedValues.value))
|
|
@@ -600,10 +653,11 @@ export function usePluginSettings<TSettings = Record<string, unknown>>(
|
|
|
600
653
|
values.value = cloneRecord(nextValues) as PluginSettingsValues<TSettings>
|
|
601
654
|
}
|
|
602
655
|
|
|
603
|
-
function applyLoaded(nextValues: Record<string, unknown
|
|
656
|
+
function applyLoaded(nextValues: Record<string, unknown>, nextRevision: string | null): void {
|
|
604
657
|
const cloned = cloneRecord(nextValues)
|
|
605
658
|
values.value = cloned as PluginSettingsValues<TSettings>
|
|
606
659
|
savedValues.value = cloneRecord(cloned)
|
|
660
|
+
revision.value = nextRevision
|
|
607
661
|
}
|
|
608
662
|
|
|
609
663
|
async function load(): Promise<void> {
|
|
@@ -620,7 +674,10 @@ export function usePluginSettings<TSettings = Record<string, unknown>>(
|
|
|
620
674
|
() => pluginApi.get<{ settings: Record<string, unknown>; mode?: string }>('/settings'),
|
|
621
675
|
{ success: 'load', errorMessage: 'Failed to load plugin settings' },
|
|
622
676
|
)
|
|
623
|
-
applyLoaded(
|
|
677
|
+
applyLoaded(
|
|
678
|
+
responseSettingsPayload(response),
|
|
679
|
+
responseSettingsRevision(response),
|
|
680
|
+
)
|
|
624
681
|
} catch {
|
|
625
682
|
// Error state is handled by request.run().
|
|
626
683
|
} finally {
|
|
@@ -636,19 +693,65 @@ export function usePluginSettings<TSettings = Record<string, unknown>>(
|
|
|
636
693
|
isSaving.value = true
|
|
637
694
|
try {
|
|
638
695
|
const payload = cloneRecord(values.value)
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
{ success: 'save', errorMessage: 'Failed to save plugin settings' },
|
|
696
|
+
let response: unknown
|
|
697
|
+
if (usesPlatformConfig.value) {
|
|
698
|
+
const dirtyKeys = changedTopLevelSettingKeys(payload, savedValues.value)
|
|
699
|
+
const platformOwnedChanged = dirtyKeys.has(PLATFORM_OWNED_SETTINGS_KEY)
|
|
700
|
+
const typedKeys = [...dirtyKeys].filter(
|
|
701
|
+
key => key !== PLATFORM_OWNED_SETTINGS_KEY,
|
|
646
702
|
)
|
|
647
|
-
|
|
648
|
-
|
|
703
|
+
let nextRevision = revision.value
|
|
704
|
+
|
|
705
|
+
if (typedKeys.length) {
|
|
706
|
+
response = await request.run(
|
|
707
|
+
() => platformApi.patch<{
|
|
708
|
+
plugin_name: string
|
|
709
|
+
config: Record<string, unknown>
|
|
710
|
+
revision?: string
|
|
711
|
+
}>(
|
|
712
|
+
`/plugins/${encodeURIComponent(resolvedPluginName.value)}/config`,
|
|
713
|
+
{ config: topLevelSettingsPatch(payload, typedKeys) },
|
|
714
|
+
),
|
|
715
|
+
{ success: 'save', errorMessage: 'Failed to save plugin settings' },
|
|
716
|
+
)
|
|
717
|
+
nextRevision = responseSettingsRevision(response) ?? nextRevision
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
if (platformOwnedChanged) {
|
|
721
|
+
response = await request.run(
|
|
722
|
+
() => platformApi.patch<{
|
|
723
|
+
plugin_name: string
|
|
724
|
+
config: Record<string, unknown>
|
|
725
|
+
revision?: string
|
|
726
|
+
}>(
|
|
727
|
+
`/plugins/${encodeURIComponent(resolvedPluginName.value)}/config`,
|
|
728
|
+
{ config: platformOwnedSettingsPatch(payload) },
|
|
729
|
+
),
|
|
730
|
+
{ success: 'save', errorMessage: 'Failed to save plugin settings' },
|
|
731
|
+
)
|
|
732
|
+
nextRevision = responseSettingsRevision(response) ?? nextRevision
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
if (!response) return true
|
|
736
|
+
applyLoaded(responseSettingsPayload(response), nextRevision)
|
|
737
|
+
return true
|
|
738
|
+
} else {
|
|
739
|
+
const ifMatch = revision.value
|
|
740
|
+
? { headers: { 'If-Match': `"${revision.value}"` } }
|
|
741
|
+
: undefined
|
|
742
|
+
response = await request.run(
|
|
743
|
+
() => pluginApi.put<{
|
|
744
|
+
settings: Record<string, unknown>
|
|
745
|
+
mode?: string
|
|
746
|
+
revision?: string
|
|
747
|
+
}>('/settings', payload, ifMatch),
|
|
649
748
|
{ success: 'save', errorMessage: 'Failed to save plugin settings' },
|
|
650
749
|
)
|
|
651
|
-
|
|
750
|
+
}
|
|
751
|
+
applyLoaded(
|
|
752
|
+
responseSettingsPayload(response),
|
|
753
|
+
responseSettingsRevision(response) ?? revision.value,
|
|
754
|
+
)
|
|
652
755
|
return true
|
|
653
756
|
} catch {
|
|
654
757
|
return false
|