@rancher/shell 3.0.9-rc.5 → 3.0.9

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 (172) hide show
  1. package/assets/images/providers/oci-open-containers.svg +22 -0
  2. package/assets/images/providers/traefik.png +0 -0
  3. package/assets/styles/themes/_dark.scss +2 -0
  4. package/assets/styles/themes/_light.scss +2 -0
  5. package/assets/styles/themes/_modern.scss +6 -0
  6. package/assets/translations/en-us.yaml +129 -25
  7. package/components/CruResource.vue +3 -1
  8. package/components/ExplorerProjectsNamespaces.vue +12 -12
  9. package/components/IconOrSvg.vue +61 -42
  10. package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +109 -0
  11. package/components/Resource/Detail/Card/StatusCard/index.vue +21 -4
  12. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +19 -2
  13. package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +19 -11
  14. package/components/Resource/Detail/ResourcePopover/__tests__/index.test.ts +12 -0
  15. package/components/Resource/Detail/ResourcePopover/index.vue +2 -0
  16. package/components/Resource/Detail/ResourceRow.vue +2 -2
  17. package/components/ResourceList/index.vue +7 -4
  18. package/components/SortableTable/index.vue +2 -2
  19. package/components/Window/ContainerLogs.vue +48 -37
  20. package/components/fleet/FleetClusterTargets/TargetsList.vue +2 -2
  21. package/components/fleet/FleetClusterTargets/index.vue +6 -1
  22. package/components/fleet/GitRepoAdvancedTab.vue +333 -0
  23. package/components/fleet/GitRepoMetadataTab.vue +43 -0
  24. package/components/fleet/GitRepoRepositoryTab.vue +101 -0
  25. package/components/fleet/GitRepoTargetTab.vue +77 -0
  26. package/components/fleet/HelmOpAdvancedTab.vue +247 -0
  27. package/components/fleet/HelmOpChartTab.vue +158 -0
  28. package/components/fleet/HelmOpMetadataTab.vue +46 -0
  29. package/components/fleet/HelmOpTargetTab.vue +84 -0
  30. package/components/fleet/HelmOpValuesTab.vue +147 -0
  31. package/components/fleet/__tests__/FleetClusterTargets.test.ts +119 -70
  32. package/components/form/BannerSettings.vue +2 -2
  33. package/components/form/NodeScheduling.vue +81 -7
  34. package/components/form/NotificationSettings.vue +2 -2
  35. package/components/form/PodAffinity.vue +1 -36
  36. package/components/form/ResourceLabeledSelect.vue +8 -4
  37. package/components/form/ResourceQuota/Namespace.vue +30 -9
  38. package/components/form/ResourceQuota/NamespaceRow.vue +25 -7
  39. package/components/form/ResourceQuota/Project.vue +140 -82
  40. package/components/form/ResourceQuota/ResourceQuotaEntry.vue +145 -0
  41. package/components/form/ResourceQuota/__tests__/Namespace.test.ts +307 -0
  42. package/components/form/ResourceQuota/__tests__/NamespaceRow.test.ts +281 -0
  43. package/components/form/ResourceQuota/__tests__/Project.test.ts +274 -27
  44. package/components/form/ResourceQuota/__tests__/ResourceQuotaEntry.test.ts +215 -0
  45. package/components/form/SchedulingCustomization.vue +14 -6
  46. package/components/form/SelectOrCreateAuthSecret.vue +107 -18
  47. package/components/form/__tests__/NodeScheduling.test.ts +12 -9
  48. package/components/form/__tests__/PodAffinity.test.ts +21 -2
  49. package/components/form/__tests__/SchedulingCustomization.test.ts +240 -0
  50. package/components/formatter/ClusterLink.vue +8 -0
  51. package/components/formatter/SecretOrigin.vue +79 -0
  52. package/config/labels-annotations.js +7 -6
  53. package/config/pagination-table-headers.js +6 -4
  54. package/config/product/explorer.js +1 -11
  55. package/config/product/manager.js +0 -1
  56. package/config/query-params.js +3 -0
  57. package/config/settings.ts +15 -2
  58. package/config/table-headers.js +21 -17
  59. package/config/types.js +23 -8
  60. package/detail/fleet.cattle.io.cluster.vue +1 -1
  61. package/detail/workload/index.vue +11 -16
  62. package/dialog/DeactivateDriverDialog.vue +1 -1
  63. package/dialog/FeatureFlagListDialog.vue +1 -1
  64. package/dialog/Ipv6NetworkingDialog.vue +156 -0
  65. package/dialog/ScalePoolDownDialog.vue +2 -2
  66. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -1
  67. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +1 -0
  68. package/edit/__tests__/management.cattle.io.project.test.js +56 -128
  69. package/edit/auth/oidc.vue +1 -1
  70. package/edit/catalog.cattle.io.clusterrepo.vue +155 -25
  71. package/edit/fleet.cattle.io.gitrepo.vue +153 -283
  72. package/edit/fleet.cattle.io.helmop.vue +190 -332
  73. package/edit/management.cattle.io.project.vue +5 -42
  74. package/edit/management.cattle.io.setting.vue +6 -0
  75. package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/auth.spec.ts +145 -0
  76. package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/index.test.ts +202 -0
  77. package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/tls.spec.ts +226 -0
  78. package/edit/monitoring.coreos.com.alertmanagerconfig/auth.vue +24 -21
  79. package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/opsgenie.spec.ts +157 -0
  80. package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/pagerduty.spec.ts +132 -0
  81. package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/slack.spec.ts +108 -0
  82. package/edit/monitoring.coreos.com.alertmanagerconfig/types/pagerduty.vue +2 -1
  83. package/edit/monitoring.coreos.com.receiver/__tests__/auth.spec.ts +165 -0
  84. package/edit/monitoring.coreos.com.receiver/__tests__/index.test.ts +153 -0
  85. package/edit/monitoring.coreos.com.receiver/__tests__/tls.spec.ts +115 -0
  86. package/edit/monitoring.coreos.com.receiver/types/__tests__/email.spec.ts +86 -0
  87. package/edit/monitoring.coreos.com.receiver/types/__tests__/opsgenie.spec.ts +209 -0
  88. package/edit/monitoring.coreos.com.receiver/types/__tests__/pagerduty.spec.ts +105 -0
  89. package/edit/monitoring.coreos.com.receiver/types/__tests__/slack.spec.ts +92 -0
  90. package/edit/monitoring.coreos.com.receiver/types/__tests__/webhook.spec.ts +131 -0
  91. package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +55 -24
  92. package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +1 -103
  93. package/edit/provisioning.cattle.io.cluster/__tests__/index.test.ts +13 -1
  94. package/edit/provisioning.cattle.io.cluster/__tests__/rke2-fleet-cluster-agent.test.ts +283 -0
  95. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +65 -49
  96. package/edit/provisioning.cattle.io.cluster/ingress/IngressCards.vue +114 -0
  97. package/edit/provisioning.cattle.io.cluster/ingress/IngressConfiguration.vue +158 -0
  98. package/edit/provisioning.cattle.io.cluster/rke2.vue +167 -69
  99. package/edit/provisioning.cattle.io.cluster/shared.ts +36 -1
  100. package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +2 -1
  101. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +70 -7
  102. package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +343 -0
  103. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +2 -1
  104. package/edit/provisioning.cattle.io.cluster/tabs/etcd/__tests__/S3Config.test.ts +13 -1
  105. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +10 -44
  106. package/edit/secret/index.vue +1 -1
  107. package/edit/token.vue +68 -29
  108. package/edit/workload/__tests__/index.test.ts +2 -37
  109. package/edit/workload/index.vue +6 -2
  110. package/edit/workload/mixins/workload.js +0 -32
  111. package/list/__tests__/management.cattle.io.setting.test.ts +198 -0
  112. package/list/management.cattle.io.setting.vue +13 -0
  113. package/list/provisioning.cattle.io.cluster.vue +50 -1
  114. package/list/secret.vue +4 -9
  115. package/list/service.vue +6 -8
  116. package/machine-config/amazonec2.vue +11 -4
  117. package/machine-config/components/EC2Networking.vue +46 -30
  118. package/machine-config/components/__tests__/EC2Networking.test.ts +7 -7
  119. package/machine-config/components/__tests__/utils/vpcSubnetMockData.js +0 -9
  120. package/machine-config/digitalocean.vue +3 -3
  121. package/models/__tests__/chart.test.ts +2 -2
  122. package/models/__tests__/namespace.test.ts +11 -0
  123. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +96 -0
  124. package/models/__tests__/workload.test.ts +42 -1
  125. package/models/catalog.cattle.io.clusterrepo.js +30 -4
  126. package/models/chart.js +3 -3
  127. package/models/ext.cattle.io.token.js +48 -0
  128. package/models/kontainerdriver.js +2 -2
  129. package/models/namespace.js +7 -1
  130. package/models/nodedriver.js +2 -2
  131. package/models/provisioning.cattle.io.cluster.js +28 -7
  132. package/models/secret.js +0 -17
  133. package/models/service.js +44 -1
  134. package/models/token.js +4 -0
  135. package/models/workload.js +12 -6
  136. package/package.json +1 -1
  137. package/pages/account/index.vue +96 -67
  138. package/pages/auth/setup.vue +5 -14
  139. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +45 -18
  140. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +4 -1
  141. package/pages/c/_cluster/apps/charts/index.vue +82 -3
  142. package/pages/c/_cluster/apps/charts/install.vue +317 -42
  143. package/pages/c/_cluster/explorer/tools/index.vue +1 -1
  144. package/pages/c/_cluster/manager/cloudCredential/index.vue +1 -1
  145. package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +5 -4
  146. package/pages/c/_cluster/settings/index.vue +3 -1
  147. package/pages/c/_cluster/uiplugins/index.vue +1 -1
  148. package/plugins/dashboard-store/__tests__/getters.test.ts +108 -0
  149. package/plugins/dashboard-store/__tests__/resource-class.test.ts +27 -0
  150. package/plugins/dashboard-store/actions.js +3 -8
  151. package/plugins/dashboard-store/getters.js +7 -5
  152. package/plugins/dashboard-store/mutations.js +4 -1
  153. package/plugins/dashboard-store/resource-class.js +3 -3
  154. package/plugins/steve/__tests__/steve-class.test.ts +102 -141
  155. package/plugins/steve/steve-class.js +12 -3
  156. package/plugins/steve/steve-pagination-utils.ts +6 -2
  157. package/rancher-components/RcIcon/types.ts +2 -0
  158. package/rancher-components/RcItemCard/RcItemCard.vue +72 -20
  159. package/store/prefs.js +3 -0
  160. package/types/aws-sdk.d.ts +121 -0
  161. package/types/resources/node.ts +15 -0
  162. package/types/shell/index.d.ts +537 -506
  163. package/types/store/pagination.types.ts +5 -5
  164. package/utils/__tests__/array.test.ts +1 -29
  165. package/utils/__tests__/cluster-agent-configuration.test.ts +203 -0
  166. package/utils/array.ts +0 -11
  167. package/utils/aws.ts +21 -0
  168. package/utils/cluster.js +22 -2
  169. package/utils/selector-typed.ts +1 -1
  170. package/utils/svg-filter.js +4 -3
  171. package/components/__tests__/ProjectRow.test.ts +0 -206
  172. package/components/form/ResourceQuota/ProjectRow.vue +0 -277
@@ -14,50 +14,297 @@ describe('project', () => {
14
14
  types: []
15
15
  };
16
16
 
17
- it('should emit validationChanged with false when validateTypes(false) is called (e.g., when adding a row)', () => {
18
- const wrapper: any = shallowMount(Project, { props: defaultProps });
17
+ describe('created() spec parser', () => {
18
+ it('should initialise resourceQuotas as an empty array when spec limits are empty', () => {
19
+ const wrapper: any = shallowMount(Project, { props: defaultProps });
19
20
 
20
- wrapper.vm.validateTypes(false);
21
+ expect(wrapper.vm.resourceQuotas).toStrictEqual([]);
22
+ });
23
+
24
+ it('should parse a standard resource type from the spec into resourceQuotas', () => {
25
+ const wrapper: any = shallowMount(Project, {
26
+ props: {
27
+ ...defaultProps,
28
+ value: {
29
+ spec: {
30
+ resourceQuota: { limit: { configMaps: '20' } },
31
+ namespaceDefaultResourceQuota: { limit: { configMaps: '10' } }
32
+ }
33
+ }
34
+ }
35
+ });
36
+
37
+ expect(wrapper.vm.resourceQuotas).toHaveLength(1);
38
+ expect(wrapper.vm.resourceQuotas[0]).toMatchObject({
39
+ resourceType: 'configMaps',
40
+ resourceIdentifier: 'configMaps',
41
+ projectLimit: '20',
42
+ namespaceDefaultLimit: '10',
43
+ });
44
+ });
45
+
46
+ it('should parse an extended resource type from the spec into resourceQuotas', () => {
47
+ const wrapper: any = shallowMount(Project, {
48
+ props: {
49
+ ...defaultProps,
50
+ value: {
51
+ spec: {
52
+ resourceQuota: { limit: { extended: { test1: '10' } } },
53
+ namespaceDefaultResourceQuota: { limit: { extended: { test1: '5' } } }
54
+ }
55
+ }
56
+ }
57
+ });
58
+
59
+ expect(wrapper.vm.resourceQuotas).toHaveLength(1);
60
+ expect(wrapper.vm.resourceQuotas[0]).toMatchObject({
61
+ resourceType: TYPES.EXTENDED,
62
+ resourceIdentifier: 'test1',
63
+ projectLimit: '10',
64
+ namespaceDefaultLimit: '5',
65
+ });
66
+ });
67
+
68
+ it('should initialise resourceQuotas as an empty array when resourceQuota exists but has no limit property', () => {
69
+ const wrapper: any = shallowMount(Project, {
70
+ props: {
71
+ ...defaultProps,
72
+ value: {
73
+ spec: {
74
+ resourceQuota: {},
75
+ namespaceDefaultResourceQuota: { limit: {} }
76
+ }
77
+ }
78
+ }
79
+ });
80
+
81
+ expect(wrapper.vm.resourceQuotas).toStrictEqual([]);
82
+ });
83
+
84
+ it('should parse project quotas and fall back to empty string for missing ns limits when namespaceDefaultResourceQuota has no limit property', () => {
85
+ const wrapper: any = shallowMount(Project, {
86
+ props: {
87
+ ...defaultProps,
88
+ value: {
89
+ spec: {
90
+ resourceQuota: { limit: { configMaps: '20' } },
91
+ namespaceDefaultResourceQuota: {}
92
+ }
93
+ }
94
+ }
95
+ });
21
96
 
22
- expect(wrapper.emitted('validationChanged')).toBeTruthy();
23
- expect(wrapper.emitted('validationChanged')[0]).toStrictEqual([false]);
97
+ expect(wrapper.vm.resourceQuotas).toHaveLength(1);
98
+ expect(wrapper.vm.resourceQuotas[0]).toMatchObject({
99
+ resourceType: 'configMaps',
100
+ projectLimit: '20',
101
+ namespaceDefaultLimit: '',
102
+ });
103
+ });
104
+
105
+ it('should parse both standard and extended resource types into resourceQuotas', () => {
106
+ const wrapper: any = shallowMount(Project, {
107
+ props: {
108
+ ...defaultProps,
109
+ value: {
110
+ spec: {
111
+ resourceQuota: { limit: { configMaps: '20', extended: { test1: '10' } } },
112
+ namespaceDefaultResourceQuota: { limit: { configMaps: '15', extended: { test1: '8' } } }
113
+ }
114
+ }
115
+ }
116
+ });
117
+
118
+ expect(wrapper.vm.resourceQuotas).toHaveLength(2);
119
+ });
24
120
  });
25
121
 
26
- it('should emit validationChanged with false if an extended type has no resource identifier', () => {
27
- const wrapper: any = shallowMount(Project, { props: defaultProps });
122
+ describe('specFromQuotas()', () => {
123
+ it('should convert a standard resourceQuota entry to projectLimit and nsLimit', () => {
124
+ const wrapper: any = shallowMount(Project, { props: defaultProps });
125
+
126
+ wrapper.setData({
127
+ resourceQuotas: [{
128
+ id: '1',
129
+ resourceType: 'configMaps',
130
+ resourceIdentifier: 'configMaps',
131
+ projectLimit: '20',
132
+ namespaceDefaultLimit: '10',
133
+ }]
134
+ });
135
+
136
+ expect(wrapper.vm.specFromQuotas()).toStrictEqual({
137
+ projectLimit: { configMaps: '20' },
138
+ nsLimit: { configMaps: '10' },
139
+ });
140
+ });
141
+
142
+ it('should convert an extended resourceQuota entry to a nested extended object', () => {
143
+ const wrapper: any = shallowMount(Project, { props: defaultProps });
144
+
145
+ wrapper.setData({
146
+ resourceQuotas: [{
147
+ id: '1',
148
+ resourceType: TYPES.EXTENDED,
149
+ resourceIdentifier: 'my-resource',
150
+ projectLimit: '5',
151
+ namespaceDefaultLimit: '3',
152
+ }]
153
+ });
154
+
155
+ expect(wrapper.vm.specFromQuotas()).toStrictEqual({
156
+ projectLimit: { extended: { 'my-resource': '5' } },
157
+ nsLimit: { extended: { 'my-resource': '3' } },
158
+ });
159
+ });
160
+
161
+ it('should omit an extended entry from the spec when resourceIdentifier is empty', () => {
162
+ const wrapper: any = shallowMount(Project, { props: defaultProps });
28
163
 
29
- wrapper.setData({ typeValues: [TYPES.EXTENDED] });
164
+ wrapper.setData({
165
+ resourceQuotas: [{
166
+ id: '1',
167
+ resourceType: TYPES.EXTENDED,
168
+ resourceIdentifier: '',
169
+ projectLimit: '5',
170
+ namespaceDefaultLimit: '3',
171
+ }]
172
+ });
30
173
 
31
- wrapper.vm.validateTypes(true);
174
+ const { projectLimit } = wrapper.vm.specFromQuotas();
32
175
 
33
- expect(wrapper.emitted('validationChanged')).toBeTruthy();
34
- expect(wrapper.emitted('validationChanged')[0]).toStrictEqual([false]);
176
+ expect(projectLimit.extended).toBeUndefined();
177
+ });
35
178
  });
36
179
 
37
- it('should emit validationChanged with true if an extended type has resource identifier', () => {
38
- const wrapper: any = shallowMount(Project, { props: defaultProps });
180
+ describe('remainingTypes()', () => {
181
+ const typesProps = [
182
+ {
183
+ value: TYPES.EXTENDED,
184
+ inputExponent: 0,
185
+ baseUnit: '',
186
+ labelKey: 'resourceQuota.custom',
187
+ placeholderKey: 'resourceQuota.projectLimit.unitlessPlaceholder'
188
+ },
189
+ {
190
+ value: 'configMaps',
191
+ inputExponent: 0,
192
+ baseUnit: '',
193
+ labelKey: 'resourceQuota.configMaps',
194
+ placeholderKey: 'resourceQuota.projectLimit.unitlessPlaceholder'
195
+ },
196
+ {
197
+ value: 'pods',
198
+ inputExponent: 0,
199
+ baseUnit: '',
200
+ labelKey: 'resourceQuota.pods',
201
+ placeholderKey: 'resourceQuota.projectLimit.unitlessPlaceholder'
202
+ }
203
+ ];
204
+
205
+ it('should exclude already-used non-extended types', () => {
206
+ const wrapper: any = shallowMount(Project, { props: { ...defaultProps, types: typesProps } });
207
+
208
+ wrapper.setData({
209
+ resourceQuotas: [{
210
+ id: '1',
211
+ resourceType: 'configMaps',
212
+ resourceIdentifier: 'configMaps',
213
+ projectLimit: '20',
214
+ namespaceDefaultLimit: '10',
215
+ }]
216
+ });
217
+
218
+ const values = wrapper.vm.remainingTypes('pods').map((t: any) => t.value);
219
+
220
+ expect(values).not.toContain('configMaps');
221
+ expect(values).toContain('pods');
222
+ expect(values).toContain(TYPES.EXTENDED);
223
+ });
224
+
225
+ it('should include the currentType even if it is already used', () => {
226
+ const wrapper: any = shallowMount(Project, { props: { ...defaultProps, types: typesProps } });
227
+
228
+ wrapper.setData({
229
+ resourceQuotas: [{
230
+ id: '1',
231
+ resourceType: 'configMaps',
232
+ resourceIdentifier: 'configMaps',
233
+ projectLimit: '20',
234
+ namespaceDefaultLimit: '10',
235
+ }]
236
+ });
237
+
238
+ const values = wrapper.vm.remainingTypes('configMaps').map((t: any) => t.value);
239
+
240
+ expect(values).toContain('configMaps');
241
+ });
39
242
 
40
- wrapper.setData({ typeValues: ['extended.my-resource'] });
243
+ it('should always include extended type', () => {
244
+ const wrapper: any = shallowMount(Project, { props: { ...defaultProps, types: typesProps } });
41
245
 
42
- wrapper.vm.validateTypes(true);
246
+ const values = wrapper.vm.remainingTypes('pods').map((t: any) => t.value);
43
247
 
44
- expect(wrapper.emitted('validationChanged')).toBeTruthy();
45
- expect(wrapper.emitted('validationChanged')[0]).toStrictEqual([true]);
248
+ expect(values).toContain(TYPES.EXTENDED);
249
+ });
46
250
  });
47
251
 
48
- it('should update typeValues and validate when updateResourceIdentifier is called', () => {
49
- const wrapper: any = shallowMount(Project, { props: defaultProps });
252
+ describe('validationChanged watcher', () => {
253
+ it('should emit validationChanged with false when an extended quota has no resource identifier', async() => {
254
+ const wrapper: any = shallowMount(Project, { props: defaultProps });
255
+
256
+ await wrapper.setData({
257
+ resourceQuotas: [{
258
+ id: '1',
259
+ resourceType: TYPES.EXTENDED,
260
+ resourceIdentifier: '',
261
+ projectLimit: '5',
262
+ namespaceDefaultLimit: '3',
263
+ }]
264
+ });
50
265
 
51
- wrapper.setData({ typeValues: [TYPES.EXTENDED] });
266
+ const emitted = wrapper.emitted('validationChanged');
52
267
 
53
- wrapper.vm.updateResourceIdentifier({
54
- type: TYPES.EXTENDED,
55
- customType: 'my-resource',
56
- index: 0
268
+ expect(emitted).toBeTruthy();
269
+ expect(emitted[emitted.length - 1]).toStrictEqual([false]);
57
270
  });
58
271
 
59
- expect(wrapper.vm.typeValues[0]).toStrictEqual('extended.my-resource');
60
- expect(wrapper.emitted('validationChanged')).toBeTruthy();
61
- expect(wrapper.emitted('validationChanged')[0]).toStrictEqual([true]);
272
+ it('should emit validationChanged with true when all extended quotas have resource identifiers', async() => {
273
+ const wrapper: any = shallowMount(Project, { props: defaultProps });
274
+
275
+ await wrapper.setData({
276
+ resourceQuotas: [{
277
+ id: '1',
278
+ resourceType: TYPES.EXTENDED,
279
+ resourceIdentifier: 'my-resource',
280
+ projectLimit: '5',
281
+ namespaceDefaultLimit: '3',
282
+ }]
283
+ });
284
+
285
+ const emitted = wrapper.emitted('validationChanged');
286
+
287
+ expect(emitted).toBeTruthy();
288
+ expect(emitted[emitted.length - 1]).toStrictEqual([true]);
289
+ });
290
+
291
+ it('should emit validationChanged with true when resourceQuotas has no extended entries', async() => {
292
+ const wrapper: any = shallowMount(Project, { props: defaultProps });
293
+
294
+ await wrapper.setData({
295
+ resourceQuotas: [{
296
+ id: '1',
297
+ resourceType: 'configMaps',
298
+ resourceIdentifier: 'configMaps',
299
+ projectLimit: '20',
300
+ namespaceDefaultLimit: '10',
301
+ }]
302
+ });
303
+
304
+ const emitted = wrapper.emitted('validationChanged');
305
+
306
+ expect(emitted).toBeTruthy();
307
+ expect(emitted[emitted.length - 1]).toStrictEqual([true]);
308
+ });
62
309
  });
63
310
  });
@@ -0,0 +1,215 @@
1
+ import { shallowMount } from '@vue/test-utils';
2
+ import { createStore } from 'vuex';
3
+ import ResourceQuotaEntry from '@shell/components/form/ResourceQuota/ResourceQuotaEntry.vue';
4
+ import { LabeledInput } from '@components/Form/LabeledInput';
5
+ import { RcButton } from '@components/RcButton';
6
+ import { TYPES } from '@shell/components/form/ResourceQuota/shared';
7
+
8
+ describe('component: ResourceQuotaEntry', () => {
9
+ const store = createStore({});
10
+
11
+ const mockType = {
12
+ value: 'configMaps',
13
+ inputExponent: 0,
14
+ baseUnit: '',
15
+ placeholder: 'Enter count',
16
+ increment: 1,
17
+ };
18
+
19
+ const podsType = {
20
+ value: 'pods',
21
+ inputExponent: 0,
22
+ baseUnit: '',
23
+ placeholder: 'Enter count',
24
+ increment: 1,
25
+ };
26
+
27
+ const limitsCpuType = {
28
+ value: 'limitsCpu',
29
+ inputExponent: -1,
30
+ baseUnit: '',
31
+ placeholder: 'Enter CPU',
32
+ increment: 1,
33
+ };
34
+
35
+ const extendedType = {
36
+ value: TYPES.EXTENDED,
37
+ inputExponent: 0,
38
+ baseUnit: '',
39
+ placeholder: 'Enter value',
40
+ increment: 1,
41
+ };
42
+
43
+ const allTypes = [mockType, podsType, limitsCpuType, extendedType];
44
+
45
+ const defaultProps = {
46
+ id: '1',
47
+ mode: 'edit',
48
+ types: allTypes,
49
+ resourceType: 'configMaps',
50
+ resourceIdentifier: 'configMaps',
51
+ projectLimit: '20',
52
+ namespaceDefaultLimit: '10',
53
+ };
54
+
55
+ const createWrapper = (propsOverrides: Record<string, unknown> = {}) => {
56
+ return shallowMount(ResourceQuotaEntry, {
57
+ props: { ...defaultProps, ...propsOverrides },
58
+ global: { provide: { store } },
59
+ });
60
+ };
61
+
62
+ describe('rendering', () => {
63
+ it('should render all input fields with their data-testid attributes', () => {
64
+ const wrapper = createWrapper();
65
+
66
+ expect(wrapper.find('[data-testid="projectrow-type-input"]').exists()).toBe(true);
67
+ expect(wrapper.find('[data-testid="projectrow-custom-type-input"]').exists()).toBe(true);
68
+ expect(wrapper.find('[data-testid="projectrow-project-quota-input"]').exists()).toBe(true);
69
+ expect(wrapper.find('[data-testid="projectrow-namespace-quota-input"]').exists()).toBe(true);
70
+ });
71
+ });
72
+
73
+ describe('computed: typeOption', () => {
74
+ it('should return the matching type option for a given resourceType', () => {
75
+ const wrapper = createWrapper({ resourceType: 'configMaps' });
76
+
77
+ expect((wrapper.vm as any).typeOption).toStrictEqual(mockType);
78
+ });
79
+
80
+ it('should return the extended type option when resourceType is TYPES.EXTENDED', () => {
81
+ const wrapper = createWrapper({ resourceType: TYPES.EXTENDED });
82
+
83
+ expect((wrapper.vm as any).typeOption).toStrictEqual(extendedType);
84
+ });
85
+ });
86
+
87
+ describe('computed: isCustom', () => {
88
+ it('should return true when resourceType equals TYPES.EXTENDED', () => {
89
+ const wrapper = createWrapper({ resourceType: TYPES.EXTENDED });
90
+
91
+ expect((wrapper.vm as any).isCustom).toBe(true);
92
+ });
93
+
94
+ it.each([
95
+ ['configMaps'],
96
+ ['pods'],
97
+ ['limitsCpu'],
98
+ ])('should return false when resourceType is "%s"', (resourceType) => {
99
+ const wrapper = createWrapper({ resourceType });
100
+
101
+ expect((wrapper.vm as any).isCustom).toBe(false);
102
+ });
103
+ });
104
+
105
+ describe('computed: customTypeRules', () => {
106
+ it('should return an empty array when resourceType is a standard type', () => {
107
+ const wrapper = createWrapper({ resourceType: 'configMaps' });
108
+
109
+ expect((wrapper.vm as any).customTypeRules).toStrictEqual([]);
110
+ });
111
+
112
+ it('should return one validation rule when resourceType is TYPES.EXTENDED', () => {
113
+ const wrapper = createWrapper({ resourceType: TYPES.EXTENDED });
114
+
115
+ expect((wrapper.vm as any).customTypeRules).toHaveLength(1);
116
+ });
117
+
118
+ describe('validation rule behavior for custom type', () => {
119
+ it.each([
120
+ ['', 'resourceQuota.errors.customTypeRequired'],
121
+ [null, 'resourceQuota.errors.customTypeRequired'],
122
+ [undefined, 'resourceQuota.errors.customTypeRequired'],
123
+ ])('should return an error message for falsy value "%s"', (value, expectedError) => {
124
+ const wrapper = createWrapper({ resourceType: TYPES.EXTENDED });
125
+ const [rule] = (wrapper.vm as any).customTypeRules;
126
+
127
+ expect(rule(value)).toBe(expectedError);
128
+ });
129
+
130
+ it.each([
131
+ ['my-resource'],
132
+ ['cpu'],
133
+ ['custom.resource/name'],
134
+ ])('should return undefined for non-empty value "%s"', (value) => {
135
+ const wrapper = createWrapper({ resourceType: TYPES.EXTENDED });
136
+ const [rule] = (wrapper.vm as any).customTypeRules;
137
+
138
+ expect(rule(value)).toBeUndefined();
139
+ });
140
+ });
141
+ });
142
+
143
+ describe('method: remove', () => {
144
+ it('should emit remove event with the resource quota id', () => {
145
+ const id = 'test-remove-id';
146
+ const wrapper = createWrapper({ id });
147
+
148
+ (wrapper.vm as any).remove(id);
149
+
150
+ expect(wrapper.emitted('remove')).toBeTruthy();
151
+ expect(wrapper.emitted('remove')![0]).toStrictEqual([id]);
152
+ });
153
+
154
+ it('should emit remove when the Remove button is clicked', async() => {
155
+ const id = 'click-test-id';
156
+ const wrapper = createWrapper({ id });
157
+
158
+ await wrapper.findComponent(RcButton as any).trigger('click');
159
+
160
+ expect(wrapper.emitted('remove')).toBeTruthy();
161
+ expect(wrapper.emitted('remove')![0]).toStrictEqual([id]);
162
+ });
163
+ });
164
+
165
+ describe('method: updateResourceIdentifier', () => {
166
+ it('should emit update:resourceIdentifier with the new type for standard types', () => {
167
+ const wrapper = createWrapper({ resourceType: 'configMaps' });
168
+
169
+ (wrapper.vm as any).updateResourceIdentifier('pods');
170
+
171
+ expect(wrapper.emitted('update:resourceIdentifier')).toBeTruthy();
172
+ expect(wrapper.emitted('update:resourceIdentifier')![0]).toStrictEqual(['pods']);
173
+ });
174
+
175
+ it('should not emit update:resourceIdentifier when called with TYPES.EXTENDED', () => {
176
+ const wrapper = createWrapper({ resourceType: TYPES.EXTENDED });
177
+
178
+ (wrapper.vm as any).updateResourceIdentifier(TYPES.EXTENDED);
179
+
180
+ expect(wrapper.emitted('update:resourceIdentifier')).toBeFalsy();
181
+ });
182
+ });
183
+
184
+ describe('template: LabeledInput disabled state', () => {
185
+ it('should have the identifier input disabled when resourceType is a standard type', () => {
186
+ const wrapper = createWrapper({ resourceType: 'configMaps' });
187
+ const input = wrapper.findComponent(LabeledInput as any);
188
+
189
+ expect(input.props('disabled')).toBe(true);
190
+ });
191
+
192
+ it('should not have the identifier input disabled when resourceType is TYPES.EXTENDED', () => {
193
+ const wrapper = createWrapper({ resourceType: TYPES.EXTENDED });
194
+ const input = wrapper.findComponent(LabeledInput as any);
195
+
196
+ expect(input.props('disabled')).toBe(false);
197
+ });
198
+ });
199
+
200
+ describe('template: LabeledInput required state', () => {
201
+ it('should have the identifier input required when resourceType is TYPES.EXTENDED', () => {
202
+ const wrapper = createWrapper({ resourceType: TYPES.EXTENDED });
203
+ const input = wrapper.findComponent(LabeledInput as any);
204
+
205
+ expect(input.props('required')).toBe(true);
206
+ });
207
+
208
+ it('should not have the identifier input required when resourceType is a standard type', () => {
209
+ const wrapper = createWrapper({ resourceType: 'configMaps' });
210
+ const input = wrapper.findComponent(LabeledInput as any);
211
+
212
+ expect(input.props('required')).toBe(false);
213
+ });
214
+ });
215
+ });
@@ -7,6 +7,10 @@ export default {
7
7
  components: { Checkbox, Banner },
8
8
  emits: ['scheduling-customization-changed'],
9
9
  props: {
10
+ type: {
11
+ type: String,
12
+ required: true,
13
+ },
10
14
  value: {
11
15
  type: Object,
12
16
  default: () => {},
@@ -26,6 +30,10 @@ export default {
26
30
  defaultPDB: {
27
31
  type: Object,
28
32
  default: () => {},
33
+ },
34
+ checkboxWithOnlyAgentName: {
35
+ type: Boolean,
36
+ default: false
29
37
  }
30
38
  },
31
39
  data() {
@@ -59,10 +67,10 @@ export default {
59
67
  <Checkbox
60
68
  :value="enabled"
61
69
  :mode="mode"
62
- label-key="cluster.agentConfig.subGroups.schedulingCustomization.label"
63
- descriptionKey="cluster.agentConfig.subGroups.schedulingCustomization.description"
70
+ :label="checkboxWithOnlyAgentName ? t(`cluster.agentConfig.subGroups.agentsScheduling.${type}`) : t('cluster.agentConfig.subGroups.agentsScheduling.label', { agent: t(`cluster.agentConfig.subGroups.agentsScheduling.${type}`)})"
71
+ :description="checkboxWithOnlyAgentName ? '' :t('cluster.agentConfig.subGroups.agentsScheduling.description', { agent: t(`cluster.agentConfig.subGroups.agentsScheduling.${type}`)})"
64
72
  data-testid="scheduling-customization-checkbox"
65
- @update:value="$emit('scheduling-customization-changed', $event)"
73
+ @update:value="$emit('scheduling-customization-changed', { event: $event, agentType: type })"
66
74
  >
67
75
  <template
68
76
  v-if="feature && isEdit && settingMissmatch"
@@ -71,13 +79,13 @@ export default {
71
79
  <Banner
72
80
  class="mt-10 mb-10"
73
81
  color="info"
74
- label-key="cluster.agentConfig.subGroups.schedulingCustomization.banner"
82
+ :label="t('cluster.agentConfig.subGroups.agentsScheduling.banner', { agent: t(`cluster.agentConfig.subGroups.agentsScheduling.${type}`)})"
75
83
  />
76
84
  <Checkbox
77
85
  :value="applyGlobal"
78
86
  :mode="mode"
79
- label-key="cluster.agentConfig.subGroups.schedulingCustomization.innerCheckbox"
80
- @update:value="$emit('scheduling-customization-changed', feature)"
87
+ :label="t('cluster.agentConfig.subGroups.agentsScheduling.innerCheckbox', { agent: t(`cluster.agentConfig.subGroups.agentsScheduling.${type}`)})"
88
+ @update:value="$emit('scheduling-customization-changed', { event: $event, agentType: type })"
81
89
  />
82
90
  </template>
83
91
  </Checkbox>