@rancher/shell 3.0.5-rc.6 → 3.0.5-rc.8

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 (243) hide show
  1. package/assets/brand/classic/metadata.json +3 -0
  2. package/assets/styles/app.scss +1 -0
  3. package/assets/styles/base/_color.scss +16 -0
  4. package/assets/styles/base/_helpers.scss +10 -0
  5. package/assets/styles/base/_variables.scss +18 -12
  6. package/assets/styles/fonts/_icons.scss +1 -32
  7. package/assets/styles/global/_layout.scss +1 -1
  8. package/assets/styles/themes/_dark.scss +262 -258
  9. package/assets/styles/themes/_light.scss +538 -509
  10. package/assets/styles/themes/_modern.scss +914 -0
  11. package/assets/translations/en-us.yaml +110 -29
  12. package/chart/__tests__/S3.test.ts +2 -1
  13. package/cloud-credential/generic.vue +18 -10
  14. package/cloud-credential/harvester.vue +1 -9
  15. package/components/AdvancedSection.vue +8 -0
  16. package/components/ChartReadme.vue +17 -7
  17. package/components/CodeMirror.vue +1 -1
  18. package/components/Drawer/Chrome.vue +0 -1
  19. package/components/Drawer/ResourceDetailDrawer/__tests__/composables.test.ts +27 -28
  20. package/components/Drawer/ResourceDetailDrawer/composables.ts +4 -24
  21. package/components/Drawer/ResourceDetailDrawer/index.vue +18 -4
  22. package/components/InstallHelmCharts.vue +656 -0
  23. package/components/LazyImage.vue +60 -4
  24. package/components/Loading.vue +1 -1
  25. package/components/LocaleSelector.vue +7 -2
  26. package/components/Markdown.vue +4 -0
  27. package/components/PaginatedResourceTable.vue +46 -1
  28. package/components/PromptRestore.vue +22 -44
  29. package/components/Resource/Detail/Masthead/composable.ts +16 -0
  30. package/components/Resource/Detail/Masthead/index.vue +37 -0
  31. package/components/Resource/Detail/Metadata/IdentifyingInformation/composable.ts +10 -2
  32. package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +26 -7
  33. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +8 -1
  34. package/components/Resource/Detail/Metadata/KeyValue.vue +12 -10
  35. package/components/Resource/Detail/Metadata/Rectangle.vue +3 -1
  36. package/components/Resource/Detail/Metadata/__tests__/composables.test.ts +10 -17
  37. package/components/Resource/Detail/Metadata/composables.ts +9 -7
  38. package/components/Resource/Detail/Metadata/index.vue +17 -2
  39. package/components/Resource/Detail/Page.vue +35 -21
  40. package/components/Resource/Detail/SpacedRow.vue +1 -1
  41. package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +8 -9
  42. package/components/Resource/Detail/TitleBar/composables.ts +5 -5
  43. package/components/Resource/Detail/TitleBar/index.vue +12 -3
  44. package/components/ResourceDetail/Masthead/legacy.vue +1 -1
  45. package/components/ResourceDetail/index.vue +569 -72
  46. package/components/ResourceList/index.vue +1 -0
  47. package/components/ResourceTable.vue +6 -1
  48. package/components/ResourceYaml.vue +1 -1
  49. package/components/RichTranslation.vue +106 -0
  50. package/components/SlideInPanelManager.vue +13 -10
  51. package/components/SortableTable/index.vue +5 -5
  52. package/components/SortableTable/selection.js +0 -1
  53. package/components/Tabbed/index.vue +35 -4
  54. package/components/__tests__/LazyImage.spec.ts +121 -0
  55. package/components/__tests__/PromptRestore.test.ts +1 -65
  56. package/components/__tests__/RichTranslation.test.ts +115 -0
  57. package/components/fleet/FleetStatus.vue +4 -0
  58. package/components/fleet/dashboard/ResourcePanel.vue +2 -1
  59. package/components/form/ClusterAppearance.vue +5 -0
  60. package/components/form/FileImageSelector.vue +1 -1
  61. package/components/form/Members/ClusterPermissionsEditor.vue +1 -1
  62. package/components/form/NameNsDescription.vue +1 -0
  63. package/components/form/Networking.vue +24 -19
  64. package/components/form/ProjectMemberEditor.vue +1 -1
  65. package/components/form/ResourceLabeledSelect.vue +22 -8
  66. package/components/form/ResourceTabs/index.vue +20 -0
  67. package/components/form/SecretSelector.vue +9 -0
  68. package/components/form/SelectOrCreateAuthSecret.vue +6 -3
  69. package/components/form/__tests__/Networking.test.ts +116 -0
  70. package/components/form/labeled-select-utils/labeled-select-pagination.ts +3 -38
  71. package/components/formatter/FleetApplicationSource.vue +25 -17
  72. package/components/formatter/PodImages.vue +1 -1
  73. package/components/formatter/__tests__/LiveDate.test.ts +10 -2
  74. package/components/google/AccountAccess.vue +44 -46
  75. package/components/nav/Favorite.vue +4 -0
  76. package/components/nav/Group.vue +4 -1
  77. package/components/nav/NotificationCenter/Notification.vue +1 -27
  78. package/components/nav/WindowManager/index.vue +3 -3
  79. package/composables/resources.ts +2 -2
  80. package/config/labels-annotations.js +3 -2
  81. package/config/pagination-table-headers.js +8 -1
  82. package/config/product/explorer.js +27 -2
  83. package/config/product/manager.js +0 -1
  84. package/config/query-params.js +10 -0
  85. package/config/router/routes.js +21 -1
  86. package/config/system-namespaces.js +1 -1
  87. package/config/table-headers.js +30 -1
  88. package/config/types.js +1 -1
  89. package/config/version.js +1 -1
  90. package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +11 -0
  91. package/detail/__tests__/workload.test.ts +164 -0
  92. package/detail/configmap.vue +33 -75
  93. package/detail/projectsecret.vue +11 -0
  94. package/detail/provisioning.cattle.io.cluster.vue +351 -369
  95. package/detail/secret.vue +49 -308
  96. package/detail/workload/index.vue +38 -21
  97. package/dialog/InstallExtensionDialog.vue +8 -5
  98. package/dialog/RotateEncryptionKeyDialog.vue +10 -30
  99. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +224 -0
  100. package/edit/auth/ldap/__tests__/config.test.ts +14 -0
  101. package/edit/auth/ldap/config.vue +24 -0
  102. package/edit/compliance.cattle.io.clusterscan.vue +1 -1
  103. package/edit/configmap.vue +4 -1
  104. package/edit/fleet.cattle.io.gitrepo.vue +5 -6
  105. package/edit/fleet.cattle.io.helmop.vue +78 -56
  106. package/edit/logging.banzaicloud.io.output/index.vue +1 -1
  107. package/edit/logging.banzaicloud.io.output/providers/awsElasticsearch.vue +5 -6
  108. package/edit/networking.k8s.io.ingress/Certificate.vue +20 -22
  109. package/edit/networking.k8s.io.ingress/DefaultBackend.vue +8 -3
  110. package/edit/networking.k8s.io.ingress/Rule.vue +2 -5
  111. package/edit/networking.k8s.io.ingress/RulePath.vue +17 -11
  112. package/edit/networking.k8s.io.ingress/__tests__/Certificate.test.ts +165 -0
  113. package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +11 -10
  114. package/edit/networking.k8s.io.networkpolicy/PolicyRules.vue +1 -3
  115. package/edit/networking.k8s.io.networkpolicy/index.vue +17 -17
  116. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +3 -2
  117. package/edit/provisioning.cattle.io.cluster/rke2.vue +123 -61
  118. package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +9 -7
  119. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +22 -13
  120. package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +10 -12
  121. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +39 -38
  122. package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +41 -19
  123. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +16 -3
  124. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +32 -33
  125. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryMirrors.vue +9 -10
  126. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +1 -3
  127. package/edit/provisioning.cattle.io.cluster/tabs/upgrade/DrainOptions.vue +16 -9
  128. package/edit/secret/basic.vue +1 -0
  129. package/edit/secret/index.vue +126 -15
  130. package/edit/workload/index.vue +5 -14
  131. package/list/projectsecret.vue +345 -0
  132. package/list/provisioning.cattle.io.cluster.vue +1 -69
  133. package/list/secret.vue +109 -0
  134. package/machine-config/__tests__/vmwarevsphere.test.ts +5 -7
  135. package/machine-config/google.vue +9 -1
  136. package/machine-config/vmwarevsphere.vue +7 -17
  137. package/mixins/__tests__/brand.spec.ts +2 -2
  138. package/mixins/chart.js +0 -2
  139. package/mixins/create-edit-view/impl.js +10 -1
  140. package/mixins/resource-fetch-api-pagination.js +11 -12
  141. package/mixins/resource-fetch.js +3 -1
  142. package/models/__tests__/chart.test.ts +111 -80
  143. package/models/__tests__/fleet.cattle.io.helmop.test.ts +224 -0
  144. package/models/__tests__/node.test.ts +7 -63
  145. package/models/catalog.cattle.io.app.js +1 -1
  146. package/models/catalog.cattle.io.operation.js +1 -1
  147. package/models/chart.js +36 -20
  148. package/models/cloudcredential.js +2 -163
  149. package/models/cluster/node.js +7 -7
  150. package/models/cluster.x-k8s.io.machine.js +3 -3
  151. package/models/cluster.x-k8s.io.machinedeployment.js +11 -2
  152. package/models/compliance.cattle.io.clusterscan.js +2 -2
  153. package/models/configmap.js +4 -0
  154. package/models/constraints.gatekeeper.sh.constraint.js +1 -1
  155. package/models/fleet-application.js +0 -17
  156. package/models/fleet.cattle.io.cluster.js +2 -2
  157. package/models/fleet.cattle.io.gitrepo.js +15 -1
  158. package/models/fleet.cattle.io.helmop.js +26 -22
  159. package/models/management.cattle.io.setting.js +4 -0
  160. package/models/persistentvolumeclaim.js +1 -1
  161. package/models/pod.js +2 -2
  162. package/models/provisioning.cattle.io.cluster.js +39 -67
  163. package/models/rke.cattle.io.etcdsnapshot.js +1 -1
  164. package/models/secret.js +161 -2
  165. package/models/storage.k8s.io.storageclass.js +2 -2
  166. package/models/workload.js +3 -3
  167. package/package.json +11 -10
  168. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +1 -0
  169. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +4 -1
  170. package/pages/c/_cluster/apps/charts/__tests__/AppChartCardFooter.spec.js +41 -0
  171. package/pages/c/_cluster/apps/charts/chart.vue +422 -174
  172. package/pages/c/_cluster/apps/charts/index.vue +46 -35
  173. package/pages/c/_cluster/apps/charts/install.vue +1 -1
  174. package/pages/c/_cluster/explorer/projectsecret.vue +24 -0
  175. package/pages/c/_cluster/fleet/__tests__/index.test.ts +608 -314
  176. package/pages/c/_cluster/fleet/index.vue +103 -45
  177. package/pages/c/_cluster/manager/cloudCredential/index.vue +2 -59
  178. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +10 -3
  179. package/pages/c/_cluster/uiplugins/index.vue +36 -25
  180. package/plugins/dashboard-store/__tests__/normalize.test.ts +223 -0
  181. package/plugins/dashboard-store/__tests__/resource-class.test.ts +191 -0
  182. package/plugins/dashboard-store/__tests__/utils/normalize-usecases.ts +1526 -0
  183. package/plugins/dashboard-store/actions.js +42 -22
  184. package/plugins/dashboard-store/normalize.js +29 -17
  185. package/plugins/dashboard-store/resource-class.js +83 -17
  186. package/plugins/steve/__tests__/getters.test.ts +1 -1
  187. package/plugins/steve/__tests__/subscribe.spec.ts +259 -1
  188. package/plugins/steve/getters.js +8 -2
  189. package/plugins/steve/resourceWatcher.js +10 -3
  190. package/plugins/steve/steve-pagination-utils.ts +14 -3
  191. package/plugins/steve/subscribe.js +192 -19
  192. package/plugins/steve/worker/web-worker.advanced.js +2 -0
  193. package/rancher-components/Card/Card.vue +0 -18
  194. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.test.ts +15 -0
  195. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +65 -0
  196. package/rancher-components/Pill/RcStatusBadge/index.ts +2 -0
  197. package/rancher-components/Pill/RcStatusBadge/types.ts +5 -0
  198. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.test.ts +33 -0
  199. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +75 -0
  200. package/rancher-components/Pill/RcStatusIndicator/index.ts +2 -0
  201. package/rancher-components/Pill/RcStatusIndicator/types.ts +7 -0
  202. package/rancher-components/Pill/types.ts +2 -0
  203. package/rancher-components/RcButton/RcButton.vue +1 -1
  204. package/rancher-components/RcDropdown/RcDropdown.test.ts +98 -0
  205. package/rancher-components/RcDropdown/RcDropdown.vue +5 -0
  206. package/rancher-components/RcDropdown/RcDropdownItem.vue +7 -1
  207. package/rancher-components/RcDropdown/RcDropdownItemCheckbox.vue +2 -1
  208. package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +2 -1
  209. package/rancher-components/RcDropdown/useDropdownContext.ts +21 -0
  210. package/rancher-components/RcDropdown/useDropdownItem.ts +30 -1
  211. package/rancher-components/RcItemCard/RcItemCard.test.ts +20 -0
  212. package/rancher-components/RcItemCard/RcItemCard.vue +40 -6
  213. package/store/__tests__/catalog.test.ts +93 -1
  214. package/store/aws.js +19 -8
  215. package/store/catalog.js +8 -3
  216. package/types/kube/kube-api.ts +12 -0
  217. package/types/resources/settings.d.ts +1 -1
  218. package/types/shell/index.d.ts +643 -585
  219. package/types/store/pagination.types.ts +16 -6
  220. package/types/uiplugins.ts +73 -0
  221. package/utils/__tests__/back-off.test.ts +354 -0
  222. package/utils/__tests__/create-yaml.test.ts +235 -0
  223. package/utils/__tests__/kontainer.test.ts +19 -0
  224. package/utils/__tests__/uiplugins.test.ts +84 -0
  225. package/utils/back-off.ts +176 -0
  226. package/utils/create-yaml.js +103 -9
  227. package/utils/dynamic-importer.js +8 -0
  228. package/utils/kontainer.ts +3 -5
  229. package/utils/pagination-utils.ts +18 -0
  230. package/utils/style.ts +3 -0
  231. package/utils/uiplugins.ts +29 -2
  232. package/utils/validators/__tests__/setting.test.js +92 -0
  233. package/utils/validators/formRules/__tests__/index.test.ts +88 -7
  234. package/utils/validators/formRules/index.ts +83 -8
  235. package/utils/validators/setting.js +17 -0
  236. package/cloud-credential/__tests__/harvester.test.ts +0 -18
  237. package/components/ResourceDetail/__tests__/index.test.ts +0 -135
  238. package/components/ResourceDetail/legacy.vue +0 -562
  239. package/components/formatter/CloudCredExpired.vue +0 -69
  240. package/models/etcdbackup.js +0 -45
  241. package/pages/explorer/resource/detail/configmap.vue +0 -42
  242. package/pages/explorer/resource/detail/secret.vue +0 -50
  243. package/utils/aws.js +0 -0
@@ -488,3 +488,238 @@ __clone: true
488
488
  expect(actual).toStrictEqual(expected);
489
489
  });
490
490
  });
491
+
492
+ describe('fx: createYaml', () => {
493
+ interface CommentFieldsOption {
494
+ path: string;
495
+ key: string;
496
+ }
497
+
498
+ const schemas = [
499
+ {
500
+ id: 'provisioning.cattle.io.cluster',
501
+ resourceFields: {
502
+ apiVersion: {
503
+ type: 'string',
504
+ create: false,
505
+ update: false
506
+ },
507
+ kind: {
508
+ type: 'string',
509
+ create: false,
510
+ update: false
511
+ },
512
+ metadata: {
513
+ type: 'io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta',
514
+ create: true,
515
+ update: true
516
+ },
517
+ spec: {
518
+ type: 'provisioning.cattle.io.v1.cluster.spec',
519
+ nullable: true,
520
+ create: true,
521
+ update: true
522
+ }
523
+ }
524
+ },
525
+ {
526
+ id: 'io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta',
527
+ resourceFields: {
528
+ annotations: {
529
+ type: 'map[string]',
530
+ create: true,
531
+ update: true
532
+ },
533
+ labels: {
534
+ type: 'map[string]',
535
+ create: true,
536
+ update: true
537
+ },
538
+ namespace: {
539
+ type: 'string',
540
+ create: true,
541
+ update: true
542
+ }
543
+ }
544
+ },
545
+ {
546
+ id: 'provisioning.cattle.io.v1.cluster.spec',
547
+ resourceFields: {
548
+ localClusterAuthEndpoint: {
549
+ type: 'provisioning.cattle.io.v1.cluster.spec.localClusterAuthEndpoint',
550
+ nullable: true,
551
+ create: true,
552
+ update: true
553
+ },
554
+ rkeConfig: {
555
+ type: 'provisioning.cattle.io.v1.cluster.spec.rkeConfig',
556
+ nullable: true,
557
+ create: true,
558
+ update: true
559
+ },
560
+ foo: {
561
+ type: 'array[string]',
562
+ nullable: true,
563
+ create: true,
564
+ update: true
565
+ },
566
+ }
567
+ },
568
+ {
569
+ id: 'provisioning.cattle.io.v1.cluster.spec.localClusterAuthEndpoint',
570
+ resourceFields: {
571
+ caCerts: {
572
+ type: 'string',
573
+ nullable: true,
574
+ create: true,
575
+ update: true
576
+ },
577
+ enabled: {
578
+ type: 'boolean',
579
+ nullable: true,
580
+ create: true,
581
+ update: true
582
+ },
583
+ fqdn: {
584
+ type: 'string',
585
+ nullable: true,
586
+ create: true,
587
+ update: true
588
+ }
589
+ }
590
+ },
591
+ {
592
+ id: 'provisioning.cattle.io.v1.cluster.spec.rkeConfig',
593
+ resourceFields: {
594
+ additionalManifest: {
595
+ type: 'string',
596
+ nullable: true,
597
+ create: true,
598
+ update: true
599
+ },
600
+ chartValues: {
601
+ type: 'provisioning.cattle.io.v1.cluster.spec.rkeConfig.chartValues',
602
+ nullable: true,
603
+ create: true,
604
+ update: true
605
+ },
606
+ machineGlobalConfig: {
607
+ type: 'provisioning.cattle.io.v1.cluster.spec.rkeConfig.machineGlobalConfig',
608
+ nullable: true,
609
+ create: true,
610
+ update: true
611
+ }
612
+ }
613
+ },
614
+ {
615
+ id: 'provisioning.cattle.io.v1.cluster.spec.rkeConfig.chartValues',
616
+ resourceFields: {}
617
+ },
618
+ {
619
+ id: 'provisioning.cattle.io.v1.cluster.spec.rkeConfig.machineGlobalConfig',
620
+ resourceFields: {}
621
+ }
622
+ ];
623
+
624
+ it('should comment out fields when specific properties are defined on the model with commentFieldsOptions', () => {
625
+ const obj = {
626
+ type: 'provisioning.cattle.io.cluster',
627
+ metadata: {
628
+ namespace: 'fleet-default',
629
+ annotations: { someannotation: 'test' },
630
+ labels: {}
631
+ },
632
+ __clone: true,
633
+ spec: {
634
+ localClusterAuthEndpoint: {
635
+ caCerts: '',
636
+ enabled: false,
637
+ fqdn: ''
638
+ },
639
+ rkeConfig: {
640
+ machineGlobalConfig: {
641
+ cni: 'calico',
642
+ 'disable-kube-proxy': false,
643
+ 'etcd-expose-metrics': false,
644
+ profile: null
645
+ },
646
+ chartValues: {}
647
+ },
648
+ foo: [
649
+ 'bar',
650
+ 'bar2'
651
+ ],
652
+ },
653
+ apiVersion: 'provisioning.cattle.io/v1',
654
+ kind: 'Cluster'
655
+ };
656
+
657
+ const objResult = {
658
+ metadata: {
659
+ namespace: 'fleet-default',
660
+ annotations: { someannotation: 'test' },
661
+ labels: {}
662
+ },
663
+ __clone: true,
664
+ spec: {
665
+ rkeConfig: {
666
+ machineGlobalConfig: {
667
+ cni: 'calico',
668
+ 'disable-kube-proxy': false,
669
+ 'etcd-expose-metrics': false,
670
+ },
671
+ chartValues: {}
672
+ },
673
+ },
674
+ apiVersion: 'provisioning.cattle.io/v1',
675
+ kind: 'Cluster'
676
+ };
677
+
678
+ const type = 'provisioning.cattle.io.cluster';
679
+ const commentFieldsOptions: CommentFieldsOption[] = [
680
+ { path: 'spec.rkeConfig.machineGlobalConfig', key: 'profile' },
681
+ { path: 'spec', key: 'localClusterAuthEndpoint' },
682
+ { path: 'spec', key: 'foo' },
683
+ ];
684
+
685
+ // Define the expected YAML output as a string, adjusted for correct spacing
686
+ const expectedYaml = `
687
+ apiVersion: provisioning.cattle.io/v1
688
+ kind: Cluster
689
+ metadata:
690
+ annotations:
691
+ someannotation: test
692
+ # key: string
693
+ labels:
694
+ {}
695
+ # key: string
696
+ namespace: fleet-default
697
+ spec:
698
+ # localClusterAuthEndpoint:
699
+ # caCerts: ''
700
+ # enabled: false
701
+ # fqdn: ''
702
+ rkeConfig:
703
+ chartValues:
704
+ {}
705
+ machineGlobalConfig:
706
+ cni: calico
707
+ disable-kube-proxy: false
708
+ etcd-expose-metrics: false
709
+ # profile: null
710
+ # additionalManifest: string
711
+ # foo:
712
+ # - bar
713
+ # - bar2
714
+ # - string
715
+ __clone: true`.trim();
716
+
717
+ const result = createYaml(schemas, type, obj, true, 0, '', null, {}, commentFieldsOptions as any);
718
+
719
+ // Check if result is a valid YAML
720
+ expect(jsyaml.load(result.trim())).toStrictEqual(objResult);
721
+
722
+ // Check if properties are commented out
723
+ expect(result.trim()).toStrictEqual(expectedYaml);
724
+ });
725
+ });
@@ -17,6 +17,25 @@ describe('fx: diffUpstreamSpec', () => {
17
17
  expect(diffUpstreamSpec(upstream, local)).toStrictEqual(diff);
18
18
  });
19
19
 
20
+ it.each([
21
+ [{
22
+ a: { one: 'a' },
23
+ b: ['1', 'b'],
24
+ c: 'c'
25
+ }, {
26
+ a: {},
27
+ b: [],
28
+ c: ''
29
+ }, {
30
+ a: {},
31
+ b: [],
32
+ c: ''
33
+ }],
34
+ [{ a: 'a' }, { b: null }, { }],
35
+ ])('should include fields that are empty objects, arrays or strings on the local object regardless of upstream definition', (upstream, local, diff) => {
36
+ expect(diffUpstreamSpec(upstream, local)).toStrictEqual(diff);
37
+ });
38
+
20
39
  it.each([
21
40
  [{ a: null }, { a: {} }, {}],
22
41
  [{ a: null }, { a: [] }, {}],
@@ -0,0 +1,84 @@
1
+ import type { Plugin, Version } from '@shell/types/uiplugins';
2
+ import { getPluginChartVersion, getPluginChartVersionLabel } from '../uiplugins';
3
+
4
+ function makePlugin(partial: Partial<Plugin>): Plugin {
5
+ return {
6
+ name: 'test',
7
+ label: 'Test',
8
+ description: '',
9
+ id: 'test',
10
+ versions: [],
11
+ installed: false,
12
+ builtin: false,
13
+ experimental: false,
14
+ certified: false,
15
+ chart: {} as any,
16
+ incompatibilityMessage: '',
17
+ installableVersions: [],
18
+ displayVersion: '',
19
+ pluginVersionLabel: '',
20
+ helmError: false,
21
+ ...partial
22
+ };
23
+ }
24
+
25
+ describe('fx: getPluginChartVersion', () => {
26
+ it('returns the Helm Chart version when displayVersion matches appVersion', () => {
27
+ const plugin = makePlugin({
28
+ displayVersion: '1.2.3',
29
+ versions: [
30
+ { appVersion: '1.2.3', version: '4.5.6' } as Version,
31
+ { appVersion: '2.0.0', version: '5.0.0' } as Version
32
+ ]
33
+ });
34
+
35
+ expect(getPluginChartVersion(plugin)).toBe('4.5.6');
36
+ });
37
+
38
+ it('falls back to version if appVersion is not present', () => {
39
+ const plugin = makePlugin({
40
+ displayVersion: '7.8.9',
41
+ versions: [
42
+ { version: '7.8.9' } as Version,
43
+ { version: '8.0.0' } as Version
44
+ ]
45
+ });
46
+
47
+ expect(getPluginChartVersion(plugin)).toBe('7.8.9');
48
+ });
49
+
50
+ it('returns displayVersion if no matching chart is found', () => {
51
+ const plugin = makePlugin({
52
+ displayVersion: '9.9.9',
53
+ versions: [
54
+ { appVersion: '1.0.0', version: '1.0.0' } as Version,
55
+ { appVersion: '2.0.0', version: '2.0.0' } as Version
56
+ ]
57
+ });
58
+
59
+ expect(getPluginChartVersion(plugin)).toBe('9.9.9');
60
+ });
61
+
62
+ it('returns undefined if plugin is undefined', () => {
63
+ expect(getPluginChartVersion(undefined)).toBeUndefined();
64
+ });
65
+
66
+ it('returns displayVersion if plugin.versions is undefined', () => {
67
+ const plugin = makePlugin({ displayVersion: '1.0.0', versions: undefined as any });
68
+
69
+ expect(getPluginChartVersion(plugin)).toBe('1.0.0');
70
+ });
71
+ });
72
+
73
+ describe('fx: getPluginChartVersionLabel', () => {
74
+ it('returns simple version string if Helm Chart Version appVersion matches version', () => {
75
+ const version = { version: '1.2.3', appVersion: '1.2.3' } as Version;
76
+
77
+ expect(getPluginChartVersionLabel(version)).toBe('1.2.3');
78
+ });
79
+ it(`if version and appVersion don't match, it returns 'appVersion (version)' string`, () => {
80
+ const version = { version: '1.2.3', appVersion: '4.5.6' } as Version;
81
+
82
+ expect(getPluginChartVersionLabel(version)).toBe('4.5.6 (1.2.3)');
83
+ });
84
+ });
@@ -0,0 +1,176 @@
1
+ type BackOffEntry = {
2
+ timeoutId?: NodeJS.Timeout,
3
+ try: number,
4
+ retries: number,
5
+ description: string,
6
+ metadata: any,
7
+ }
8
+
9
+ /**
10
+ * Helper class which handles backing off making the supplied request
11
+ *
12
+ * see `execute` for more info
13
+ */
14
+ class BackOff {
15
+ private map: {
16
+ [id: string]: BackOffEntry
17
+ } = {};
18
+
19
+ private log(level: 'error' | 'info' | 'debug', id: string, classDescription: string, description: string, ...args: any[]) {
20
+ console[level](`BackOff... Id: "${ id }". Description: "${ description }"\nStatus: ${ classDescription }\n`, ...args); // eslint-disable-line no-console
21
+ }
22
+
23
+ /**
24
+ * Get a specific back off process
25
+ */
26
+ getBackOff(id: string): BackOffEntry {
27
+ return this.map[id];
28
+ }
29
+
30
+ /**
31
+ * Stop ALL back off processes started since the ui was loaded
32
+ */
33
+ resetAll() {
34
+ Object.keys(this.map).forEach((id) => {
35
+ this.reset(id);
36
+ });
37
+ }
38
+
39
+ /**
40
+ * Stop all back off process with a specific prefix
41
+ */
42
+ resetPrefix(prefix:string) {
43
+ Object.keys(this.map).forEach((id) => {
44
+ if (id.startsWith(prefix)) {
45
+ this.reset(id);
46
+ }
47
+ });
48
+ }
49
+
50
+ /**
51
+ * Stop a back off process with a specific id
52
+ */
53
+ reset(id: string) {
54
+ const backOff: BackOffEntry = this.map[id];
55
+
56
+ if (backOff) {
57
+ if (backOff?.timeoutId) {
58
+ this.log('info', id, 'Stopping (cancelling active back-off)', backOff.description);
59
+
60
+ clearTimeout(backOff.timeoutId);
61
+ }
62
+ this.log('debug', id, 'Reset', backOff.description);
63
+
64
+ delete this.map[id];
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Call a function, but if it's recently been called delay execution aka back off
70
+ *
71
+ * This can be used in a totally disjoined asynchronous way
72
+ *
73
+ * 1. Request function A to be run
74
+ * 2. Entirely separate process requests function A to be run again
75
+ * 3. Back off process waits Xms and then runs function A again
76
+ * 4. Repeat steps 2 and 3, with an exponential increasing delay
77
+ *
78
+ * This can be called repeatedly, if the previous delay is still running new requests will be ignored
79
+ */
80
+ async execute<T = any>({
81
+ id, description, retries = 10, delayedFn, canFn = async() => true, metadata
82
+ }: {
83
+ /**
84
+ * Unique id for the execution of this function.
85
+ *
86
+ * This will be used to delay further executions, and also to cancel it
87
+ */
88
+ id: string,
89
+ /**
90
+ * Basic text description to use in logging
91
+ */
92
+ description: string,
93
+ /**
94
+ * Number of executions allowed before flatly refusing to call more. Defaults to 10
95
+ */
96
+ retries?: number,
97
+ /**
98
+ * Before calling delayedFn check if it can still run
99
+ *
100
+ * Useful for checking state after a looong delay
101
+ */
102
+ canFn?: () => Promise<boolean>,
103
+ /**
104
+ * Call this function
105
+ * - if it's not already waiting to run
106
+ * - if it's passed canFn
107
+ * - if it hasn't been tried over `retries` amount
108
+ *
109
+ * The function will be increasingly (exponentially) delayed if it has previously been called
110
+ */
111
+ delayedFn: () => Promise<any>,
112
+ /**
113
+ * Anything that might be important outside of this file (used with `getBackOff`)
114
+ */
115
+ metadata?: T,
116
+ }): Promise<NodeJS.Timeout | undefined> {
117
+ const backOff: BackOffEntry = this.map[id];
118
+
119
+ const cont = await canFn();
120
+
121
+ if (!cont) {
122
+ this.log('info', id, 'Skipping (can execute fn test failed)', description);
123
+
124
+ return undefined;
125
+ } else if (backOff?.timeoutId) {
126
+ this.log('info', id, 'Skipping (previous back off process still running)', description);
127
+
128
+ return backOff.timeoutId;
129
+ } else {
130
+ const backOffTry = backOff?.try || 0;
131
+
132
+ if (backOffTry + 1 > retries) {
133
+ this.log('error', id, 'Aborting (too many retries)', description);
134
+
135
+ return undefined;
136
+ }
137
+
138
+ // First step is immediate (0.001s)
139
+ // Second and others are exponential
140
+ // 1, 2, 3, 4, 5, 6, 7, 8, 9
141
+ // 1, 4, 9, 16, 25, 36, 49, 64, 81
142
+ // 0.25s, 1s, 2.25s, 4s, 6.25s, 9s, 12.25s, 16s, 20.25s
143
+ const delay = backOffTry === 0 ? 1 : Math.pow(backOffTry, 2) * 250;
144
+
145
+ this.log('info', id, `Delaying call (attempt ${ backOffTry + 1 }, delayed by ${ delay }ms)`, description);
146
+
147
+ const timeout = setTimeout(async() => {
148
+ try {
149
+ this.log('info', id, `Executing call`, description);
150
+
151
+ await delayedFn();
152
+ } catch (e) {
153
+ // Error occurred. Don't clear the map. Next time this is called we'll back off before trying ...
154
+ this.log('error', id, 'Failed call', description, e);
155
+ }
156
+
157
+ // Unblock future calls
158
+ delete this.map[id]?.timeoutId;
159
+ }, delay);
160
+
161
+ this.map[id] = {
162
+ timeoutId: timeout,
163
+ try: backOff?.try ? backOff.try + 1 : 1,
164
+ retries,
165
+ description,
166
+ metadata
167
+ };
168
+
169
+ return timeout;
170
+ }
171
+ }
172
+ }
173
+
174
+ const backOff = new BackOff();
175
+
176
+ export default backOff;
@@ -70,13 +70,14 @@ export const ACTIVELY_REMOVE = [
70
70
 
71
71
  const INDENT = 2;
72
72
 
73
- export function createYamlWithOptions(schemas, type, data, options) {
73
+ export function createYamlWithOptions(schemas, type, data, options, commentFieldsOptions) {
74
74
  return createYaml(
75
75
  schemas,
76
76
  type,
77
77
  data,
78
78
  true, 0, '', null,
79
- options
79
+ options,
80
+ commentFieldsOptions
80
81
  );
81
82
  }
82
83
 
@@ -89,6 +90,7 @@ export function createYaml(
89
90
  path = '',
90
91
  rootType = null,
91
92
  dataOptions = {},
93
+ commentFieldsOptions = null
92
94
  ) {
93
95
  data = data || {};
94
96
 
@@ -133,6 +135,10 @@ export function createYaml(
133
135
 
134
136
  const regularFields = [];
135
137
 
138
+ if ( !commentFieldsOptions ) {
139
+ commentFieldsOptions = data?.commentFieldsOptions;
140
+ }
141
+
136
142
  if (processAlwaysAdd) {
137
143
  // Add all the parents of each key so that spec.template.foo.blah
138
144
  // causes 'spec', 'template' and 'foo' keys to be created
@@ -230,6 +236,18 @@ export function createYaml(
230
236
  out = 'type:';
231
237
  }
232
238
 
239
+ commentFieldsOptions = addCommentSubFieldsOptions(commentFieldsOptions, data, path, key);
240
+
241
+ // If commentFieldOptions is defined on the model and the currentPath matches the path and key
242
+ // defined in one of the options, then comment out that line.
243
+ if ( Array.isArray(commentFieldsOptions) && commentFieldsOptions.length ) {
244
+ const currentPath = path ? `${ path }.${ key }` : key;
245
+
246
+ if ( commentFieldsOptions.some((option) => `${ option.path }.${ option.key }` === currentPath) ) {
247
+ out = `#${ out }`;
248
+ }
249
+ }
250
+
233
251
  // if a key on data is not listed in the schema's resourceFields, just convert it to yaml, add indents where needed, and return
234
252
  if ( !field ) {
235
253
  if (data[key]) {
@@ -275,7 +293,7 @@ export function createYaml(
275
293
  out += `# key: ${ mapOf }`;
276
294
  } else {
277
295
  // If not a simple type ie some sort of object/array, recursively build out commented fields (note data = null here) per the type's (mapOf's) schema
278
- const chunk = createYaml(schemas, mapOf, null, processAlwaysAdd, depth + 1, (path ? `${ path }.${ key }` : key), rootType, dataOptions);
296
+ const chunk = createYaml(schemas, mapOf, null, processAlwaysAdd, depth + 1, (path ? `${ path }.${ key }` : key), rootType, dataOptions, commentFieldsOptions);
279
297
  let indented = indent(chunk);
280
298
 
281
299
  // convert "# foo" to "#foo"
@@ -296,7 +314,34 @@ export function createYaml(
296
314
  if ( cleaned?.[key] ) {
297
315
  const parsedData = jsyaml.dump(cleaned[key]);
298
316
 
299
- out += `\n${ indent(parsedData.trim()) }`;
317
+ let chunk;
318
+
319
+ // If commentFieldOptions is defined on the model and the array has the property (`key`)
320
+ // defined in one of the options, then comment out that line.
321
+ if ( Array.isArray(commentFieldsOptions) && commentFieldsOptions.length ) {
322
+ let lines = parsedData.split('\n');
323
+
324
+ commentFieldsOptions.forEach((option) => {
325
+ // Assuming the path for the current array matches the option's path
326
+ // and the specific key to comment out exists in the array
327
+ if ( `${ path }.${ key }` === option.path && data[key][option.key] !== undefined ) {
328
+ // Comment out the line containing the target line
329
+ lines = lines.map((line, i) => {
330
+ if ( i === Number(option.key) ) {
331
+ return `#${ line }`;
332
+ }
333
+
334
+ return line;
335
+ });
336
+ }
337
+ });
338
+
339
+ chunk = lines.join('\n').trim();
340
+ } else {
341
+ chunk = parsedData.trim();
342
+ }
343
+
344
+ out += `\n${ indent(chunk) }`;
300
345
  }
301
346
  } catch (e) {
302
347
  console.error(`Error: Unable to parse array data for yaml of type: ${ type }`, e); // eslint-disable-line no-console
@@ -306,7 +351,7 @@ export function createYaml(
306
351
  if ( SIMPLE_TYPES.includes(arrayOf) ) {
307
352
  out += `\n# - ${ arrayOf }`;
308
353
  } else {
309
- const chunk = createYaml(schemas, arrayOf, null, false, depth + 1, (path ? `${ path }.${ key }` : key), rootType, dataOptions);
354
+ const chunk = createYaml(schemas, arrayOf, null, false, depth + 1, (path ? `${ path }.${ key }` : key), rootType, dataOptions, commentFieldsOptions);
310
355
  let indented = indent(chunk, 2);
311
356
 
312
357
  // turn "# foo" into "# - foo"
@@ -357,19 +402,45 @@ export function createYaml(
357
402
 
358
403
  const subDef = schemaDefinitions?.[type] || findBy(schemas, 'id', type);
359
404
 
360
- if ( subDef) {
405
+ if ( subDef ) {
361
406
  let chunk;
362
407
 
363
408
  if (subDef?.resourceFields && !isEmpty(subDef?.resourceFields)) {
364
- chunk = createYaml(schemas, type, data[key], processAlwaysAdd, depth + 1, (path ? `${ path }.${ key }` : key), rootType, dataOptions);
409
+ chunk = createYaml(schemas, type, data[key], processAlwaysAdd, depth + 1, (path ? `${ path }.${ key }` : key), rootType, dataOptions, commentFieldsOptions);
365
410
  } else if (data[key]) {
366
411
  // if there are no fields defined on the schema but there are in the data, just format data as yaml and add to output yaml
367
412
  try {
368
413
  const parsed = jsyaml.dump(data[key]);
369
414
 
370
- chunk = parsed.trim();
415
+ // If commentFieldOptions is defined on the model and `data[key]` has the property (`key`)
416
+ // defined in one of the options, then comment out that line.
417
+ if ( Array.isArray(commentFieldsOptions) && commentFieldsOptions.length ) {
418
+ let lines = parsed.split('\n');
419
+
420
+ commentFieldsOptions.forEach((option) => {
421
+ // Assuming the path for the current data[key] matches the option's path
422
+ // and the specific key to comment out exists in the data[key]
423
+ if ( `${ path }.${ key }` === option.path && data[key][option.key] !== undefined ) {
424
+ const targetKeyString = `${ option.key }:`;
425
+
426
+ // Comment out the line containing the target key
427
+ lines = lines.map((line) => {
428
+ if ( line.trim().startsWith(targetKeyString) ) {
429
+ return `# ${ line }`;
430
+ }
431
+
432
+ return line;
433
+ });
434
+ }
435
+ });
436
+
437
+ chunk = lines.join('\n').trim();
438
+ } else {
439
+ // If commentFieldsOptions do not exist, use the original `parsed` string
440
+ chunk = parsed.trim();
441
+ }
371
442
  } catch (e) {
372
- console.error(`Error: Unale to parse data for yaml of type: ${ type }`, e); // eslint-disable-line no-console
443
+ console.error(`Error: Unable to parse data for yaml of type: ${ type }`, e); // eslint-disable-line no-console
373
444
  }
374
445
  }
375
446
 
@@ -380,6 +451,29 @@ export function createYaml(
380
451
 
381
452
  return out;
382
453
  }
454
+
455
+ /**
456
+ * Extends original commentFieldsOptions to cover nested objects
457
+ */
458
+ function addCommentSubFieldsOptions(options, data, path, key) {
459
+ if (!!options) {
460
+ if ( Array.isArray(options) && options.length ) {
461
+ const currentPath = path ? `${ path }.${ key }` : key;
462
+
463
+ if ( options.some((option) => `${ option.path }.${ option.key }` === currentPath) ) {
464
+ options = [
465
+ ...options,
466
+ ...Object.keys(data[key]).map((k) => ({
467
+ path: `${ path }.${ key }`,
468
+ key: k
469
+ }))
470
+ ];
471
+ }
472
+ }
473
+ }
474
+
475
+ return options;
476
+ }
383
477
  }
384
478
 
385
479
  function comment(lines) {