@rancher/shell 3.0.8-rc.8 → 3.0.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 (260) hide show
  1. package/apis/impl/apis.ts +61 -0
  2. package/apis/index.ts +40 -0
  3. package/apis/intf/modal.ts +90 -0
  4. package/apis/intf/shell.ts +36 -0
  5. package/apis/intf/slide-in.ts +98 -0
  6. package/apis/intf/system.ts +41 -0
  7. package/apis/shell/__tests__/modal.test.ts +80 -0
  8. package/apis/shell/__tests__/notifications.test.ts +71 -0
  9. package/apis/shell/__tests__/slide-in.test.ts +54 -0
  10. package/apis/shell/__tests__/system.test.ts +129 -0
  11. package/apis/shell/index.ts +38 -0
  12. package/apis/shell/modal.ts +41 -0
  13. package/apis/shell/notifications.ts +65 -0
  14. package/apis/shell/slide-in.ts +33 -0
  15. package/apis/shell/system.ts +65 -0
  16. package/apis/vue-shim.d.ts +11 -0
  17. package/assets/brand/suse/dark/rancher-logo.svg +1 -64
  18. package/assets/styles/global/_tooltip.scss +6 -1
  19. package/assets/translations/en-us.yaml +14 -1
  20. package/components/ActionMenuShell.vue +3 -1
  21. package/components/BackLink.vue +8 -0
  22. package/components/BannerGraphic.vue +1 -5
  23. package/components/BrandImage.vue +17 -6
  24. package/components/Cron/CronExpressionEditor.vue +1 -1
  25. package/components/Cron/CronExpressionEditorModal.vue +1 -1
  26. package/components/CruResource.vue +8 -1
  27. package/components/Drawer/ResourceDetailDrawer/ConfigTab.vue +1 -0
  28. package/components/Drawer/ResourceDetailDrawer/__tests__/composables.test.ts +50 -1
  29. package/components/Drawer/ResourceDetailDrawer/composables.ts +19 -0
  30. package/components/Drawer/ResourceDetailDrawer/index.vue +4 -1
  31. package/components/Drawer/ResourceDetailDrawer/types.ts +2 -1
  32. package/components/LocaleSelector.vue +2 -2
  33. package/components/ModalManager.vue +11 -1
  34. package/components/Questions/__tests__/Yaml.test.ts +1 -1
  35. package/components/Questions/__tests__/index.test.ts +159 -0
  36. package/components/RelatedResources.vue +5 -0
  37. package/components/Resource/Detail/Metadata/Annotations/index.vue +2 -2
  38. package/components/Resource/Detail/Metadata/Labels/index.vue +2 -2
  39. package/components/Resource/Detail/Metadata/index.vue +3 -3
  40. package/components/Resource/Detail/ResourcePopover/index.vue +5 -1
  41. package/components/Resource/Detail/composables.ts +2 -2
  42. package/components/ResourceDetail/Masthead/latest.vue +23 -21
  43. package/components/ResourceDetail/index.vue +3 -0
  44. package/components/ResourceTable.vue +54 -21
  45. package/components/SlideInPanelManager.vue +16 -11
  46. package/components/SortableTable/THead.vue +2 -1
  47. package/components/SortableTable/index.vue +20 -2
  48. package/components/Tabbed/__tests__/index.test.ts +86 -0
  49. package/components/Tabbed/index.vue +37 -2
  50. package/components/__tests__/NamespaceFilter.test.ts +49 -0
  51. package/components/auth/SelectPrincipal.vue +28 -6
  52. package/components/auth/__tests__/SelectPrincipal.test.ts +119 -0
  53. package/components/auth/login/ldap.vue +3 -3
  54. package/components/fleet/FleetSecretSelector.vue +1 -1
  55. package/components/form/KeyValue.vue +1 -1
  56. package/components/form/NameNsDescription.vue +1 -1
  57. package/components/form/NodeScheduling.vue +2 -2
  58. package/components/form/ResourceTabs/composable.ts +2 -2
  59. package/components/form/ResourceTabs/index.vue +0 -2
  60. package/components/form/__tests__/NameNsDescription.test.ts +42 -0
  61. package/components/formatter/InternalExternalIP.vue +4 -1
  62. package/components/formatter/LinkName.vue +5 -0
  63. package/components/formatter/__tests__/InternalExternalIP.test.ts +1 -1
  64. package/components/nav/Group.vue +25 -7
  65. package/components/nav/Header.vue +1 -1
  66. package/components/nav/NamespaceFilter.vue +1 -0
  67. package/components/nav/Type.vue +17 -6
  68. package/components/nav/WindowManager/panels/TabBodyContainer.vue +1 -1
  69. package/components/nav/__tests__/Type.test.ts +59 -0
  70. package/components/templates/standalone.vue +1 -1
  71. package/composables/cruResource.ts +27 -0
  72. package/composables/focusTrap.ts +3 -1
  73. package/composables/resourceDetail.ts +15 -0
  74. package/composables/useI18n.ts +10 -1
  75. package/composables/useLabeledFormElement.ts +3 -4
  76. package/config/__test__/uiplugins.test.ts +309 -0
  77. package/config/labels-annotations.js +1 -0
  78. package/config/product/explorer.js +3 -1
  79. package/config/product/fleet.js +1 -1
  80. package/config/router/navigation-guards/clusters.js +3 -3
  81. package/config/router/navigation-guards/products.js +1 -1
  82. package/config/router/routes.js +7 -7
  83. package/config/types.js +7 -0
  84. package/config/uiplugins.js +46 -2
  85. package/core/__tests__/extension-manager-impl.test.js +437 -0
  86. package/core/extension-manager-impl.js +21 -25
  87. package/core/plugin-helpers.ts +2 -2
  88. package/core/plugin.ts +9 -1
  89. package/core/plugins-loader.js +2 -2
  90. package/core/types-provisioning.ts +5 -1
  91. package/core/types.ts +35 -0
  92. package/detail/provisioning.cattle.io.cluster.vue +9 -6
  93. package/dialog/DeveloperLoadExtensionDialog.vue +13 -4
  94. package/dialog/MoveNamespaceDialog.vue +20 -4
  95. package/dialog/RollbackWorkloadDialog.vue +2 -5
  96. package/dialog/SearchDialog.vue +1 -0
  97. package/dialog/__tests__/MoveNamespaceDialog.test.ts +249 -0
  98. package/directives/__tests__/clean-tooltip.test.ts +298 -0
  99. package/directives/clean-tooltip.ts +234 -0
  100. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +2 -2
  101. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +100 -3
  102. package/edit/autoscaling.horizontalpodautoscaler/index.vue +1 -0
  103. package/edit/configmap.vue +1 -0
  104. package/edit/constraints.gatekeeper.sh.constraint/index.vue +1 -0
  105. package/edit/fleet.cattle.io.helmop.vue +11 -6
  106. package/edit/helm.cattle.io.projecthelmchart.vue +1 -0
  107. package/edit/k8s.cni.cncf.io.networkattachmentdefinition.vue +1 -0
  108. package/edit/logging-flow/index.vue +1 -0
  109. package/edit/logging.banzaicloud.io.output/index.vue +1 -0
  110. package/edit/management.cattle.io.fleetworkspace.vue +1 -1
  111. package/edit/management.cattle.io.project.vue +1 -0
  112. package/edit/monitoring.coreos.com.alertmanagerconfig/index.vue +4 -1
  113. package/edit/monitoring.coreos.com.alertmanagerconfig/receiverConfig.vue +2 -1
  114. package/edit/monitoring.coreos.com.prometheusrule/index.vue +1 -0
  115. package/edit/monitoring.coreos.com.receiver/index.vue +2 -1
  116. package/edit/monitoring.coreos.com.route.vue +1 -1
  117. package/edit/namespace.vue +1 -0
  118. package/edit/networking.istio.io.destinationrule/index.vue +1 -0
  119. package/edit/networking.k8s.io.ingress/index.vue +1 -0
  120. package/edit/networking.k8s.io.networkpolicy/PolicyRules.vue +1 -0
  121. package/edit/networking.k8s.io.networkpolicy/index.vue +1 -0
  122. package/edit/node.vue +1 -0
  123. package/edit/persistentvolume/index.vue +27 -22
  124. package/edit/persistentvolume/plugins/awsElasticBlockStore.vue +13 -14
  125. package/edit/persistentvolume/plugins/azureDisk.vue +49 -48
  126. package/edit/persistentvolume/plugins/azureFile.vue +15 -14
  127. package/edit/persistentvolume/plugins/cephfs.vue +15 -14
  128. package/edit/persistentvolume/plugins/cinder.vue +15 -14
  129. package/edit/persistentvolume/plugins/csi.vue +18 -16
  130. package/edit/persistentvolume/plugins/fc.vue +13 -14
  131. package/edit/persistentvolume/plugins/flexVolume.vue +15 -14
  132. package/edit/persistentvolume/plugins/flocker.vue +1 -3
  133. package/edit/persistentvolume/plugins/gcePersistentDisk.vue +13 -14
  134. package/edit/persistentvolume/plugins/glusterfs.vue +15 -14
  135. package/edit/persistentvolume/plugins/hostPath.vue +40 -39
  136. package/edit/persistentvolume/plugins/iscsi.vue +13 -14
  137. package/edit/persistentvolume/plugins/local.vue +1 -3
  138. package/edit/persistentvolume/plugins/longhorn.vue +23 -22
  139. package/edit/persistentvolume/plugins/nfs.vue +15 -14
  140. package/edit/persistentvolume/plugins/photonPersistentDisk.vue +1 -14
  141. package/edit/persistentvolume/plugins/portworxVolume.vue +15 -14
  142. package/edit/persistentvolume/plugins/quobyte.vue +15 -14
  143. package/edit/persistentvolume/plugins/rbd.vue +15 -14
  144. package/edit/persistentvolume/plugins/scaleIO.vue +15 -14
  145. package/edit/persistentvolume/plugins/storageos.vue +15 -14
  146. package/edit/persistentvolume/plugins/vsphereVolume.vue +1 -3
  147. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +21 -21
  148. package/edit/provisioning.cattle.io.cluster/index.vue +5 -5
  149. package/edit/provisioning.cattle.io.cluster/rke2.vue +9 -8
  150. package/edit/resources.cattle.io.restore.vue +1 -1
  151. package/edit/secret/index.vue +1 -1
  152. package/edit/service.vue +1 -0
  153. package/edit/serviceaccount.vue +1 -0
  154. package/edit/storage.k8s.io.storageclass/index.vue +1 -0
  155. package/edit/workload/Job.vue +2 -2
  156. package/edit/workload/index.vue +2 -1
  157. package/edit/workload/mixins/workload.js +1 -1
  158. package/initialize/App.vue +4 -4
  159. package/initialize/install-plugins.js +19 -5
  160. package/machine-config/azure.vue +1 -1
  161. package/machine-config/components/GCEImage.vue +1 -1
  162. package/mixins/__tests__/brand.spec.ts +2 -2
  163. package/mixins/brand.js +1 -7
  164. package/mixins/create-edit-view/index.js +5 -0
  165. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +128 -5
  166. package/models/chart.js +70 -74
  167. package/models/management.cattle.io.cluster.js +21 -3
  168. package/models/provisioning.cattle.io.cluster.js +31 -11
  169. package/package.json +11 -10
  170. package/pages/auth/login.vue +4 -6
  171. package/pages/auth/setup.vue +1 -1
  172. package/pages/auth/verify.vue +3 -3
  173. package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +135 -0
  174. package/pages/c/_cluster/apps/charts/chart.vue +33 -15
  175. package/pages/c/_cluster/apps/charts/index.vue +122 -24
  176. package/pages/c/_cluster/apps/charts/install.vue +33 -0
  177. package/pages/c/_cluster/explorer/__tests__/index.test.ts +1 -1
  178. package/pages/c/_cluster/explorer/index.vue +8 -6
  179. package/pages/c/_cluster/fleet/index.vue +4 -7
  180. package/pages/c/_cluster/manager/hostedprovider/index.vue +12 -6
  181. package/pages/c/_cluster/settings/brand.vue +1 -1
  182. package/pages/c/_cluster/settings/index.vue +5 -0
  183. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +7 -0
  184. package/pages/c/_cluster/uiplugins/catalogs.vue +147 -0
  185. package/pages/c/_cluster/uiplugins/index.vue +126 -184
  186. package/pkg/auto-import.js +3 -3
  187. package/pkg/dynamic-importer.lib.js +1 -1
  188. package/pkg/import.js +1 -1
  189. package/plugins/__tests__/mutations.tests.ts +179 -0
  190. package/plugins/dashboard-client-init.js +3 -0
  191. package/plugins/dashboard-store/getters.js +19 -2
  192. package/plugins/dashboard-store/model-loader.js +1 -1
  193. package/plugins/dashboard-store/mutations.js +23 -2
  194. package/plugins/dashboard-store/resource-class.js +11 -5
  195. package/plugins/i18n.js +8 -0
  196. package/plugins/plugin.js +2 -2
  197. package/plugins/steve/__tests__/steve-pagination-utils.test.ts +506 -0
  198. package/plugins/steve/steve-class.js +1 -1
  199. package/plugins/steve/steve-pagination-utils.ts +131 -47
  200. package/rancher-components/Form/Checkbox/Checkbox.vue +1 -1
  201. package/rancher-components/Form/LabeledInput/LabeledInput.vue +1 -1
  202. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +6 -42
  203. package/rancher-components/Pill/RcStatusBadge/index.ts +0 -1
  204. package/rancher-components/Pill/RcStatusBadge/types.ts +1 -1
  205. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +5 -28
  206. package/rancher-components/Pill/RcStatusIndicator/types.ts +2 -1
  207. package/rancher-components/Pill/types.ts +0 -1
  208. package/rancher-components/RcDropdown/useDropdownContext.ts +2 -4
  209. package/rancher-components/RcIcon/RcIcon.test.ts +51 -0
  210. package/rancher-components/RcIcon/RcIcon.vue +46 -0
  211. package/rancher-components/RcIcon/index.ts +1 -0
  212. package/rancher-components/RcIcon/types.ts +160 -0
  213. package/rancher-components/RcItemCard/RcItemCard.vue +1 -1
  214. package/rancher-components/utils/status.test.ts +67 -0
  215. package/rancher-components/utils/status.ts +77 -0
  216. package/scripts/publish-shell.sh +25 -0
  217. package/scripts/typegen.sh +1 -0
  218. package/store/__tests__/catalog.test.ts +1 -1
  219. package/store/__tests__/type-map.test.ts +164 -2
  220. package/store/action-menu.js +8 -0
  221. package/store/auth.js +25 -13
  222. package/store/catalog.js +6 -0
  223. package/store/i18n.js +3 -3
  224. package/store/index.js +8 -6
  225. package/store/notifications.ts +2 -0
  226. package/store/prefs.js +6 -7
  227. package/store/type-map.js +17 -7
  228. package/store/wm.ts +4 -4
  229. package/types/internal-api/shell/modal.d.ts +6 -6
  230. package/types/notifications/index.ts +126 -15
  231. package/types/rancher/index.d.ts +9 -0
  232. package/types/shell/index.d.ts +54 -3
  233. package/types/store/__tests__/pagination.types.spec.ts +137 -0
  234. package/types/store/pagination.types.ts +157 -9
  235. package/types/vue-shim.d.ts +5 -4
  236. package/utils/__tests__/provider.test.ts +98 -0
  237. package/utils/__tests__/router.test.js +238 -0
  238. package/utils/__tests__/selector-typed.test.ts +263 -0
  239. package/utils/cluster.js +4 -1
  240. package/utils/color.js +1 -1
  241. package/utils/dynamic-content/__tests__/info.test.ts +6 -0
  242. package/utils/dynamic-content/info.ts +43 -0
  243. package/utils/favicon.js +4 -4
  244. package/utils/fleet.ts +8 -1
  245. package/utils/pagination-utils.ts +2 -2
  246. package/utils/pagination-wrapper.ts +1 -1
  247. package/utils/provider.ts +14 -0
  248. package/utils/router.js +50 -0
  249. package/utils/selector-typed.ts +6 -2
  250. package/utils/unit-tests/pagination-utils.spec.ts +8 -8
  251. package/vue.config.js +3 -3
  252. package/composables/useExtensionManager.ts +0 -17
  253. package/core/plugins.js +0 -38
  254. package/directives/clean-tooltip.js +0 -32
  255. package/plugins/internal-api/index.ts +0 -37
  256. package/plugins/internal-api/shared/base-api.ts +0 -13
  257. package/plugins/internal-api/shell/shell.api.ts +0 -108
  258. package/plugins/nuxt-client-init.js +0 -3
  259. package/types/internal-api/shell/growl.d.ts +0 -25
  260. package/types/internal-api/shell/slideIn.d.ts +0 -15
@@ -96,15 +96,15 @@ export default {
96
96
  await this.value.waitForProvisioner();
97
97
 
98
98
  // Support for the 'provisioner' extension
99
- const extClass = this.$plugin.getDynamic('provisioner', this.value.machineProvider);
99
+ const extClass = this.$extension.getDynamic('provisioner', this.value.machineProvider);
100
100
 
101
101
  if (extClass) {
102
102
  this.extProvider = new extClass({
103
- dispatch: this.$store.dispatch,
104
- getters: this.$store.getters,
105
- axios: this.$store.$axios,
106
- $plugin: this.$store.app.$plugin,
107
- $t: this.t
103
+ dispatch: this.$store.dispatch,
104
+ getters: this.$store.getters,
105
+ axios: this.$store.$axios,
106
+ $extension: this.$store.app.$extension,
107
+ $t: this.t
108
108
  });
109
109
 
110
110
  this.extDetailTabs = {
@@ -578,6 +578,7 @@ export default {
578
578
  // Hosted kubernetes providers with private endpoints need the registration tab
579
579
  // https://github.com/rancher/dashboard/issues/6036
580
580
  // https://github.com/rancher/dashboard/issues/4545
581
+
581
582
  if ( this.value.isHostedKubernetesProvider && this.value.isPrivateHostedProvider && !this.isClusterReady ) {
582
583
  return this.extDetailTabs.registration;
583
584
  }
@@ -892,6 +893,7 @@ export default {
892
893
  :disabled="!group.ref.canScaleDownPool()"
893
894
  type="button"
894
895
  class="btn btn-sm role-secondary"
896
+ data-testid="scale-down-button"
895
897
  @click="toggleScaleDownModal($event, group.ref)"
896
898
  >
897
899
  <i class="icon icon-sm icon-minus" />
@@ -901,6 +903,7 @@ export default {
901
903
  :disabled="!group.ref.canScaleUpPool()"
902
904
  type="button"
903
905
  class="btn btn-sm role-secondary ml-10"
906
+ data-testid="scale-up-button"
904
907
  @click="group.ref.scalePool(1)"
905
908
  >
906
909
  <i class="icon icon-sm icon-plus" />
@@ -4,6 +4,7 @@ import { LabeledInput } from '@components/Form/LabeledInput';
4
4
  import Checkbox from '@components/Form/Checkbox/Checkbox.vue';
5
5
  import { UI_PLUGIN } from '@shell/config/types';
6
6
  import { UI_PLUGIN_CHART_ANNOTATIONS, UI_PLUGIN_NAMESPACE } from '@shell/config/uiplugins';
7
+ import { DEVELOPER_LOAD_NAME_SUFFIX } from '@shell/core/extension-manager-impl';
7
8
 
8
9
  export default {
9
10
  emits: ['close'],
@@ -101,8 +102,16 @@ export default {
101
102
  const parts = name.split('-');
102
103
 
103
104
  if (parts.length >= 2) {
104
- version = parts.pop();
105
- crdName = parts.join('-');
105
+ // fixing the name-version separation, especially in RC versions
106
+ // like: elemental-3.0.1-rc.1
107
+ // on capturing version it must be "digit + dot + digit" + rest of string
108
+ const regex = /^(?<name>.+?)-(?<version>\d+\.\d+.*)$/;
109
+ const match = name.match(regex);
110
+
111
+ if (match && match.groups) {
112
+ version = match.groups.version;
113
+ crdName = match.groups.name;
114
+ }
106
115
  }
107
116
 
108
117
  if (this.persist) {
@@ -114,7 +123,7 @@ export default {
114
123
  },
115
124
  spec: {
116
125
  plugin: {
117
- name: crdName,
126
+ name: `${ crdName }${ DEVELOPER_LOAD_NAME_SUFFIX }`,
118
127
  version,
119
128
  endpoint: url,
120
129
  noCache: true,
@@ -136,7 +145,7 @@ export default {
136
145
  }
137
146
  }
138
147
 
139
- this.$plugin.loadAsync(name, url).then(() => {
148
+ this.$extension.loadAsync(name, url).then(() => {
140
149
  this.closeDialog(true);
141
150
 
142
151
  this.$store.dispatch('growl/success', {
@@ -6,6 +6,8 @@ import LabeledSelect from '@shell/components/form/LabeledSelect';
6
6
  import { MANAGEMENT } from '@shell/config/types';
7
7
  import { PROJECT } from '@shell/config/labels-annotations';
8
8
 
9
+ const NONE_VALUE = ' ';
10
+
9
11
  export default {
10
12
  emits: ['close'],
11
13
 
@@ -48,8 +50,12 @@ export default {
48
50
  return this.toMove.filter((namespace) => !!namespace.project).map((namespace) => namespace.project.shortId);
49
51
  },
50
52
 
53
+ isAllInProject() {
54
+ return this.toMove.every((namespace) => !!namespace.project);
55
+ },
56
+
51
57
  projectOptions() {
52
- return this.projects.reduce((inCluster, project) => {
58
+ const options = this.projects.reduce((inCluster, project) => {
53
59
  if (!this.excludedProjects.includes(project.shortId) && project.spec?.clusterName === this.currentCluster.id) {
54
60
  inCluster.push({
55
61
  value: project.shortId,
@@ -59,6 +65,16 @@ export default {
59
65
 
60
66
  return inCluster;
61
67
  }, []);
68
+
69
+ // To be consistent with listed projects we should only provide the option if it applies too all of the namespaces
70
+ if (this.isAllInProject) {
71
+ options.unshift({
72
+ value: NONE_VALUE,
73
+ label: this.t('moveModal.noProject')
74
+ });
75
+ }
76
+
77
+ return options;
62
78
  }
63
79
  },
64
80
 
@@ -69,10 +85,10 @@ export default {
69
85
 
70
86
  async move(finish) {
71
87
  const cluster = this.$store.getters['currentCluster'];
72
- const clusterWithProjectId = `${ cluster.id }:${ this.targetProject }`;
88
+ const clusterWithProjectId = this.targetProject && this.targetProject !== NONE_VALUE ? `${ cluster.id }:${ this.targetProject }` : null;
73
89
 
74
90
  const promises = this.toMove.map((namespace) => {
75
- namespace.setLabel(PROJECT, this.targetProject);
91
+ namespace.setLabel(PROJECT, this.targetProject && this.targetProject !== NONE_VALUE ? this.targetProject : null);
76
92
  namespace.setAnnotation(PROJECT, clusterWithProjectId);
77
93
 
78
94
  return namespace.save();
@@ -128,7 +144,7 @@ export default {
128
144
  <AsyncButton
129
145
  :action-label="t('moveModal.moveButtonLabel')"
130
146
  class="btn bg-primary ml-10"
131
- :disabled="!targetProject"
147
+ :disabled="targetProject === null"
132
148
  @click="move"
133
149
  />
134
150
  </template>
@@ -165,12 +165,9 @@ export default {
165
165
  return option.label;
166
166
  },
167
167
  sizeDialog() {
168
- const dialogs = document.getElementsByClassName('modal-container');
169
- const width = this.showDiff ? '85%' : '600px';
168
+ const modalWidth = this.showDiff ? '85%' : '600px';
170
169
 
171
- if (dialogs.length === 1) {
172
- dialogs[0].style.setProperty('width', width);
173
- }
170
+ this.$store.commit('action-menu/updateModalData', [{ key: 'modalWidth', value: modalWidth }]);
174
171
  },
175
172
  sanitizeYaml(obj, path = '') {
176
173
  const res = {};
@@ -106,6 +106,7 @@ export default {
106
106
  :group="g"
107
107
  :can-collapse="false"
108
108
  :fixed-open="true"
109
+ :highlight-route="false"
109
110
  @close="$emit('close')"
110
111
  >
111
112
  <template #accordion>
@@ -0,0 +1,249 @@
1
+ import { shallowMount, VueWrapper } from '@vue/test-utils';
2
+ import MoveNamespaceDialog from '@shell/dialog/MoveNamespaceDialog.vue';
3
+
4
+ const t = (key: string): string => key;
5
+ const NONE_VALUE = ' ';
6
+
7
+ describe('component: MoveNamespaceDialog', () => {
8
+ let wrapper: VueWrapper<any>;
9
+
10
+ const mockProjects = [
11
+ {
12
+ shortId: 'p-abc123',
13
+ nameDisplay: 'Project A',
14
+ spec: { clusterName: 'local' }
15
+ },
16
+ {
17
+ shortId: 'p-def456',
18
+ nameDisplay: 'Project B',
19
+ spec: { clusterName: 'local' }
20
+ },
21
+ {
22
+ shortId: 'p-other',
23
+ nameDisplay: 'Other Cluster Project',
24
+ spec: { clusterName: 'other-cluster' }
25
+ }
26
+ ];
27
+
28
+ const createMockNamespace = (projectId: string | null = null) => {
29
+ const namespace: any = {
30
+ nameDisplay: 'test-namespace',
31
+ projectId,
32
+ project: projectId ? { shortId: projectId } : null,
33
+ setLabel: jest.fn(),
34
+ setAnnotation: jest.fn(),
35
+ save: jest.fn().mockResolvedValue({}),
36
+ };
37
+
38
+ return namespace;
39
+ };
40
+
41
+ const mountComponent = (propsData = {}, options = {}) => {
42
+ const store = {
43
+ dispatch: jest.fn().mockResolvedValue(mockProjects),
44
+ getters: { currentCluster: { id: 'local' } }
45
+ };
46
+
47
+ const defaultProps = {
48
+ resources: [createMockNamespace('p-abc123')],
49
+ movingCb: jest.fn(),
50
+ registerBackgroundClosing: jest.fn(),
51
+ };
52
+
53
+ return shallowMount(MoveNamespaceDialog, {
54
+ propsData: {
55
+ ...defaultProps,
56
+ ...propsData,
57
+ },
58
+ global: {
59
+ mocks: {
60
+ $store: store,
61
+ t,
62
+ },
63
+ },
64
+ ...options,
65
+ });
66
+ };
67
+
68
+ afterEach(() => {
69
+ if (wrapper) {
70
+ wrapper.unmount();
71
+ }
72
+ });
73
+
74
+ describe('projectOptions', () => {
75
+ it('should include "None" option as first item', async() => {
76
+ wrapper = mountComponent();
77
+ await wrapper.vm.$options.fetch.call(wrapper.vm);
78
+
79
+ const options = wrapper.vm.projectOptions;
80
+
81
+ expect(options[0]).toStrictEqual({
82
+ value: NONE_VALUE,
83
+ label: 'moveModal.noProject'
84
+ });
85
+ });
86
+
87
+ it('should include projects from current cluster', async() => {
88
+ // Use a namespace not in any project so no projects get excluded
89
+ const namespace = createMockNamespace(null);
90
+
91
+ wrapper = mountComponent({ resources: [namespace] });
92
+ await wrapper.vm.$options.fetch.call(wrapper.vm);
93
+
94
+ const options = wrapper.vm.projectOptions;
95
+ const projectLabels = options.map((o: any) => o.label);
96
+
97
+ expect(projectLabels).toContain('Project A');
98
+ expect(projectLabels).toContain('Project B');
99
+ });
100
+
101
+ it('should exclude projects from other clusters', async() => {
102
+ wrapper = mountComponent();
103
+ await wrapper.vm.$options.fetch.call(wrapper.vm);
104
+
105
+ const options = wrapper.vm.projectOptions;
106
+ const projectLabels = options.map((o: any) => o.label);
107
+
108
+ expect(projectLabels).not.toContain('Other Cluster Project');
109
+ });
110
+
111
+ it('should exclude current project of namespaces being moved', async() => {
112
+ const namespace = createMockNamespace('p-abc123');
113
+
114
+ wrapper = mountComponent({ resources: [namespace] });
115
+ await wrapper.vm.$options.fetch.call(wrapper.vm);
116
+
117
+ const options = wrapper.vm.projectOptions;
118
+ const projectValues = options.map((o: any) => o.value);
119
+
120
+ expect(projectValues).not.toContain('p-abc123');
121
+ expect(projectValues).toContain('p-def456');
122
+ });
123
+
124
+ it('should NOT include "None" option when some namespaces are not in a project', async() => {
125
+ const namespaceInProject = createMockNamespace('p-abc123');
126
+ const namespaceNotInProject = createMockNamespace(null);
127
+
128
+ wrapper = mountComponent({ resources: [namespaceInProject, namespaceNotInProject] });
129
+ await wrapper.vm.$options.fetch.call(wrapper.vm);
130
+
131
+ const options = wrapper.vm.projectOptions;
132
+ const optionValues = options.map((o: any) => o.value);
133
+
134
+ expect(optionValues).not.toContain(NONE_VALUE);
135
+ });
136
+
137
+ it('should NOT include "None" option when no namespaces are in a project', async() => {
138
+ const namespace = createMockNamespace(null);
139
+
140
+ wrapper = mountComponent({ resources: [namespace] });
141
+ await wrapper.vm.$options.fetch.call(wrapper.vm);
142
+
143
+ const options = wrapper.vm.projectOptions;
144
+ const optionValues = options.map((o: any) => o.value);
145
+
146
+ expect(optionValues).not.toContain(NONE_VALUE);
147
+ });
148
+ });
149
+
150
+ describe('targetProject default value', () => {
151
+ it('should default to empty string (None option)', () => {
152
+ wrapper = mountComponent();
153
+
154
+ expect(wrapper.vm.targetProject).toBeNull();
155
+ });
156
+ });
157
+
158
+ describe('move button disabled state', () => {
159
+ it('should be enabled when targetProject is NONE_VALUE (None)', () => {
160
+ wrapper = mountComponent();
161
+ wrapper.vm.targetProject = NONE_VALUE;
162
+
163
+ // The button should be enabled when targetProject !== null
164
+ expect(wrapper.vm.targetProject === null).toBe(false);
165
+ });
166
+
167
+ it('should be enabled when targetProject is a project id', () => {
168
+ wrapper = mountComponent();
169
+ wrapper.vm.targetProject = 'p-def456';
170
+
171
+ expect(wrapper.vm.targetProject === null).toBe(false);
172
+ });
173
+ });
174
+
175
+ describe('move method', () => {
176
+ it('should clear labels and annotations when targetProject is NONE_VALUE (None)', async() => {
177
+ const namespace = createMockNamespace('p-abc123');
178
+
179
+ wrapper = mountComponent({ resources: [namespace] });
180
+ await wrapper.vm.$options.fetch.call(wrapper.vm);
181
+
182
+ wrapper.vm.targetProject = NONE_VALUE;
183
+
184
+ const finish = jest.fn();
185
+
186
+ await wrapper.vm.move(finish);
187
+
188
+ expect(namespace.setLabel).toHaveBeenCalledWith('field.cattle.io/projectId', null);
189
+ expect(namespace.setAnnotation).toHaveBeenCalledWith('field.cattle.io/projectId', null);
190
+ expect(namespace.save).toHaveBeenCalledWith();
191
+ expect(finish).toHaveBeenCalledWith(true);
192
+ });
193
+
194
+ it('should set labels and annotations when moving to a project', async() => {
195
+ const namespace = createMockNamespace('p-abc123');
196
+
197
+ wrapper = mountComponent({ resources: [namespace] });
198
+ await wrapper.vm.$options.fetch.call(wrapper.vm);
199
+
200
+ wrapper.vm.targetProject = 'p-def456';
201
+
202
+ const finish = jest.fn();
203
+
204
+ await wrapper.vm.move(finish);
205
+
206
+ expect(namespace.setLabel).toHaveBeenCalledWith('field.cattle.io/projectId', 'p-def456');
207
+ expect(namespace.setAnnotation).toHaveBeenCalledWith('field.cattle.io/projectId', 'local:p-def456');
208
+ expect(namespace.save).toHaveBeenCalledWith();
209
+ expect(finish).toHaveBeenCalledWith(true);
210
+ });
211
+
212
+ it('should handle multiple namespaces', async() => {
213
+ const namespace1 = createMockNamespace('p-abc123');
214
+ const namespace2 = createMockNamespace('p-abc123');
215
+
216
+ wrapper = mountComponent({ resources: [namespace1, namespace2] });
217
+ await wrapper.vm.$options.fetch.call(wrapper.vm);
218
+
219
+ wrapper.vm.targetProject = NONE_VALUE;
220
+
221
+ const finish = jest.fn();
222
+
223
+ await wrapper.vm.move(finish);
224
+
225
+ expect(namespace1.setLabel).toHaveBeenCalledWith('field.cattle.io/projectId', null);
226
+ expect(namespace1.setAnnotation).toHaveBeenCalledWith('field.cattle.io/projectId', null);
227
+ expect(namespace2.setLabel).toHaveBeenCalledWith('field.cattle.io/projectId', null);
228
+ expect(namespace2.setAnnotation).toHaveBeenCalledWith('field.cattle.io/projectId', null);
229
+ expect(namespace1.save).toHaveBeenCalledWith();
230
+ expect(namespace2.save).toHaveBeenCalledWith();
231
+ });
232
+
233
+ it('should call finish with false when save fails', async() => {
234
+ const namespace = createMockNamespace('p-abc123');
235
+
236
+ jest.spyOn(namespace, 'save').mockImplementation().mockRejectedValue(new Error('Save failed'));
237
+ wrapper = mountComponent({ resources: [namespace] });
238
+ await wrapper.vm.$options.fetch.call(wrapper.vm);
239
+
240
+ wrapper.vm.targetProject = NONE_VALUE;
241
+
242
+ const finish = jest.fn();
243
+
244
+ await wrapper.vm.move(finish);
245
+
246
+ expect(finish).toHaveBeenCalledWith(false);
247
+ });
248
+ });
249
+ });