@morscherlab/mint-sdk 1.1.0-beta.1 → 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.
Files changed (94) hide show
  1. package/README.md +18 -4
  2. package/dist/{EmptyState-D1c4RdC7.js → EmptyState-DgBRoYAy.js} +22 -13
  3. package/dist/EmptyState-DgBRoYAy.js.map +1 -0
  4. package/dist/{ExperimentSelectorModal-B7w6k3Y8.js → ExperimentSelectorModal-BBWXC7KW.js} +3 -3
  5. package/dist/{ExperimentSelectorModal-B7w6k3Y8.js.map → ExperimentSelectorModal-BBWXC7KW.js.map} +1 -1
  6. package/dist/{SettingsModal-DPDwCbXK.js → SettingsModal-Dpg-X1OM.js} +3 -3
  7. package/dist/{SettingsModal-DPDwCbXK.js.map → SettingsModal-Dpg-X1OM.js.map} +1 -1
  8. package/dist/__tests__/components/BaseToggle.test.d.ts +1 -0
  9. package/dist/__tests__/components/SectionCard.test.d.ts +1 -0
  10. package/dist/__tests__/composables/analysisArtifactTarget.test.d.ts +1 -0
  11. package/dist/__tests__/types/GeneratedJsonSchema.test.d.ts +1 -0
  12. package/dist/analysisArtifactTypes-ejqR8MX3.js.map +1 -1
  13. package/dist/components/BaseSelect.vue.d.ts +1 -1
  14. package/dist/components/BaseToggle.vue.d.ts +2 -0
  15. package/dist/components/FitPanel.vue.d.ts +1 -1
  16. package/dist/components/PluginWorkspaceView.props.d.ts +1 -1
  17. package/dist/components/SectionCard.vue.d.ts +47 -0
  18. package/dist/components/index.d.ts +1 -0
  19. package/dist/components/index.js +6 -6
  20. package/dist/{components-UfkPN1bK.js → components-Ci_BTx00.js} +711 -633
  21. package/dist/components-Ci_BTx00.js.map +1 -0
  22. package/dist/composables/analysisArtifactTarget.d.ts +5 -0
  23. package/dist/composables/index.d.ts +3 -1
  24. package/dist/composables/index.js +8 -8
  25. package/dist/composables/platformContextHelpers.d.ts +2 -0
  26. package/dist/composables/useAnalysisArtifacts.d.ts +3 -1
  27. package/dist/composables/useGeneratedAnalysis.d.ts +1 -1
  28. package/dist/composables/usePlatformContext.d.ts +3 -0
  29. package/dist/composables/usePluginClient.d.ts +11 -5
  30. package/dist/{composables-F4JFK9K1.js → composables-C0VEJ73O.js} +172 -30
  31. package/dist/composables-C0VEJ73O.js.map +1 -0
  32. package/dist/index.js +11 -11
  33. package/dist/install.js +5 -5
  34. package/dist/styles.css +312 -11
  35. package/dist/tailwind.preset.d.ts +5 -0
  36. package/dist/tailwind.preset.js +7 -2
  37. package/dist/tailwind.preset.js.map +1 -1
  38. package/dist/templates/index.js +3 -3
  39. package/dist/{templates-Cttbjbyc.js → templates-Cdj93S9C.js} +2 -2
  40. package/dist/{templates-Cttbjbyc.js.map → templates-Cdj93S9C.js.map} +1 -1
  41. package/dist/types/analysisArtifactTypes.d.ts +8 -0
  42. package/dist/types/components.d.ts +2 -0
  43. package/dist/types/generated-ui.d.ts +13 -0
  44. package/dist/types/index.d.ts +2 -2
  45. package/dist/types/platform.d.ts +2 -0
  46. package/dist/{useControlSchema-Dallso-L.js → useControlSchema-B_qgU4rW.js} +48 -19
  47. package/dist/useControlSchema-B_qgU4rW.js.map +1 -0
  48. package/dist/{useExperimentSelector-BMjzNcf2.js → useExperimentSelector-BuQZRgMt.js} +2 -2
  49. package/dist/{useExperimentSelector-BMjzNcf2.js.map → useExperimentSelector-BuQZRgMt.js.map} +1 -1
  50. package/dist/{useFormBuilder-BXaaDI41.js → useFormBuilder-D1G5J5Z5.js} +2 -2
  51. package/dist/{useFormBuilder-BXaaDI41.js.map → useFormBuilder-D1G5J5Z5.js.map} +1 -1
  52. package/dist/{useProtocolTemplates-BP7_4RGQ.js → useProtocolTemplates-DeaOmMaw.js} +4 -4
  53. package/dist/{useProtocolTemplates-BP7_4RGQ.js.map → useProtocolTemplates-DeaOmMaw.js.map} +1 -1
  54. package/package.json +1 -1
  55. package/src/__tests__/components/BaseSelect.test.ts +52 -0
  56. package/src/__tests__/components/BaseToggle.test.ts +40 -0
  57. package/src/__tests__/components/GeneratedPluginView.test.ts +10 -0
  58. package/src/__tests__/components/PluginWorkspaceView.test.ts +68 -14
  59. package/src/__tests__/components/SectionCard.test.ts +181 -0
  60. package/src/__tests__/composables/analysisArtifactTarget.test.ts +77 -0
  61. package/src/__tests__/composables/useAnalysisArtifacts.test.ts +74 -1
  62. package/src/__tests__/composables/useGeneratedAnalysis.test.ts +2 -0
  63. package/src/__tests__/composables/usePlatformContext.test.ts +28 -0
  64. package/src/__tests__/composables/usePluginClient.test.ts +365 -21
  65. package/src/__tests__/composables/usePluginConfig.test.ts +323 -7
  66. package/src/__tests__/types/GeneratedJsonSchema.test.ts +62 -0
  67. package/src/components/BaseSelect.vue +16 -2
  68. package/src/components/BaseToggle.vue +3 -1
  69. package/src/components/GeneratedPluginView.vue +1 -1
  70. package/src/components/PluginWorkspaceView.props.ts +1 -1
  71. package/src/components/PluginWorkspaceView.vue +1 -5
  72. package/src/components/SectionCard.story.vue +163 -0
  73. package/src/components/SectionCard.vue +98 -0
  74. package/src/components/index.ts +1 -0
  75. package/src/composables/analysisArtifactTarget.ts +53 -0
  76. package/src/composables/index.ts +6 -0
  77. package/src/composables/platformContextHelpers.ts +10 -3
  78. package/src/composables/useAnalysisArtifacts.ts +48 -0
  79. package/src/composables/useGeneratedAnalysis.ts +30 -12
  80. package/src/composables/usePluginClient.ts +130 -20
  81. package/src/composables/usePluginConfig.ts +156 -20
  82. package/src/styles/components/section-card.css +161 -0
  83. package/src/styles/index.css +1 -0
  84. package/src/styles/variables.css +25 -0
  85. package/src/tailwind.preset.ts +5 -0
  86. package/src/types/analysisArtifactTypes.ts +9 -0
  87. package/src/types/components.ts +12 -0
  88. package/src/types/generated-ui.ts +13 -0
  89. package/src/types/index.ts +3 -0
  90. package/src/types/platform.ts +2 -0
  91. package/dist/EmptyState-D1c4RdC7.js.map +0 -1
  92. package/dist/components-UfkPN1bK.js.map +0 -1
  93. package/dist/composables-F4JFK9K1.js.map +0 -1
  94. package/dist/useControlSchema-Dallso-L.js.map +0 -1
@@ -22,12 +22,37 @@ import { AuthenticationRequiredError } from '../../composables/useApi'
22
22
  import { useAuthStore } from '../../stores/auth'
23
23
 
24
24
  const contract: PluginContract = {
25
- schemaVersion: 1,
25
+ schemaVersion: 2,
26
26
  plugin: {
27
27
  routesPrefix: '/drp',
28
28
  apiPrefix: '/api/drp',
29
29
  type: 'analysis',
30
30
  },
31
+ settings: {
32
+ modelName: 'PluginSettings',
33
+ valueSchema: {
34
+ type: 'object',
35
+ properties: {
36
+ threshold: { type: 'number', default: 0.05 },
37
+ },
38
+ },
39
+ formSchema: {
40
+ groups: [
41
+ {
42
+ id: 'settings',
43
+ label: 'Settings',
44
+ fields: [
45
+ {
46
+ name: 'threshold',
47
+ label: 'Threshold',
48
+ type: 'number',
49
+ defaultValue: 0.05,
50
+ },
51
+ ],
52
+ },
53
+ ],
54
+ },
55
+ },
31
56
  endpoints: [],
32
57
  hash: 'sha256:test',
33
58
  }
@@ -687,7 +712,79 @@ describe('usePluginClient', () => {
687
712
  expect(requestConfigs).toHaveLength(0)
688
713
  })
689
714
 
690
- it('loads and saves plugin settings through the integrated platform config endpoint', async () => {
715
+ it.each([
716
+ 'platform',
717
+ undefined,
718
+ ] as const)(
719
+ 'should use the integrated platform config facade when settings_api is %s',
720
+ async (settingsApi) => {
721
+ ;(window as unknown as { __MINT_PLATFORM__?: unknown }).__MINT_PLATFORM__ = {
722
+ isIntegrated: true,
723
+ theme: 'light',
724
+ platformApiUrl: '/api',
725
+ plugin: {
726
+ id: 'drp',
727
+ name: 'drp',
728
+ version: '1.0.0',
729
+ route_prefix: '/drp',
730
+ api_prefix: '/api/drp',
731
+ ...(settingsApi ? { settings_api: settingsApi } : {}),
732
+ },
733
+ }
734
+ nextGetData = {
735
+ plugin_name: 'drp',
736
+ config: {
737
+ nasBasePath: '/data',
738
+ allowed_experiment_types: ['metabolomics'],
739
+ },
740
+ revision: 'revision-1',
741
+ }
742
+ nextPatchData = {
743
+ plugin_name: 'drp',
744
+ config: {
745
+ nasBasePath: '/mnt',
746
+ allowed_experiment_types: ['metabolomics'],
747
+ },
748
+ revision: 'revision-2',
749
+ }
750
+
751
+ const settings = usePluginSettings<{ nasBasePath?: string }>({ pluginName: 'drp' })
752
+ await settings.load()
753
+
754
+ expect(requestConfigs[0]!.baseURL).toBe('/api')
755
+ expect(requestConfigs[0]!.url).toBe('/plugins/drp/config')
756
+ expect(settings.values.value).toEqual({
757
+ nasBasePath: '/data',
758
+ allowed_experiment_types: ['metabolomics'],
759
+ })
760
+ expect(settings.settings.value).toEqual({
761
+ nasBasePath: '/data',
762
+ allowed_experiment_types: ['metabolomics'],
763
+ })
764
+ expect(settings.isDirty.value).toBe(false)
765
+
766
+ settings.setValues({
767
+ nasBasePath: '/mnt',
768
+ allowed_experiment_types: ['metabolomics'],
769
+ })
770
+ expect(settings.isDirty.value).toBe(true)
771
+ const ok = await settings.save()
772
+
773
+ expect(ok).toBe(true)
774
+ expect(requestConfigs[1]!.baseURL).toBe('/api')
775
+ expect(requestConfigs[1]!.url).toBe('/plugins/drp/config')
776
+ expect(requestMethods[1]).toBe('patch')
777
+ expect(requestConfigs[1]!.headers).toBeUndefined()
778
+ expect(requestBodies[0]).toEqual({ config: { nasBasePath: '/mnt' } })
779
+ expect(settings.values.value).toEqual({
780
+ nasBasePath: '/mnt',
781
+ allowed_experiment_types: ['metabolomics'],
782
+ })
783
+ expect(settings.isDirty.value).toBe(false)
784
+ },
785
+ )
786
+
787
+ it('should use the plugin settings router for an integrated plugin capability', async () => {
691
788
  ;(window as unknown as { __MINT_PLATFORM__?: unknown }).__MINT_PLATFORM__ = {
692
789
  isIntegrated: true,
693
790
  theme: 'light',
@@ -698,35 +795,281 @@ describe('usePluginClient', () => {
698
795
  version: '1.0.0',
699
796
  route_prefix: '/drp',
700
797
  api_prefix: '/api/drp',
798
+ settings_api: 'plugin',
701
799
  },
702
800
  }
703
- nextGetData = { plugin_name: 'drp', config: { nasBasePath: '/data' } }
704
- nextPatchData = { plugin_name: 'drp', config: { nasBasePath: '/mnt' } }
801
+ nextGetData = {
802
+ settings: { threshold: 1 },
803
+ mode: 'integrated',
804
+ revision: 'revision-1',
805
+ }
806
+ nextPutData = {
807
+ settings: { threshold: 2 },
808
+ mode: 'integrated',
809
+ revision: 'revision-2',
810
+ }
705
811
 
706
- const settings = usePluginSettings<{ nasBasePath?: string }>({ pluginName: 'drp' })
812
+ const settings = usePluginSettings<{ threshold?: number }>({ pluginName: 'drp' })
707
813
  await settings.load()
814
+ const saved = await settings.save({ threshold: 2 })
708
815
 
709
- expect(requestConfigs[0]!.baseURL).toBe('/api')
710
- expect(requestConfigs[0]!.url).toBe('/plugins/drp/config')
711
- expect(settings.values.value).toEqual({ nasBasePath: '/data' })
712
- expect(settings.settings.value).toEqual({ nasBasePath: '/data' })
713
- expect(settings.isDirty.value).toBe(false)
816
+ expect({
817
+ saved,
818
+ methods: requestMethods,
819
+ requests: requestConfigs.map(({ baseURL, url, headers }) => ({
820
+ baseURL,
821
+ url,
822
+ headers,
823
+ })),
824
+ bodies: requestBodies,
825
+ values: settings.values.value,
826
+ }).toEqual({
827
+ saved: true,
828
+ methods: ['get', 'put'],
829
+ requests: [
830
+ {
831
+ baseURL: '/api/drp',
832
+ url: '/settings',
833
+ headers: undefined,
834
+ },
835
+ {
836
+ baseURL: '/api/drp',
837
+ url: '/settings',
838
+ headers: { 'If-Match': '"revision-1"' },
839
+ },
840
+ ],
841
+ bodies: [{ threshold: 2 }],
842
+ values: { threshold: 2 },
843
+ })
844
+ })
714
845
 
715
- settings.setValues({ nasBasePath: '/mnt' })
716
- expect(settings.isDirty.value).toBe(true)
717
- const ok = await settings.save()
846
+ it('should preserve the contract v2 form schema in settingsConfig', () => {
847
+ const settings = usePluginSettings({
848
+ apiBaseUrl: '/api/drp',
849
+ loadOnMount: false,
850
+ schema: contract.settings.formSchema,
851
+ })
718
852
 
719
- expect(ok).toBe(true)
720
- expect(requestConfigs[1]!.baseURL).toBe('/api')
721
- expect(requestConfigs[1]!.url).toBe('/plugins/drp/config')
722
- expect(requestBodies[0]).toEqual({ config: { nasBasePath: '/mnt' } })
723
- expect(settings.values.value).toEqual({ nasBasePath: '/mnt' })
724
- expect(settings.isDirty.value).toBe(false)
853
+ expect(settings.settingsConfig.value.schema).toBe(contract.settings.formSchema)
854
+ })
855
+
856
+ it('should use the platform facade for an explicit different plugin name', async () => {
857
+ ;(window as unknown as { __MINT_PLATFORM__?: unknown }).__MINT_PLATFORM__ = {
858
+ isIntegrated: true,
859
+ theme: 'light',
860
+ platformApiUrl: '/api',
861
+ plugin: {
862
+ id: 'current-plugin',
863
+ name: 'current-plugin',
864
+ version: '1.0.0',
865
+ route_prefix: '/current-plugin',
866
+ api_prefix: '/api/current-plugin',
867
+ settings_api: 'plugin',
868
+ },
869
+ }
870
+ nextGetData = {
871
+ plugin_name: 'other-plugin',
872
+ config: { threshold: 1 },
873
+ revision: 'revision-1',
874
+ }
875
+
876
+ const settings = usePluginSettings<{ threshold?: number }>({
877
+ pluginName: 'other-plugin',
878
+ })
879
+ await settings.load()
880
+
881
+ expect({
882
+ method: requestMethods[0],
883
+ baseURL: requestConfigs[0]!.baseURL,
884
+ url: requestConfigs[0]!.url,
885
+ values: settings.values.value,
886
+ }).toEqual({
887
+ method: 'get',
888
+ baseURL: '/api',
889
+ url: '/plugins/other-plugin/config',
890
+ values: { threshold: 1 },
891
+ })
892
+ })
893
+
894
+ it('should split changed platform-owned settings from a versioned typed save', async () => {
895
+ ;(window as unknown as { __MINT_PLATFORM__?: unknown }).__MINT_PLATFORM__ = {
896
+ isIntegrated: true,
897
+ theme: 'light',
898
+ platformApiUrl: '/api',
899
+ plugin: {
900
+ id: 'drp',
901
+ name: 'drp',
902
+ version: '1.0.0',
903
+ route_prefix: '/drp',
904
+ api_prefix: '/api/drp',
905
+ },
906
+ }
907
+ nextGetData = {
908
+ plugin_name: 'drp',
909
+ config: {
910
+ threshold: 1,
911
+ allowed_experiment_types: ['metabolomics'],
912
+ },
913
+ revision: 'revision-1',
914
+ }
915
+ nextPatchData = {
916
+ plugin_name: 'drp',
917
+ config: {
918
+ threshold: 2,
919
+ allowed_experiment_types: ['proteomics'],
920
+ },
921
+ revision: 'untrusted-platform-revision',
922
+ }
923
+ const settings = usePluginSettings<{ threshold?: number }>({ pluginName: 'drp' })
924
+ await settings.load()
925
+ settings.setValues({
926
+ threshold: 2,
927
+ allowed_experiment_types: ['proteomics'],
928
+ })
929
+
930
+ const saved = await settings.save()
931
+ nextPatchData = {
932
+ plugin_name: 'drp',
933
+ config: {
934
+ threshold: 3,
935
+ allowed_experiment_types: ['proteomics'],
936
+ },
937
+ revision: 'revision-3',
938
+ }
939
+ settings.setValues({
940
+ threshold: 3,
941
+ allowed_experiment_types: ['proteomics'],
942
+ })
943
+ const savedAgain = await settings.save()
944
+
945
+ expect({
946
+ saved,
947
+ savedAgain,
948
+ methods: requestMethods,
949
+ bodies: requestBodies,
950
+ writeHeaders: requestConfigs.slice(1).map(config => config.headers),
951
+ values: settings.values.value,
952
+ isDirty: settings.isDirty.value,
953
+ }).toEqual({
954
+ saved: true,
955
+ savedAgain: true,
956
+ methods: ['get', 'patch', 'patch', 'patch'],
957
+ bodies: [
958
+ { config: { threshold: 2 } },
959
+ { config: { allowed_experiment_types: ['proteomics'] } },
960
+ { config: { threshold: 3 } },
961
+ ],
962
+ writeHeaders: [undefined, undefined, undefined],
963
+ values: {
964
+ threshold: 3,
965
+ allowed_experiment_types: ['proteomics'],
966
+ },
967
+ isDirty: false,
968
+ })
969
+ })
970
+
971
+ it('should avoid a typed PUT when only the platform-owned setting changes', async () => {
972
+ ;(window as unknown as { __MINT_PLATFORM__?: unknown }).__MINT_PLATFORM__ = {
973
+ isIntegrated: true,
974
+ theme: 'light',
975
+ platformApiUrl: '/api',
976
+ plugin: {
977
+ id: 'drp',
978
+ name: 'drp',
979
+ version: '1.0.0',
980
+ route_prefix: '/drp',
981
+ api_prefix: '/api/drp',
982
+ },
983
+ }
984
+ nextGetData = {
985
+ plugin_name: 'drp',
986
+ config: {
987
+ threshold: 1,
988
+ allowed_experiment_types: ['metabolomics'],
989
+ },
990
+ revision: 'revision-1',
991
+ }
992
+ nextPatchData = {
993
+ plugin_name: 'drp',
994
+ config: {
995
+ threshold: 1,
996
+ allowed_experiment_types: ['proteomics'],
997
+ },
998
+ }
999
+
1000
+ const settings = usePluginSettings<{ threshold?: number }>({ pluginName: 'drp' })
1001
+ await settings.load()
1002
+ settings.setValues({
1003
+ threshold: 1,
1004
+ allowed_experiment_types: ['proteomics'],
1005
+ })
1006
+
1007
+ const saved = await settings.save()
1008
+
1009
+ expect({
1010
+ saved,
1011
+ methods: requestMethods,
1012
+ bodies: requestBodies,
1013
+ values: settings.values.value,
1014
+ }).toEqual({
1015
+ saved: true,
1016
+ methods: ['get', 'patch'],
1017
+ bodies: [{ config: { allowed_experiment_types: ['proteomics'] } }],
1018
+ values: {
1019
+ threshold: 1,
1020
+ allowed_experiment_types: ['proteomics'],
1021
+ },
1022
+ })
1023
+ })
1024
+
1025
+ it('should detect nested settings edits and restore the saved JSON snapshot', async () => {
1026
+ nextGetData = {
1027
+ settings: {
1028
+ rules: {
1029
+ thresholds: [1, 2],
1030
+ },
1031
+ },
1032
+ mode: 'standalone',
1033
+ }
1034
+
1035
+ const settings = usePluginSettings<{
1036
+ rules?: { thresholds: number[] }
1037
+ }>({
1038
+ apiBaseUrl: '/api/drp',
1039
+ loadOnMount: false,
1040
+ })
1041
+ await settings.load()
1042
+ settings.values.value.rules!.thresholds[0] = 9
1043
+
1044
+ const dirtyAfterMutation = settings.isDirty.value
1045
+ settings.reset()
1046
+
1047
+ expect({
1048
+ dirtyAfterMutation,
1049
+ values: settings.values.value,
1050
+ isDirty: settings.isDirty.value,
1051
+ }).toEqual({
1052
+ dirtyAfterMutation: true,
1053
+ values: {
1054
+ rules: {
1055
+ thresholds: [1, 2],
1056
+ },
1057
+ },
1058
+ isDirty: false,
1059
+ })
725
1060
  })
726
1061
 
727
1062
  it('loads and saves plugin settings through the standalone settings router', async () => {
728
- nextGetData = { settings: { nas_base_path: '/data' }, mode: 'standalone' }
729
- nextPutData = { settings: { nas_base_path: '/mnt' }, mode: 'standalone' }
1063
+ nextGetData = {
1064
+ settings: { nas_base_path: '/data' },
1065
+ mode: 'standalone',
1066
+ revision: 'revision-1',
1067
+ }
1068
+ nextPutData = {
1069
+ settings: { nas_base_path: '/mnt' },
1070
+ mode: 'standalone',
1071
+ revision: 'revision-2',
1072
+ }
730
1073
 
731
1074
  const settings = usePluginSettings<{ nas_base_path?: string }>({
732
1075
  apiBaseUrl: '/api/drp',
@@ -740,6 +1083,7 @@ describe('usePluginClient', () => {
740
1083
  expect(requestConfigs[0]!.url).toBe('/settings')
741
1084
  expect(requestConfigs[1]!.baseURL).toBe('/api/drp')
742
1085
  expect(requestConfigs[1]!.url).toBe('/settings')
1086
+ expect(requestConfigs[1]!.headers).toEqual({ 'If-Match': '"revision-1"' })
743
1087
  expect(requestBodies[0]).toEqual({ nas_base_path: '/mnt' })
744
1088
  expect(settings.settingsConfig.value.values).toEqual({ nas_base_path: '/mnt' })
745
1089
  })