@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
|
@@ -28,7 +28,13 @@ import {
|
|
|
28
28
|
pluginEndpointRequestParts,
|
|
29
29
|
requestBodyFromPayload,
|
|
30
30
|
} from './pluginEndpointBuilder'
|
|
31
|
-
import type {
|
|
31
|
+
import type {
|
|
32
|
+
ExperimentSummary,
|
|
33
|
+
GeneratedJobDefinition,
|
|
34
|
+
PageSelectorItem,
|
|
35
|
+
SettingsModalSchema,
|
|
36
|
+
TopBarSettingsConfig,
|
|
37
|
+
} from '../types'
|
|
32
38
|
|
|
33
39
|
export {
|
|
34
40
|
buildPluginEndpointUrl,
|
|
@@ -39,8 +45,10 @@ export type PluginHttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete'
|
|
|
39
45
|
|
|
40
46
|
export interface PluginEndpointContract {
|
|
41
47
|
name: string
|
|
48
|
+
operationId?: string
|
|
42
49
|
method: PluginHttpMethod
|
|
43
50
|
path: string
|
|
51
|
+
requiresAuth?: boolean
|
|
44
52
|
pathParams?: PluginEndpointParamContract[]
|
|
45
53
|
queryParams?: PluginEndpointParamContract[]
|
|
46
54
|
requestType?: string | null
|
|
@@ -68,8 +76,16 @@ export interface PluginNavItemContract {
|
|
|
68
76
|
requiresFeature?: string
|
|
69
77
|
}
|
|
70
78
|
|
|
79
|
+
export interface PluginContractSettings {
|
|
80
|
+
modelName: string | null
|
|
81
|
+
/** Pydantic JSON Schema used to generate typed settings values. */
|
|
82
|
+
valueSchema: Record<string, unknown> | null
|
|
83
|
+
/** SDK SettingsModal schema used to render the settings form. */
|
|
84
|
+
formSchema: SettingsModalSchema | null
|
|
85
|
+
}
|
|
86
|
+
|
|
71
87
|
export interface PluginContract {
|
|
72
|
-
schemaVersion:
|
|
88
|
+
schemaVersion: 2
|
|
73
89
|
plugin: {
|
|
74
90
|
name?: string
|
|
75
91
|
packageName?: string
|
|
@@ -85,17 +101,17 @@ export interface PluginContract {
|
|
|
85
101
|
analysisResultReaders?: string[]
|
|
86
102
|
capabilities?: Record<string, unknown>
|
|
87
103
|
}
|
|
88
|
-
settings
|
|
89
|
-
modelName?: string | null
|
|
90
|
-
schema?: SettingsModalSchema | null
|
|
91
|
-
}
|
|
104
|
+
settings: PluginContractSettings
|
|
92
105
|
endpoints: PluginEndpointContract[]
|
|
106
|
+
schemas?: Record<string, Record<string, unknown>>
|
|
107
|
+
jobs?: GeneratedJobDefinition[]
|
|
93
108
|
hash: string
|
|
94
109
|
}
|
|
95
110
|
|
|
96
111
|
export interface PluginEndpointDefinition {
|
|
97
112
|
method: PluginHttpMethod
|
|
98
113
|
path: string
|
|
114
|
+
requiresAuth?: boolean
|
|
99
115
|
pathParams?: PluginEndpointParamDefinition[]
|
|
100
116
|
queryParams?: PluginEndpointParamDefinition[]
|
|
101
117
|
hasBody?: boolean
|
|
@@ -157,7 +173,7 @@ export interface UsePluginSettingsOptions {
|
|
|
157
173
|
apiBaseUrl?: string
|
|
158
174
|
/** Platform API base URL for /plugins/{name}/config. Defaults to the injected platform API URL. */
|
|
159
175
|
platformApiBaseUrl?: string
|
|
160
|
-
/** SettingsModal schema generated from the backend
|
|
176
|
+
/** SettingsModal schema generated from the backend plugin config model. */
|
|
161
177
|
schema?: SettingsModalSchema | null
|
|
162
178
|
/** SettingsModal title when using settingsConfig. */
|
|
163
179
|
title?: string
|
|
@@ -383,7 +399,7 @@ function pluginPageIdFromPath(path: string, fallbackIndex: number): string {
|
|
|
383
399
|
return normalizedPath.replace(/^\/+/, '').replace(/\/+/g, '-') || `page-${fallbackIndex + 1}`
|
|
384
400
|
}
|
|
385
401
|
|
|
386
|
-
/** Convert
|
|
402
|
+
/** Convert resolved @mint_plugin nav_items into page selectors for the topbar and homepage PluginCards. */
|
|
387
403
|
export function getPluginPageSelectorItems(contract: PluginContract): PageSelectorItem[] {
|
|
388
404
|
const pluginIcon = contract.plugin.icon ?? ''
|
|
389
405
|
const navItems = contract.plugin.navItems ?? []
|
|
@@ -413,7 +429,10 @@ export function createPluginClient<TClient extends object = GeneratedPluginClien
|
|
|
413
429
|
? explicitOptions
|
|
414
430
|
: (payloadOrOptions as PluginClientRequestOptions | undefined) ?? explicitOptions
|
|
415
431
|
const parts = pluginEndpointRequestParts(contract, endpoint, payload, options.baseUrl)
|
|
416
|
-
const api = useApi({
|
|
432
|
+
const api = useApi({
|
|
433
|
+
baseUrl: parts.baseUrl,
|
|
434
|
+
withAuth: endpoint.requiresAuth !== false,
|
|
435
|
+
})
|
|
417
436
|
const config = endpointRequestConfig(endpoint, parts.query, callOptions)
|
|
418
437
|
|
|
419
438
|
if (endpoint.method === 'get') {
|
|
@@ -456,7 +475,10 @@ export async function uploadPluginEndpoint<TResponse = unknown>(
|
|
|
456
475
|
}
|
|
457
476
|
|
|
458
477
|
const parts = pluginEndpointRequestParts(contract, endpoint, payload, options.baseUrl)
|
|
459
|
-
const api = useApi({
|
|
478
|
+
const api = useApi({
|
|
479
|
+
baseUrl: parts.baseUrl,
|
|
480
|
+
withAuth: endpoint.requiresAuth !== false,
|
|
481
|
+
})
|
|
460
482
|
const body = endpoint.hasBody
|
|
461
483
|
? requestBodyFromPayload(payload, parts.requestPayload, endpoint)
|
|
462
484
|
: payload
|
|
@@ -491,7 +513,10 @@ export async function downloadPluginEndpoint(
|
|
|
491
513
|
}
|
|
492
514
|
|
|
493
515
|
const parts = pluginEndpointRequestParts(contract, endpoint, payload, options.baseUrl)
|
|
494
|
-
const api = useApi({
|
|
516
|
+
const api = useApi({
|
|
517
|
+
baseUrl: parts.baseUrl,
|
|
518
|
+
withAuth: endpoint.requiresAuth !== false,
|
|
519
|
+
})
|
|
495
520
|
const config = {
|
|
496
521
|
...(endpointRequestConfig(endpoint, parts.query, { signal: options.signal }) ?? {}),
|
|
497
522
|
responseType: 'blob' as const,
|
|
@@ -528,8 +553,14 @@ function normalizePluginSettingsOptions(options: UsePluginSettingsOptions | stri
|
|
|
528
553
|
return typeof options === 'string' ? { pluginName: options } : options
|
|
529
554
|
}
|
|
530
555
|
|
|
556
|
+
const PLATFORM_OWNED_SETTINGS_KEY = 'allowed_experiment_types'
|
|
557
|
+
|
|
531
558
|
function cloneRecord(value: Record<string, unknown>): Record<string, unknown> {
|
|
532
|
-
return
|
|
559
|
+
return JSON.parse(JSON.stringify(value)) as Record<string, unknown>
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function hasOwnSetting(settings: Record<string, unknown>, key: string): boolean {
|
|
563
|
+
return Object.prototype.hasOwnProperty.call(settings, key)
|
|
533
564
|
}
|
|
534
565
|
|
|
535
566
|
function responseSettingsPayload(response: unknown): Record<string, unknown> {
|
|
@@ -547,6 +578,43 @@ function responseSettingsPayload(response: unknown): Record<string, unknown> {
|
|
|
547
578
|
return {}
|
|
548
579
|
}
|
|
549
580
|
|
|
581
|
+
function responseSettingsRevision(response: unknown): string | null {
|
|
582
|
+
if (!response || typeof response !== 'object') return null
|
|
583
|
+
const revision = (response as { revision?: unknown }).revision
|
|
584
|
+
return typeof revision === 'string' && revision ? revision : null
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function topLevelSettingsPatch(
|
|
588
|
+
current: Record<string, unknown>,
|
|
589
|
+
keys: Iterable<string>,
|
|
590
|
+
): Record<string, unknown> {
|
|
591
|
+
return Object.fromEntries(
|
|
592
|
+
[...keys].map(key => [
|
|
593
|
+
key,
|
|
594
|
+
hasOwnSetting(current, key) ? current[key] : null,
|
|
595
|
+
]),
|
|
596
|
+
)
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function changedTopLevelSettingKeys(
|
|
600
|
+
current: Record<string, unknown>,
|
|
601
|
+
saved: Record<string, unknown>,
|
|
602
|
+
): Set<string> {
|
|
603
|
+
return new Set(
|
|
604
|
+
[...new Set([...Object.keys(current), ...Object.keys(saved)])].filter(
|
|
605
|
+
key => JSON.stringify(current[key]) !== JSON.stringify(saved[key]),
|
|
606
|
+
),
|
|
607
|
+
)
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function platformOwnedSettingsPatch(settings: Record<string, unknown>): Record<string, unknown> {
|
|
611
|
+
return {
|
|
612
|
+
[PLATFORM_OWNED_SETTINGS_KEY]: hasOwnSetting(settings, PLATFORM_OWNED_SETTINGS_KEY)
|
|
613
|
+
? settings[PLATFORM_OWNED_SETTINGS_KEY]
|
|
614
|
+
: null,
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
550
618
|
/** Load, edit, and persist plugin settings from platform config or the plugin-local settings router. */
|
|
551
619
|
export function usePluginSettings<TSettings = Record<string, unknown>>(
|
|
552
620
|
options: UsePluginSettingsOptions | string = {},
|
|
@@ -557,8 +625,9 @@ export function usePluginSettings<TSettings = Record<string, unknown>>(
|
|
|
557
625
|
const platformApi = useApi({ baseUrl: resolvedOptions.platformApiBaseUrl ?? injectedContext?.platformApiUrl })
|
|
558
626
|
const pluginApi = useApi({ baseUrl: resolvedOptions.apiBaseUrl ?? injectedContext?.plugin?.api_prefix })
|
|
559
627
|
|
|
560
|
-
const values =
|
|
628
|
+
const values = ref({}) as Ref<PluginSettingsValues<TSettings>>
|
|
561
629
|
const savedValues = shallowRef<Record<string, unknown>>({})
|
|
630
|
+
const revision = ref<string | null>(null)
|
|
562
631
|
const request = useRequestSyncState('Plugin settings request failed.')
|
|
563
632
|
const isLoading = ref(false)
|
|
564
633
|
const isSaving = ref(false)
|
|
@@ -566,8 +635,17 @@ export function usePluginSettings<TSettings = Record<string, unknown>>(
|
|
|
566
635
|
const resolvedPluginName = computed(() =>
|
|
567
636
|
resolvedOptions.pluginName ?? plugin.value?.name ?? injectedContext?.plugin?.name ?? '',
|
|
568
637
|
)
|
|
638
|
+
const usesPluginSettingsApi = computed(() => {
|
|
639
|
+
const currentPlugin = injectedContext?.plugin ?? plugin.value
|
|
640
|
+
return currentPlugin?.settings_api === 'plugin'
|
|
641
|
+
&& resolvedPluginName.value === currentPlugin.name
|
|
642
|
+
})
|
|
569
643
|
const usesPlatformConfig = computed(() =>
|
|
570
|
-
Boolean(
|
|
644
|
+
Boolean(
|
|
645
|
+
(isIntegrated.value || injectedContext?.isIntegrated)
|
|
646
|
+
&& resolvedPluginName.value
|
|
647
|
+
&& !usesPluginSettingsApi.value,
|
|
648
|
+
),
|
|
571
649
|
)
|
|
572
650
|
const settings = computed(() => values.value)
|
|
573
651
|
const isDirty = computed(() => JSON.stringify(values.value) !== JSON.stringify(savedValues.value))
|
|
@@ -582,10 +660,11 @@ export function usePluginSettings<TSettings = Record<string, unknown>>(
|
|
|
582
660
|
values.value = cloneRecord(nextValues) as PluginSettingsValues<TSettings>
|
|
583
661
|
}
|
|
584
662
|
|
|
585
|
-
function applyLoaded(nextValues: Record<string, unknown
|
|
663
|
+
function applyLoaded(nextValues: Record<string, unknown>, nextRevision: string | null): void {
|
|
586
664
|
const cloned = cloneRecord(nextValues)
|
|
587
665
|
values.value = cloned as PluginSettingsValues<TSettings>
|
|
588
666
|
savedValues.value = cloneRecord(cloned)
|
|
667
|
+
revision.value = nextRevision
|
|
589
668
|
}
|
|
590
669
|
|
|
591
670
|
async function load(): Promise<void> {
|
|
@@ -602,7 +681,10 @@ export function usePluginSettings<TSettings = Record<string, unknown>>(
|
|
|
602
681
|
() => pluginApi.get<{ settings: Record<string, unknown>; mode?: string }>('/settings'),
|
|
603
682
|
{ success: 'load', errorMessage: 'Failed to load plugin settings' },
|
|
604
683
|
)
|
|
605
|
-
applyLoaded(
|
|
684
|
+
applyLoaded(
|
|
685
|
+
responseSettingsPayload(response),
|
|
686
|
+
responseSettingsRevision(response),
|
|
687
|
+
)
|
|
606
688
|
} catch {
|
|
607
689
|
// Error state is handled by request.run().
|
|
608
690
|
} finally {
|
|
@@ -618,19 +700,65 @@ export function usePluginSettings<TSettings = Record<string, unknown>>(
|
|
|
618
700
|
isSaving.value = true
|
|
619
701
|
try {
|
|
620
702
|
const payload = cloneRecord(values.value)
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
{ success: 'save', errorMessage: 'Failed to save plugin settings' },
|
|
703
|
+
let response: unknown
|
|
704
|
+
if (usesPlatformConfig.value) {
|
|
705
|
+
const dirtyKeys = changedTopLevelSettingKeys(payload, savedValues.value)
|
|
706
|
+
const platformOwnedChanged = dirtyKeys.has(PLATFORM_OWNED_SETTINGS_KEY)
|
|
707
|
+
const typedKeys = [...dirtyKeys].filter(
|
|
708
|
+
key => key !== PLATFORM_OWNED_SETTINGS_KEY,
|
|
628
709
|
)
|
|
629
|
-
|
|
630
|
-
|
|
710
|
+
let nextRevision = revision.value
|
|
711
|
+
|
|
712
|
+
if (typedKeys.length) {
|
|
713
|
+
response = await request.run(
|
|
714
|
+
() => platformApi.patch<{
|
|
715
|
+
plugin_name: string
|
|
716
|
+
config: Record<string, unknown>
|
|
717
|
+
revision?: string
|
|
718
|
+
}>(
|
|
719
|
+
`/plugins/${encodeURIComponent(resolvedPluginName.value)}/config`,
|
|
720
|
+
{ config: topLevelSettingsPatch(payload, typedKeys) },
|
|
721
|
+
),
|
|
722
|
+
{ success: 'save', errorMessage: 'Failed to save plugin settings' },
|
|
723
|
+
)
|
|
724
|
+
nextRevision = responseSettingsRevision(response) ?? nextRevision
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
if (platformOwnedChanged) {
|
|
728
|
+
response = await request.run(
|
|
729
|
+
() => platformApi.patch<{
|
|
730
|
+
plugin_name: string
|
|
731
|
+
config: Record<string, unknown>
|
|
732
|
+
revision?: string
|
|
733
|
+
}>(
|
|
734
|
+
`/plugins/${encodeURIComponent(resolvedPluginName.value)}/config`,
|
|
735
|
+
{ config: platformOwnedSettingsPatch(payload) },
|
|
736
|
+
),
|
|
737
|
+
{ success: 'save', errorMessage: 'Failed to save plugin settings' },
|
|
738
|
+
)
|
|
739
|
+
nextRevision = responseSettingsRevision(response) ?? nextRevision
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
if (!response) return true
|
|
743
|
+
applyLoaded(responseSettingsPayload(response), nextRevision)
|
|
744
|
+
return true
|
|
745
|
+
} else {
|
|
746
|
+
const ifMatch = revision.value
|
|
747
|
+
? { headers: { 'If-Match': `"${revision.value}"` } }
|
|
748
|
+
: undefined
|
|
749
|
+
response = await request.run(
|
|
750
|
+
() => pluginApi.put<{
|
|
751
|
+
settings: Record<string, unknown>
|
|
752
|
+
mode?: string
|
|
753
|
+
revision?: string
|
|
754
|
+
}>('/settings', payload, ifMatch),
|
|
631
755
|
{ success: 'save', errorMessage: 'Failed to save plugin settings' },
|
|
632
756
|
)
|
|
633
|
-
|
|
757
|
+
}
|
|
758
|
+
applyLoaded(
|
|
759
|
+
responseSettingsPayload(response),
|
|
760
|
+
responseSettingsRevision(response) ?? revision.value,
|
|
761
|
+
)
|
|
634
762
|
return true
|
|
635
763
|
} catch {
|
|
636
764
|
return false
|
|
@@ -668,7 +796,10 @@ export function usePluginSettings<TSettings = Record<string, unknown>>(
|
|
|
668
796
|
}
|
|
669
797
|
}
|
|
670
798
|
|
|
671
|
-
function streamRequestAuth(
|
|
799
|
+
function streamRequestAuth(
|
|
800
|
+
options: PluginEventStreamOptions<unknown>,
|
|
801
|
+
policy: { requiresAuth: boolean },
|
|
802
|
+
): {
|
|
672
803
|
headers: Headers
|
|
673
804
|
token?: string
|
|
674
805
|
} {
|
|
@@ -677,11 +808,11 @@ function streamRequestAuth(options: PluginEventStreamOptions<unknown>): {
|
|
|
677
808
|
if (!authStore.isInitialized) {
|
|
678
809
|
authStore.initialize()
|
|
679
810
|
}
|
|
680
|
-
if (authStore.token && !headers.has('Authorization')) {
|
|
811
|
+
if (policy.requiresAuth && authStore.token && !headers.has('Authorization')) {
|
|
681
812
|
headers.set('Authorization', `Bearer ${authStore.token}`)
|
|
682
813
|
}
|
|
683
814
|
headers.set('Accept', 'text/event-stream')
|
|
684
|
-
const token = authStore.token
|
|
815
|
+
const token = policy.requiresAuth ? authStore.token : undefined
|
|
685
816
|
return {
|
|
686
817
|
headers,
|
|
687
818
|
token: token && headers.get('Authorization') === `Bearer ${token}` ? token : undefined,
|
|
@@ -832,7 +963,10 @@ export function usePluginEventStream<TData = string>(
|
|
|
832
963
|
const url = buildPluginEndpointUrl(contract, endpoint, payload, {
|
|
833
964
|
baseUrl: options.baseUrl,
|
|
834
965
|
})
|
|
835
|
-
const requestAuth = streamRequestAuth(
|
|
966
|
+
const requestAuth = streamRequestAuth(
|
|
967
|
+
options as PluginEventStreamOptions<unknown>,
|
|
968
|
+
{ requiresAuth: endpoint.requiresAuth !== false },
|
|
969
|
+
)
|
|
836
970
|
const response = await fetch(url, {
|
|
837
971
|
method: 'GET',
|
|
838
972
|
headers: requestAuth.headers,
|
|
@@ -1,8 +1,57 @@
|
|
|
1
1
|
import { ref, computed, onMounted, type Ref, type ComputedRef } from 'vue'
|
|
2
2
|
import { useApi } from './useApi'
|
|
3
3
|
import { usePlatformContext } from './usePlatformContext'
|
|
4
|
+
import { getInjectedPlatformContext } from './platformContextHelpers'
|
|
4
5
|
import { useRequestSyncState } from './useRequestSyncState'
|
|
5
6
|
|
|
7
|
+
const PLATFORM_OWNED_SETTINGS_KEY = 'allowed_experiment_types'
|
|
8
|
+
|
|
9
|
+
function cloneConfig(config: Record<string, unknown>): Record<string, unknown> {
|
|
10
|
+
return JSON.parse(JSON.stringify(config)) as Record<string, unknown>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function hasOwnConfig(config: Record<string, unknown>, key: string): boolean {
|
|
14
|
+
return Object.prototype.hasOwnProperty.call(config, key)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function changedTopLevelKeys(
|
|
18
|
+
current: Record<string, unknown>,
|
|
19
|
+
saved: Record<string, unknown>,
|
|
20
|
+
): Set<string> {
|
|
21
|
+
return new Set(
|
|
22
|
+
[...new Set([...Object.keys(current), ...Object.keys(saved)])].filter(
|
|
23
|
+
key => JSON.stringify(current[key]) !== JSON.stringify(saved[key]),
|
|
24
|
+
),
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function topLevelConfigPatch(
|
|
29
|
+
current: Record<string, unknown>,
|
|
30
|
+
keys: Iterable<string>,
|
|
31
|
+
): Record<string, unknown> {
|
|
32
|
+
return Object.fromEntries(
|
|
33
|
+
[...keys].map(key => [
|
|
34
|
+
key,
|
|
35
|
+
hasOwnConfig(current, key) ? current[key] : null,
|
|
36
|
+
]),
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function platformOwnedConfigPatch(config: Record<string, unknown>): Record<string, unknown> {
|
|
41
|
+
return {
|
|
42
|
+
[PLATFORM_OWNED_SETTINGS_KEY]: hasOwnConfig(config, PLATFORM_OWNED_SETTINGS_KEY)
|
|
43
|
+
? config[PLATFORM_OWNED_SETTINGS_KEY]
|
|
44
|
+
: null,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function responseConfigPayload(response: {
|
|
49
|
+
config?: Record<string, unknown>
|
|
50
|
+
settings?: Record<string, unknown>
|
|
51
|
+
}): Record<string, unknown> {
|
|
52
|
+
return response.config ?? response.settings ?? {}
|
|
53
|
+
}
|
|
54
|
+
|
|
6
55
|
export interface UsePluginConfigReturn {
|
|
7
56
|
/** Editable plugin configuration values. */
|
|
8
57
|
config: Ref<Record<string, unknown>>
|
|
@@ -28,13 +77,25 @@ export interface UsePluginConfigReturn {
|
|
|
28
77
|
|
|
29
78
|
/** Loads, saves, and tracks dirty state for a plugin's persistent configuration via the platform API. */
|
|
30
79
|
export function usePluginConfig(pluginName?: string): UsePluginConfigReturn {
|
|
31
|
-
const
|
|
80
|
+
const injectedContext = getInjectedPlatformContext()
|
|
32
81
|
const { plugin } = usePlatformContext()
|
|
82
|
+
const platformApi = useApi()
|
|
83
|
+
const pluginApi = useApi({
|
|
84
|
+
baseUrl: injectedContext?.plugin?.api_prefix ?? plugin.value?.api_prefix,
|
|
85
|
+
})
|
|
33
86
|
|
|
34
|
-
const resolvedName = computed(
|
|
87
|
+
const resolvedName = computed(
|
|
88
|
+
() => pluginName ?? plugin.value?.name ?? injectedContext?.plugin?.name ?? '',
|
|
89
|
+
)
|
|
90
|
+
const usesPluginSettingsApi = computed(() => {
|
|
91
|
+
const currentPlugin = injectedContext?.plugin ?? plugin.value
|
|
92
|
+
return currentPlugin?.settings_api === 'plugin'
|
|
93
|
+
&& resolvedName.value === currentPlugin.name
|
|
94
|
+
})
|
|
35
95
|
|
|
36
96
|
const config = ref<Record<string, unknown>>({})
|
|
37
97
|
const savedConfig = ref<Record<string, unknown>>({})
|
|
98
|
+
const revision = ref<string | null>(null)
|
|
38
99
|
const request = useRequestSyncState('Plugin config request failed.')
|
|
39
100
|
const isLoading = ref(false)
|
|
40
101
|
const isSaving = ref(false)
|
|
@@ -52,14 +113,29 @@ export function usePluginConfig(pluginName?: string): UsePluginConfigReturn {
|
|
|
52
113
|
|
|
53
114
|
isLoading.value = true
|
|
54
115
|
try {
|
|
55
|
-
const response =
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
116
|
+
const response = usesPluginSettingsApi.value
|
|
117
|
+
? await request.run(
|
|
118
|
+
() => pluginApi.get<{
|
|
119
|
+
settings: Record<string, unknown>
|
|
120
|
+
mode?: string
|
|
121
|
+
revision?: string
|
|
122
|
+
}>('/settings'),
|
|
123
|
+
{ success: 'load', errorMessage: 'Failed to load plugin config' },
|
|
124
|
+
)
|
|
125
|
+
: await request.run(
|
|
126
|
+
() => platformApi.get<{
|
|
127
|
+
plugin_name: string
|
|
128
|
+
config: Record<string, unknown>
|
|
129
|
+
revision?: string
|
|
130
|
+
}>(
|
|
131
|
+
`/plugins/${encodeURIComponent(name)}/config`,
|
|
132
|
+
),
|
|
133
|
+
{ success: 'load', errorMessage: 'Failed to load plugin config' },
|
|
134
|
+
)
|
|
135
|
+
const responseConfig = responseConfigPayload(response)
|
|
136
|
+
config.value = cloneConfig(responseConfig)
|
|
137
|
+
savedConfig.value = cloneConfig(responseConfig)
|
|
138
|
+
revision.value = response.revision ?? null
|
|
63
139
|
} catch {
|
|
64
140
|
// Error state is handled by request.run().
|
|
65
141
|
} finally {
|
|
@@ -73,15 +149,75 @@ export function usePluginConfig(pluginName?: string): UsePluginConfigReturn {
|
|
|
73
149
|
|
|
74
150
|
isSaving.value = true
|
|
75
151
|
try {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
152
|
+
const payload = cloneConfig(config.value)
|
|
153
|
+
let response: {
|
|
154
|
+
plugin_name?: string
|
|
155
|
+
config?: Record<string, unknown>
|
|
156
|
+
settings?: Record<string, unknown>
|
|
157
|
+
mode?: string
|
|
158
|
+
revision?: string
|
|
159
|
+
} | undefined
|
|
160
|
+
let nextRevision = revision.value
|
|
161
|
+
|
|
162
|
+
if (usesPluginSettingsApi.value) {
|
|
163
|
+
response = await request.run(
|
|
164
|
+
() => pluginApi.put<{
|
|
165
|
+
settings: Record<string, unknown>
|
|
166
|
+
mode?: string
|
|
167
|
+
revision?: string
|
|
168
|
+
}>(
|
|
169
|
+
'/settings',
|
|
170
|
+
payload,
|
|
171
|
+
revision.value
|
|
172
|
+
? { headers: { 'If-Match': `"${revision.value}"` } }
|
|
173
|
+
: undefined,
|
|
174
|
+
),
|
|
175
|
+
{ success: 'save', errorMessage: 'Failed to save plugin config' },
|
|
176
|
+
)
|
|
177
|
+
nextRevision = response.revision ?? nextRevision
|
|
178
|
+
} else {
|
|
179
|
+
const dirtyKeys = changedTopLevelKeys(payload, savedConfig.value)
|
|
180
|
+
const platformOwnedChanged = dirtyKeys.has(PLATFORM_OWNED_SETTINGS_KEY)
|
|
181
|
+
const typedKeys = [...dirtyKeys].filter(
|
|
182
|
+
key => key !== PLATFORM_OWNED_SETTINGS_KEY,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
if (typedKeys.length) {
|
|
186
|
+
response = await request.run(
|
|
187
|
+
() => platformApi.patch<{
|
|
188
|
+
plugin_name: string
|
|
189
|
+
config: Record<string, unknown>
|
|
190
|
+
revision?: string
|
|
191
|
+
}>(
|
|
192
|
+
`/plugins/${encodeURIComponent(name)}/config`,
|
|
193
|
+
{ config: topLevelConfigPatch(payload, typedKeys) },
|
|
194
|
+
),
|
|
195
|
+
{ success: 'save', errorMessage: 'Failed to save plugin config' },
|
|
196
|
+
)
|
|
197
|
+
nextRevision = response.revision ?? nextRevision
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (platformOwnedChanged) {
|
|
201
|
+
response = await request.run(
|
|
202
|
+
() => platformApi.patch<{
|
|
203
|
+
plugin_name: string
|
|
204
|
+
config: Record<string, unknown>
|
|
205
|
+
revision?: string
|
|
206
|
+
}>(
|
|
207
|
+
`/plugins/${encodeURIComponent(name)}/config`,
|
|
208
|
+
{ config: platformOwnedConfigPatch(payload) },
|
|
209
|
+
),
|
|
210
|
+
{ success: 'save', errorMessage: 'Failed to save plugin config' },
|
|
211
|
+
)
|
|
212
|
+
nextRevision = response.revision ?? nextRevision
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!response) return true
|
|
217
|
+
const responseConfig = responseConfigPayload(response)
|
|
218
|
+
config.value = cloneConfig(responseConfig)
|
|
219
|
+
savedConfig.value = cloneConfig(responseConfig)
|
|
220
|
+
revision.value = response.revision ?? nextRevision
|
|
85
221
|
return true
|
|
86
222
|
} catch {
|
|
87
223
|
return false
|
|
@@ -91,7 +227,7 @@ export function usePluginConfig(pluginName?: string): UsePluginConfigReturn {
|
|
|
91
227
|
}
|
|
92
228
|
|
|
93
229
|
function reset(): void {
|
|
94
|
-
config.value =
|
|
230
|
+
config.value = cloneConfig(savedConfig.value)
|
|
95
231
|
request.clearError()
|
|
96
232
|
}
|
|
97
233
|
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
FormFieldSchema,
|
|
3
|
+
FormFieldType,
|
|
4
|
+
FormSchema,
|
|
5
|
+
} from '../types/form-builder'
|
|
6
|
+
import type {
|
|
7
|
+
GeneratedJsonSchema,
|
|
8
|
+
GeneratedJobDefinition,
|
|
9
|
+
} from '../types/generated-ui'
|
|
10
|
+
|
|
11
|
+
type GeneratedReference = 'path'
|
|
12
|
+
|
|
13
|
+
interface ResolvedGeneratedField {
|
|
14
|
+
schema: GeneratedJsonSchema
|
|
15
|
+
reference?: GeneratedReference
|
|
16
|
+
hasDefault: boolean
|
|
17
|
+
defaultValue?: unknown
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function generatedJobFormSchema(definition: GeneratedJobDefinition): FormSchema & { sections: NonNullable<FormSchema['sections']> } {
|
|
21
|
+
const root = definition.input_schema
|
|
22
|
+
const required = new Set(root.required ?? [])
|
|
23
|
+
const fields = Object.entries(root.properties ?? {}).map(([name, schema]) =>
|
|
24
|
+
toFormField(name, resolveGeneratedField(root, schema), required.has(name)),
|
|
25
|
+
)
|
|
26
|
+
return {
|
|
27
|
+
sections: [{
|
|
28
|
+
id: definition.id,
|
|
29
|
+
title: definition.title,
|
|
30
|
+
description: definition.description || undefined,
|
|
31
|
+
columns: 1,
|
|
32
|
+
fields,
|
|
33
|
+
}],
|
|
34
|
+
submitLabel: 'Run analysis',
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function generatedJobDefaults(definition: GeneratedJobDefinition): Record<string, unknown> {
|
|
39
|
+
return Object.fromEntries(
|
|
40
|
+
Object.entries(definition.input_schema.properties ?? {})
|
|
41
|
+
.filter(([, schema]) => hasOwnDefault(schema))
|
|
42
|
+
.map(([name, schema]) => [name, structuredClone(schema.default)]),
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function normalizeGeneratedJobInput(
|
|
47
|
+
definition: GeneratedJobDefinition,
|
|
48
|
+
values: Record<string, unknown>,
|
|
49
|
+
): Record<string, unknown> {
|
|
50
|
+
const normalized: Record<string, unknown> = {}
|
|
51
|
+
for (const [name, value] of Object.entries(values)) {
|
|
52
|
+
const field = definition.input_schema.properties?.[name]
|
|
53
|
+
if (!field) continue
|
|
54
|
+
normalized[name] = value
|
|
55
|
+
}
|
|
56
|
+
return normalized
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function toFormField(
|
|
60
|
+
name: string,
|
|
61
|
+
resolved: ResolvedGeneratedField,
|
|
62
|
+
required: boolean,
|
|
63
|
+
): FormFieldSchema {
|
|
64
|
+
const { schema, reference } = resolved
|
|
65
|
+
const validation: NonNullable<FormFieldSchema['validation']> = {}
|
|
66
|
+
if (required) validation.required = true
|
|
67
|
+
if (schema.minimum !== undefined) validation.min = schema.minimum
|
|
68
|
+
if (schema.maximum !== undefined) validation.max = schema.maximum
|
|
69
|
+
if (schema.minLength !== undefined) validation.minLength = schema.minLength
|
|
70
|
+
if (schema.maxLength !== undefined) validation.maxLength = schema.maxLength
|
|
71
|
+
|
|
72
|
+
const props: Record<string, unknown> = {}
|
|
73
|
+
if (schema.enum) props.options = [...schema.enum]
|
|
74
|
+
if (reference === 'path' && schema.type === 'array') props.multiple = true
|
|
75
|
+
return {
|
|
76
|
+
name,
|
|
77
|
+
label: schema.title || humanize(name),
|
|
78
|
+
type: fieldType(schema, reference),
|
|
79
|
+
defaultValue: resolved.hasDefault ? structuredClone(resolved.defaultValue) : undefined,
|
|
80
|
+
hint: schema.description,
|
|
81
|
+
validation,
|
|
82
|
+
props,
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function resolveGeneratedField(
|
|
87
|
+
root: GeneratedJsonSchema,
|
|
88
|
+
field: GeneratedJsonSchema,
|
|
89
|
+
): ResolvedGeneratedField {
|
|
90
|
+
const hasDefault = hasOwnDefault(field)
|
|
91
|
+
const candidate = field.anyOf?.find(item => item.type !== 'null')
|
|
92
|
+
?? field.allOf?.[0]
|
|
93
|
+
?? field
|
|
94
|
+
if (candidate.$ref) {
|
|
95
|
+
const referenceParts = candidate.$ref.split('/')
|
|
96
|
+
const key = referenceParts[referenceParts.length - 1]
|
|
97
|
+
const schema = key ? root.$defs?.[key] : undefined
|
|
98
|
+
if (!schema) throw new Error(`Generated field reference is missing: ${candidate.$ref}`)
|
|
99
|
+
return {
|
|
100
|
+
schema: { ...schema, title: field.title ?? schema.title },
|
|
101
|
+
reference: field['x-mint-component'] ?? schema['x-mint-component'],
|
|
102
|
+
hasDefault,
|
|
103
|
+
defaultValue: field.default,
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
schema: candidate,
|
|
108
|
+
reference: field['x-mint-component'] ?? candidate['x-mint-component'],
|
|
109
|
+
hasDefault,
|
|
110
|
+
defaultValue: field.default,
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function fieldType(schema: GeneratedJsonSchema, reference?: GeneratedReference): FormFieldType {
|
|
115
|
+
if (reference === 'path') return 'path'
|
|
116
|
+
if (schema.enum) return 'select'
|
|
117
|
+
const type = Array.isArray(schema.type) ? schema.type.find(value => value !== 'null') : schema.type
|
|
118
|
+
if (type === 'boolean') return 'toggle'
|
|
119
|
+
if (type === 'number' || type === 'integer') return 'number'
|
|
120
|
+
if (type === 'array') return 'tags'
|
|
121
|
+
return 'text'
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function hasOwnDefault(schema: GeneratedJsonSchema): boolean {
|
|
125
|
+
return Object.prototype.hasOwnProperty.call(schema, 'default')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function humanize(value: string): string {
|
|
129
|
+
return value.replace(/[_-]+/g, ' ').replace(/^\w/, character => character.toUpperCase())
|
|
130
|
+
}
|