@rancher/shell 0.5.1 → 0.5.3

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 (70) hide show
  1. package/assets/translations/en-us.yaml +8 -4
  2. package/components/ClusterIconMenu.vue +24 -9
  3. package/components/CodeMirror.vue +79 -18
  4. package/components/FixedBanner.vue +1 -0
  5. package/components/ResourceDetail/index.vue +1 -4
  6. package/components/ResourceYaml.vue +29 -5
  7. package/components/SideNav.vue +42 -64
  8. package/components/SortableTable/index.vue +1 -1
  9. package/components/YamlEditor.vue +1 -0
  10. package/components/__tests__/CodeMirror.spec.ts +99 -0
  11. package/components/form/BannerSettings.vue +3 -0
  12. package/components/form/FileSelector.vue +1 -0
  13. package/components/form/KeyValue.vue +1 -0
  14. package/components/formatter/WorkloadDetailEndpoints.vue +12 -22
  15. package/components/formatter/__tests__/WorkloadDetailEndpoints.test.ts +81 -0
  16. package/components/nav/Header.vue +1 -0
  17. package/components/nav/Jump.vue +19 -9
  18. package/components/nav/TopLevelMenu.vue +37 -15
  19. package/components/nav/Type.vue +15 -4
  20. package/components/nav/__tests__/TopLevelMenu.test.ts +1 -1
  21. package/components/nav/__tests__/Type.test.ts +30 -0
  22. package/core/types-provisioning.ts +7 -0
  23. package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +77 -0
  24. package/detail/fleet.cattle.io.bundle.vue +1 -1
  25. package/detail/provisioning.cattle.io.cluster.vue +19 -4
  26. package/edit/management.cattle.io.setting.vue +1 -0
  27. package/edit/monitoring.coreos.com.alertmanagerconfig/types/opsgenie.vue +1 -1
  28. package/edit/monitoring.coreos.com.alertmanagerconfig/types/pagerduty.vue +1 -2
  29. package/edit/monitoring.coreos.com.alertmanagerconfig/types/slack.vue +1 -1
  30. package/edit/provisioning.cattle.io.cluster/index.vue +23 -10
  31. package/edit/provisioning.cattle.io.cluster/rke2.vue +22 -50
  32. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +9 -11
  33. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +3 -1
  34. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +3 -0
  35. package/edit/token.vue +1 -0
  36. package/list/catalog.cattle.io.app.vue +1 -0
  37. package/list/management.cattle.io.setting.vue +1 -0
  38. package/machine-config/amazonec2.vue +1 -0
  39. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +151 -0
  40. package/models/__tests__/secret.test.ts +37 -0
  41. package/models/__tests__/storage.k8s.io.storageclass.test.ts +22 -0
  42. package/models/management.cattle.io.kontainerdriver.js +2 -1
  43. package/models/provisioning.cattle.io.cluster.js +36 -1
  44. package/models/secret.js +9 -0
  45. package/models/storage.k8s.io.storageclass.js +1 -1
  46. package/package.json +1 -1
  47. package/pages/c/_cluster/settings/DefaultLinksEditor.vue +1 -0
  48. package/pages/c/_cluster/settings/brand.vue +3 -0
  49. package/pages/c/_cluster/uiplugins/AddExtensionRepos.vue +4 -4
  50. package/pages/c/_cluster/uiplugins/SetupUIPlugins.vue +5 -2
  51. package/pages/c/_cluster/uiplugins/__tests__/AddExtensionRepos.test.ts +96 -0
  52. package/pages/c/_cluster/uiplugins/__tests__/SetupUIPlugins.test.ts +128 -0
  53. package/plugins/dashboard-store/__tests__/actions.test.ts +196 -111
  54. package/plugins/dashboard-store/actions.js +4 -6
  55. package/plugins/dashboard-store/getters.js +60 -2
  56. package/plugins/dashboard-store/resource-class.js +6 -2
  57. package/plugins/steve/__tests__/getters.spec.ts +10 -0
  58. package/plugins/steve/__tests__/resource-utils.test.ts +159 -0
  59. package/plugins/steve/actions.js +3 -37
  60. package/plugins/steve/getters.js +6 -0
  61. package/plugins/steve/resource-utils.ts +38 -0
  62. package/scripts/extension/parse-tag-name +3 -3
  63. package/store/__tests__/type-map.test.ts +1122 -0
  64. package/store/index.js +3 -2
  65. package/store/plugins.js +7 -6
  66. package/store/type-map.js +145 -75
  67. package/types/shell/index.d.ts +2 -0
  68. package/utils/__tests__/create-yaml.test.ts +10 -0
  69. package/utils/create-yaml.js +5 -1
  70. package/utils/object.js +10 -0
@@ -123,7 +123,6 @@ export default {
123
123
  }
124
124
  }
125
125
  }
126
-
127
126
  };
128
127
  </script>
129
128
 
@@ -185,7 +184,7 @@ export default {
185
184
  <div class="row mb-20">
186
185
  <div class="col span-12">
187
186
  <LabeledInput
188
- v-model="value.httpConfig.proxyUrl"
187
+ v-model="value.httpConfig.proxyURL"
189
188
  :mode="mode"
190
189
  label="Proxy URL"
191
190
  placeholder="e.g. http://my-proxy/"
@@ -126,7 +126,7 @@ export default {
126
126
  </div>
127
127
  <div class="col span-6">
128
128
  <LabeledInput
129
- v-model="value.httpConfig.proxyUrl"
129
+ v-model="value.httpConfig.proxyURL"
130
130
  :mode="mode"
131
131
  label="Proxy URL"
132
132
  placeholder="e.g. http://my-proxy/"
@@ -301,11 +301,11 @@ export default {
301
301
  const getters = this.$store.getters;
302
302
  const isImport = this.isImport;
303
303
  const isElementalActive = !!this.activeProducts.find((item) => item.name === ELEMENTAL_PRODUCT_NAME);
304
- const out = [];
304
+ let out = [];
305
305
 
306
306
  const templates = this.templateOptions;
307
307
  const vueKontainerTypes = getters['plugins/clusterDrivers'];
308
- const machineTypes = this.nodeDrivers.filter((x) => x.spec.active && x.state === 'active').map((x) => x.spec.displayName || x.id);
308
+ const machineTypes = this.nodeDrivers.filter((x) => x.spec.active && x.state === 'active');
309
309
 
310
310
  this.kontainerDrivers.filter((x) => (isImport ? x.showImport : x.showCreate)).forEach((obj) => {
311
311
  if ( vueKontainerTypes.includes(obj.driverName) ) {
@@ -330,14 +330,18 @@ export default {
330
330
  });
331
331
 
332
332
  if (this.isRke1 ) {
333
- machineTypes.forEach((id) => {
334
- addType(id, _RKE1, false, `/g/clusters/add/launch/${ id }`, this.iconClasses[id]);
333
+ machineTypes.forEach((type) => {
334
+ const id = type.spec.displayName || type.id;
335
+
336
+ addType(id, _RKE1, false, `/g/clusters/add/launch/${ id }`, this.iconClasses[id], type);
335
337
  });
336
338
 
337
339
  addType('custom', 'custom1', false, '/g/clusters/add/launch/custom');
338
340
  } else {
339
- machineTypes.forEach((id) => {
340
- addType(id, _RKE2, false);
341
+ machineTypes.forEach((type) => {
342
+ const id = type.spec.displayName || type.id;
343
+
344
+ addType(id, _RKE2, false, null, undefined, type);
341
345
  });
342
346
 
343
347
  addType('custom', 'custom2', false);
@@ -348,12 +352,18 @@ export default {
348
352
  }
349
353
 
350
354
  // Add from extensions
351
- // if th rke toggle is set to rke1, don't add extensions that specify rke2 group
352
- // default group is rke2
353
355
  this.extensions.forEach((ext) => {
356
+ // if the rke toggle is set to rke1, don't add extensions that specify rke2 group
357
+ // default group is rke2
354
358
  if (!this.isRke2 && (ext.group === _RKE2 || !ext.group)) {
355
359
  return;
356
360
  }
361
+ // Do not show the extension provisioner on the import cluster page unless its explicitly set to do so
362
+ if (isImport && !ext.showImport) {
363
+ return;
364
+ }
365
+ // Allow extensions to overwrite provisioners with the same id
366
+ out = out.filter((type) => type.id !== ext.id);
357
367
  addExtensionType(ext, getters);
358
368
  });
359
369
  }
@@ -386,7 +396,7 @@ export default {
386
396
  out.push(subtype);
387
397
  }
388
398
 
389
- function addType(id, group, disabled = false, emberLink = null, iconClass = undefined) {
399
+ function addType(id, group, disabled = false, emberLink = null, iconClass = undefined, providerConfig = undefined) {
390
400
  const label = getters['i18n/withFallback'](`cluster.provider."${ id }"`, null, id);
391
401
  const description = getters['i18n/withFallback'](`cluster.providerDescription."${ id }"`, null, '');
392
402
  const tag = '';
@@ -412,7 +422,8 @@ export default {
412
422
  group,
413
423
  disabled,
414
424
  emberLink,
415
- tag
425
+ tag,
426
+ providerConfig
416
427
  };
417
428
 
418
429
  out.push(subtype);
@@ -632,6 +643,7 @@ export default {
632
643
  :live-value="liveValue"
633
644
  :mode="mode"
634
645
  :provider="subType"
646
+ :provider-config="selectedSubType.providerConfig"
635
647
  />
636
648
  <Rke2Config
637
649
  v-else
@@ -640,6 +652,7 @@ export default {
640
652
  :live-value="liveValue"
641
653
  :mode="mode"
642
654
  :provider="subType"
655
+ :provider-config="selectedSubType.providerConfig"
643
656
  />
644
657
  </template>
645
658
 
@@ -126,6 +126,11 @@ export default {
126
126
  type: String,
127
127
  required: true,
128
128
  },
129
+
130
+ providerConfig: {
131
+ type: Object,
132
+ default: () => null
133
+ }
129
134
  },
130
135
 
131
136
  async fetch() {
@@ -211,7 +216,6 @@ export default {
211
216
  machinePoolValidation: {}, // map of validation states for each machine pool
212
217
  machinePoolErrors: {},
213
218
  allNamespaces: [],
214
- initialCloudProvider: this.value?.agentConfig?.['cloud-provider-name'] || '',
215
219
  extensionTabs: getApplicableExtensionEnhancements(this, ExtensionPoint.TAB, TabLocation.CLUSTER_CREATE_RKE2, this.$route, this),
216
220
  };
217
221
  },
@@ -326,6 +330,14 @@ export default {
326
330
 
327
331
  const out = findBy(this.versionOptions, 'value', str);
328
332
 
333
+ // Adding the option 'none' to Container Network select (used in Basics component)
334
+ // https://github.com/rancher/dashboard/issues/10338
335
+ // there's an update loop on refresh that might include 'none'
336
+ // multiple times... Prevent that
337
+ if (out.serverArgs?.cni?.options && !out.serverArgs?.cni?.options.includes('none')) {
338
+ out.serverArgs.cni.options.push('none');
339
+ }
340
+
329
341
  return out;
330
342
  },
331
343
 
@@ -351,7 +363,7 @@ export default {
351
363
  },
352
364
 
353
365
  needCredential() {
354
- if ( this.provider === 'custom' || this.provider === 'import' || this.isElementalCluster || this.mode === _VIEW ) {
366
+ if ( this.provider === 'custom' || this.provider === 'import' || this.isElementalCluster || this.mode === _VIEW || (this.providerConfig?.spec?.builtin === false && this.providerConfig?.spec?.addCloudCredential === false) ) {
355
367
  return false;
356
368
  }
357
369
 
@@ -577,30 +589,8 @@ export default {
577
589
 
578
590
  const cur = this.agentConfig?.['cloud-provider-name'];
579
591
 
580
- if (cur && !out.find((x) => x.value === cur)) {
581
- // Localization missing
582
- // Look up cur in the localization file
583
- const label = this.$store.getters['i18n/withFallback'](`cluster.cloudProvider."${ cur }".label`, null, cur);
584
-
585
- out.unshift({
586
- label: `${ label } (Current)`,
587
- value: cur,
588
- unsupported: true,
589
- disabled: true
590
- });
591
- }
592
-
593
- const initial = this.initialCloudProvider;
594
-
595
- if (cur !== initial && initial && !out.find((x) => x.value === initial)) {
596
- const label = this.$store.getters['i18n/withFallback'](`cluster.cloudProvider."${ initial }".label`, null, initial);
597
-
598
- out.unshift({
599
- label: `${ label } (Current)`,
600
- value: initial,
601
- unsupported: true,
602
- disabled: true
603
- });
592
+ if ( cur && !out.find((x) => x.value === cur) ) {
593
+ out.unshift({ label: `${ cur } (Current)`, value: cur });
604
594
  }
605
595
 
606
596
  return out;
@@ -708,14 +698,6 @@ export default {
708
698
 
709
699
  return validRequiredPools && base;
710
700
  },
711
- unsupportedCloudProvider() {
712
- // The current cloud provider
713
- const cur = this.initialCloudProvider;
714
-
715
- const provider = cur && this.cloudProviderOptions.find((x) => x.value === cur);
716
-
717
- return !!provider?.unsupported;
718
- },
719
701
  },
720
702
 
721
703
  watch: {
@@ -1431,7 +1413,9 @@ export default {
1431
1413
  for ( const chartName of this.addonNames ) {
1432
1414
  const entry = this.chartVersions[chartName];
1433
1415
 
1434
- if ( this.versionInfo[chartName] ) {
1416
+ // prevent fetching of addon config for 'none' CNI option
1417
+ // https://github.com/rancher/dashboard/issues/10338
1418
+ if ( this.versionInfo[chartName] || chartName.includes('none')) {
1435
1419
  continue;
1436
1420
  }
1437
1421
 
@@ -1568,8 +1552,7 @@ export default {
1568
1552
  set(regs, 'mirrors', {});
1569
1553
  }
1570
1554
 
1571
- const hostname = Object.keys(regs.configs)[0];
1572
- const config = regs.configs[hostname];
1555
+ const config = regs.configs[this.registryHost];
1573
1556
 
1574
1557
  if ( config ) {
1575
1558
  registrySecret = config.authConfigSecretName;
@@ -1866,18 +1849,6 @@ export default {
1866
1849
  if (this.isHarvesterDriver && this.mode === _CREATE && this.isHarvesterIncompatible) {
1867
1850
  this.setHarvesterDefaultCloudProvider();
1868
1851
  }
1869
-
1870
- // Cloud Provider check
1871
- // If the cloud provider is unsupported, switch provider to 'external'
1872
- if (this.unsupportedCloudProvider) {
1873
- set(this.agentConfig, 'cloud-provider-name', 'external');
1874
- } else {
1875
- // Switch the cloud provider back to the initial value
1876
- // Use changed the Kubernetes version back to a version where the initial cloud provider is valid - so switch back to this one
1877
- // to undo the change to external that we may have made
1878
- // Note: Cloud Provider can only be changed on edit when the initial provider is no longer supported
1879
- set(this.agentConfig, 'cloud-provider-name', this.initialCloudProvider);
1880
- }
1881
1852
  }
1882
1853
  },
1883
1854
 
@@ -2048,11 +2019,13 @@ export default {
2048
2019
  :cancel="cancelCredential"
2049
2020
  :showing-form="showForm"
2050
2021
  :default-on-cancel="true"
2022
+ data-testid="select-credential"
2051
2023
  class="mt-20"
2052
2024
  />
2053
2025
 
2054
2026
  <div
2055
2027
  v-if="showForm"
2028
+ data-testid="form"
2056
2029
  class="mt-20"
2057
2030
  >
2058
2031
  <NameNsDescription
@@ -2182,7 +2155,6 @@ export default {
2182
2155
  :have-arg-info="haveArgInfo"
2183
2156
  :show-cni="showCni"
2184
2157
  :show-cloud-provider="showCloudProvider"
2185
- :unsupported-cloud-provider="unsupportedCloudProvider"
2186
2158
  :cloud-provider-options="cloudProviderOptions"
2187
2159
  @cilium-values-changed="handleCiliumValuesChanged"
2188
2160
  @enabled-system-services-changed="handleEnabledSystemServicesChanged"
@@ -103,10 +103,6 @@ export default {
103
103
  type: Boolean,
104
104
  required: true
105
105
  },
106
- unsupportedCloudProvider: {
107
- type: Boolean,
108
- required: true
109
- },
110
106
  cloudProviderOptions: {
111
107
  type: Array,
112
108
  required: true
@@ -369,7 +365,7 @@ export default {
369
365
  },
370
366
 
371
367
  canNotEditCloudProvider() {
372
- const canNotEdit = this.isEdit && !this.unsupportedCloudProvider;
368
+ const canNotEdit = this.isEdit;
373
369
 
374
370
  return canNotEdit;
375
371
  },
@@ -421,12 +417,20 @@ export default {
421
417
  >
422
418
  <span v-clean-html="t('cluster.banner.cloudProviderAddConfig', {}, true)" />
423
419
  </Banner>
420
+ <Banner
421
+ v-if="serverConfig.cni === 'none'"
422
+ color="warning"
423
+ data-testid="clusterBasics__noneOptionSelectedForCni"
424
+ >
425
+ <span v-clean-html="t('cluster.rke2.cni.cniNoneBanner', {}, true)" />
426
+ </Banner>
424
427
  <div class="row mb-10">
425
428
  <div class="col span-6">
426
429
  <LabeledSelect
427
430
  v-model="value.spec.kubernetesVersion"
428
431
  :mode="mode"
429
432
  :options="versionOptions"
433
+ data-testid="clusterBasics__kubernetesVersions"
430
434
  label-key="cluster.kubernetesVersion.label"
431
435
  @input="$emit('kubernetes-changed', $event)"
432
436
  />
@@ -495,12 +499,6 @@ export default {
495
499
  <div class="spacer" />
496
500
 
497
501
  <div class="col span-12">
498
- <Banner
499
- v-if="unsupportedCloudProvider"
500
- class="error mt-5"
501
- >
502
- {{ t('cluster.rke2.cloudProvider.unsupported') }}
503
- </Banner>
504
502
  <h3>
505
503
  {{ t('cluster.rke2.cloudProvider.header') }}
506
504
  </h3>
@@ -138,13 +138,14 @@ export default {
138
138
  :mode="mode"
139
139
  @input="update"
140
140
  >
141
- <template #default="{row}">
141
+ <template #default="{row, i}">
142
142
  <div class="row">
143
143
  <div class="col span-6">
144
144
  <LabeledInput
145
145
  v-model="row.value.hostname"
146
146
  label="Registry Hostname"
147
147
  :mode="mode"
148
+ :data-testid="`registry-auth-host-input-${i}`"
148
149
  />
149
150
 
150
151
  <SelectOrCreateAuthSecret
@@ -158,6 +159,7 @@ export default {
158
159
  :namespace="value.metadata.namespace"
159
160
  :mode="mode"
160
161
  generate-name="registryconfig-auth-"
162
+ :data-testid="`registry-auth-select-or-create-${i}`"
161
163
  />
162
164
  </div>
163
165
  <div class="col span-6">
@@ -79,6 +79,7 @@ export default {
79
79
  :value="showCustomRegistryInput"
80
80
  class="mb-20"
81
81
  :label="t('cluster.privateRegistry.label')"
82
+ data-testid="registries-enable-checkbox"
82
83
  @input="$emit('custom-registry-changed', $event)"
83
84
  />
84
85
  </div>
@@ -92,6 +93,7 @@ export default {
92
93
  label-key="catalog.chart.registry.custom.inputLabel"
93
94
  placeholder-key="catalog.chart.registry.custom.placeholder"
94
95
  :min-height="30"
96
+ data-testid="registry-host-input"
95
97
  @input="$emit('registry-host-changed', $event)"
96
98
  />
97
99
  <SelectOrCreateAuthSecret
@@ -118,6 +120,7 @@ export default {
118
120
  class="col span-12 advanced"
119
121
  :is-open-by-default="showCustomRegistryAdvancedInput"
120
122
  :mode="mode"
123
+ data-testid="registries-advanced-section"
121
124
  >
122
125
  <Banner
123
126
  :closable="false"
package/edit/token.vue CHANGED
@@ -199,6 +199,7 @@ export default {
199
199
  <RadioGroup
200
200
  v-model="form.expiryType"
201
201
  :options="expiryOptions"
202
+ data-testid="expiry__options"
202
203
  class="mr-20"
203
204
  name="expiryGroup"
204
205
  />
@@ -39,6 +39,7 @@ export default {
39
39
  :loading="loading"
40
40
  :use-query-params-for-simple-filtering="useQueryParamsForSimpleFiltering"
41
41
  :force-update-live-and-delayed="forceUpdateLiveAndDelayed"
42
+ data-testid="installed-app-catalog-list"
42
43
  >
43
44
  <template #cell:upgrade="{row}">
44
45
  <span
@@ -92,6 +92,7 @@ export default {
92
92
  v-for="setting in settings"
93
93
  :key="setting.id"
94
94
  class="advanced-setting mb-20"
95
+ :data-testid="`advanced-setting__option-${setting.id}`"
95
96
  >
96
97
  <div class="header">
97
98
  <div class="title">
@@ -475,6 +475,7 @@ export default {
475
475
  :disabled="disabled"
476
476
  :placeholder="t('cluster.machineConfig.amazonEc2.selectedNetwork.placeholder')"
477
477
  :label="t('cluster.machineConfig.amazonEc2.selectedNetwork.label')"
478
+ data-testid="amazonEc2__selectedNetwork"
478
479
  option-key="value"
479
480
  @input="updateNetwork($event)"
480
481
  >
@@ -26,6 +26,56 @@ describe('class ProvCluster', () => {
26
26
  clusterName: 'test', provisioner: 'rke2', mgmt: { isLocal: false, providerForEmberParam: 'import' }, spec: { rkeConfig: {} }
27
27
  };
28
28
 
29
+ const gkeClusterWithPrivateEndpoint = {
30
+ clusterName: 'test',
31
+ provisioner: 'GKE',
32
+ spec: { rkeConfig: {} },
33
+ mgmt: { spec: { gkeConfig: { privateClusterConfig: { enablePrivateEndpoint: true } } } }
34
+ };
35
+
36
+ const eksClusterWithPrivateEndpoint = {
37
+ clusterName: 'test',
38
+ provisioner: 'EKS',
39
+ spec: { rkeConfig: {} },
40
+ mgmt: { spec: { eksConfig: { privateAccess: true } } }
41
+ };
42
+
43
+ const aksClusterWithPrivateEndpoint = {
44
+ clusterName: 'test',
45
+ provisioner: 'AKS',
46
+ spec: { rkeConfig: {} },
47
+ mgmt: { spec: { aksConfig: { privateCluster: true } } }
48
+ };
49
+
50
+ // Related to https://github.com/rancher/dashboard/issues/9402
51
+ describe('isHostedKubernetesProvider + isPrivateHostedProvider', () => {
52
+ const testCases = [
53
+ [gkeClusterWithPrivateEndpoint, true],
54
+ [eksClusterWithPrivateEndpoint, true],
55
+ [aksClusterWithPrivateEndpoint, true],
56
+ ];
57
+ const resetMocks = () => {
58
+ // Clear all mock function calls:
59
+ jest.clearAllMocks();
60
+ };
61
+
62
+ it.each(testCases)('should return the isHostedKubernetesProvider and isPrivateHostedProvider values properly based on the props data', (clusterData: Object, expected: Boolean) => {
63
+ const cluster = new ProvCluster({ spec: clusterData.spec });
64
+
65
+ jest.spyOn(cluster, 'mgmt', 'get').mockReturnValue(
66
+ clusterData.mgmt
67
+ );
68
+ jest.spyOn(cluster, 'provisioner', 'get').mockReturnValue(
69
+ clusterData.provisioner
70
+ );
71
+
72
+ expect(cluster.isRke2).toBe(expected);
73
+ expect(cluster.isHostedKubernetesProvider).toBe(expected);
74
+ expect(cluster.isPrivateHostedProvider).toBe(expected);
75
+ resetMocks();
76
+ });
77
+ });
78
+
29
79
  describe('isImported', () => {
30
80
  const testCases = [
31
81
  [importedClusterInfo, true],
@@ -84,6 +134,107 @@ describe('class ProvCluster', () => {
84
134
 
85
135
  expect(cluster.mgmt).toBe(expected);
86
136
  resetMocks();
137
+ });
138
+ });
139
+
140
+ describe('hasError', () => {
141
+ const conditionsWithoutError = [
142
+ {
143
+ error: false,
144
+ lastUpdateTime: '2022-10-17T23:09:15Z',
145
+ status: 'True',
146
+ transitioning: false,
147
+ type: 'Ready'
148
+ },
149
+ ];
150
+
151
+ const conditionsWithoutReady = [
152
+ {
153
+ error: true,
154
+ lastUpdateTime: '2022-10-17T23:09:15Z',
155
+ status: 'False',
156
+ message: 'some-error-message',
157
+ transitioning: false,
158
+ type: 'Pending'
159
+ },
160
+ ];
161
+
162
+ const noConditions:[] = [];
163
+
164
+ const conditionsWithReadyLatest = [
165
+ {
166
+ error: true,
167
+ lastUpdateTime: '2022-10-17T23:09:15Z',
168
+ status: 'False',
169
+ message: 'some-error-message',
170
+ transitioning: false,
171
+ type: 'Pending'
172
+ },
173
+ {
174
+ error: false,
175
+ lastUpdateTime: '2023-10-17T23:09:15Z',
176
+ status: 'True',
177
+ transitioning: false,
178
+ type: 'Ready'
179
+ }
180
+ ];
181
+
182
+ const conditionsWithErrorLatest = [
183
+ {
184
+ error: false,
185
+ lastUpdateTime: '2022-10-17T23:09:15Z',
186
+ status: 'True',
187
+ transitioning: false,
188
+ type: 'Ready'
189
+ },
190
+ {
191
+ error: true,
192
+ lastUpdateTime: '2023-10-17T23:09:15Z',
193
+ status: 'False',
194
+ message: 'some-error-message',
195
+ transitioning: false,
196
+ type: 'Pending'
197
+ }
198
+ ];
199
+
200
+ const conditionsWithProblemInLastUpdateTimeProp = [
201
+ {
202
+ error: true,
203
+ lastUpdateTime: '',
204
+ status: 'False',
205
+ message: 'some-error-message',
206
+ transitioning: false,
207
+ type: 'Pending'
208
+ },
209
+ {
210
+ error: false,
211
+ lastUpdateTime: '2023-10-17T23:09:15Z',
212
+ status: 'True',
213
+ transitioning: false,
214
+ type: 'Ready'
215
+ }
216
+ ];
217
+
218
+ const testCases = [
219
+ ['conditionsWithoutError', conditionsWithoutError, false],
220
+ ['conditionsWithoutReady', conditionsWithoutReady, true],
221
+ ['noConditions', noConditions, false],
222
+ ['conditionsWithReadyLatest', conditionsWithReadyLatest, false],
223
+ ['conditionsWithErrorLatest', conditionsWithErrorLatest, true],
224
+ ['conditionsWithProblemInLastUpdateTimeProp', conditionsWithProblemInLastUpdateTimeProp, false],
225
+ ];
226
+
227
+ const resetMocks = () => {
228
+ // Clear all mock function calls
229
+ jest.clearAllMocks();
230
+ };
231
+
232
+ it.each(testCases)('should return the hasError value properly based on the "status.conditions" props data for testcase %p', (testName: string, conditions: Array, expected: Boolean) => {
233
+ const ctx = { rootGetters: { 'management/byId': jest.fn() } };
234
+ const cluster = new ProvCluster({ status: { conditions } }, ctx);
235
+
236
+ expect(cluster.hasError).toBe(expected);
237
+ resetMocks();
87
238
  }
88
239
  );
89
240
  });
@@ -0,0 +1,37 @@
1
+ import Secret from '@shell/models/secret';
2
+
3
+ describe('class Secret', () => {
4
+ it('should contains the type attribute if cleanForDownload', async() => {
5
+ const secret = new Secret({});
6
+ const yaml = `apiVersion: v1
7
+ kind: Secret
8
+ metadata:
9
+ name: my-secret
10
+ type: Opaque
11
+ `;
12
+ const cleanYaml = await secret.cleanForDownload(yaml);
13
+
14
+ expect(cleanYaml).toBe(yaml);
15
+ });
16
+
17
+ it('should remove id, links and actions keys if cleanForDownload', async() => {
18
+ const secret = new Secret({});
19
+ const expectedYamlStr = `apiVersion: v1
20
+ kind: Secret
21
+ metadata:
22
+ name: my-secret
23
+ namespace: default
24
+ type: Opaque
25
+ `;
26
+ const part = `id: test_id
27
+ links:
28
+ view: https://example.com
29
+ actions:
30
+ remove: https://example.com`;
31
+ const yaml = `${ expectedYamlStr }
32
+ ${ part }`;
33
+ const cleanYaml = await secret.cleanForDownload(yaml);
34
+
35
+ expect(cleanYaml).toBe(expectedYamlStr);
36
+ });
37
+ });
@@ -0,0 +1,22 @@
1
+ import StorageClass, { PROVISIONER_OPTIONS } from '@shell/models/storage.k8s.io.storageclass';
2
+
3
+ describe('class StorageClass', () => {
4
+ describe('checking if provisionerDisplay', () => {
5
+ it.each([
6
+ ['kubernetes.io/azure-disk', true],
7
+ ['kubernetes.io/portworx-volume', true],
8
+ ['rancher.io/local-path', false],
9
+ ['some-random-string-as-provisioner', false],
10
+ ])('should NOT show a suffix IF they are built-in (on the PROVISIONER_OPTIONS list)', (provisioner, expectation) => {
11
+ const storageClass = new StorageClass({
12
+ metadata: {},
13
+ spec: {},
14
+ provisioner
15
+ });
16
+
17
+ jest.spyOn(storageClass, '$rootGetters', 'get').mockReturnValue({ 'i18n/t': jest.fn() });
18
+
19
+ expect(!!PROVISIONER_OPTIONS.find((opt) => opt.value === provisioner)).toBe(expectation);
20
+ });
21
+ });
22
+ });
@@ -1,6 +1,7 @@
1
1
  import HybridModel from '@shell/plugins/steve/hybrid-class';
2
2
 
3
- const HIDDEN = ['rke', 'rancherkubernetesengine', 'azureaks'];
3
+ const HIDDEN = ['rke', 'rancherkubernetesengine'];
4
+
4
5
  const V2 = ['amazoneks', 'googlegke', 'azureaks'];
5
6
  const IMPORTABLE = ['amazoneks', 'googlegke', 'azureaks'];
6
7