@rancher/shell 3.0.7 → 3.0.8-rc.1

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 (83) hide show
  1. package/assets/images/vendor/githubapp.svg +13 -0
  2. package/assets/styles/base/_typography.scss +1 -1
  3. package/assets/styles/themes/_modern.scss +5 -5
  4. package/assets/translations/en-us.yaml +91 -11
  5. package/assets/translations/zh-hans.yaml +0 -4
  6. package/components/Inactivity.vue +222 -106
  7. package/components/InstallHelmCharts.vue +2 -2
  8. package/components/ResourceDetail/index.vue +1 -1
  9. package/components/SortableTable/index.vue +17 -2
  10. package/components/fleet/FleetConfigMapSelector.vue +117 -0
  11. package/components/fleet/FleetSecretSelector.vue +127 -0
  12. package/components/fleet/__tests__/FleetConfigMapSelector.test.ts +125 -0
  13. package/components/fleet/__tests__/FleetSecretSelector.test.ts +82 -0
  14. package/components/form/FileImageSelector.vue +13 -4
  15. package/components/form/FileSelector.vue +11 -2
  16. package/components/form/ResourceLabeledSelect.vue +1 -0
  17. package/components/form/__tests__/ResourceLabeledSelect.test.ts +90 -0
  18. package/components/nav/Header.vue +1 -0
  19. package/config/product/auth.js +1 -0
  20. package/config/query-params.js +1 -0
  21. package/config/settings.ts +8 -1
  22. package/config/types.js +2 -0
  23. package/dialog/AddonConfigConfirmationDialog.vue +45 -1
  24. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +52 -11
  25. package/edit/auth/AuthProviderWarningBanners.vue +14 -1
  26. package/edit/auth/github-app-steps.vue +97 -0
  27. package/edit/auth/github-steps.vue +75 -0
  28. package/edit/auth/github.vue +94 -65
  29. package/edit/fleet.cattle.io.helmop.vue +51 -2
  30. package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +15 -5
  31. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +11 -9
  32. package/edit/provisioning.cattle.io.cluster/rke2.vue +56 -9
  33. package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +28 -2
  34. package/list/projectsecret.vue +1 -1
  35. package/machine-config/azure.vue +1 -1
  36. package/mixins/chart.js +1 -1
  37. package/models/__tests__/chart.test.ts +17 -9
  38. package/models/__tests__/compliance.cattle.io.clusterscanprofile.spec.js +30 -0
  39. package/models/catalog.cattle.io.app.js +1 -1
  40. package/models/chart.js +3 -1
  41. package/models/compliance.cattle.io.clusterscanprofile.js +1 -1
  42. package/models/management.cattle.io.authconfig.js +1 -0
  43. package/package.json +2 -2
  44. package/pages/auth/login.vue +5 -2
  45. package/pages/auth/verify.vue +1 -1
  46. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +3 -2
  47. package/pages/c/_cluster/apps/charts/chart.vue +2 -2
  48. package/pages/c/_cluster/explorer/EventsTable.vue +89 -3
  49. package/pages/c/_cluster/explorer/tools/index.vue +3 -3
  50. package/pages/c/_cluster/settings/performance.vue +12 -25
  51. package/pages/home.vue +313 -12
  52. package/plugins/axios.js +2 -1
  53. package/plugins/dashboard-store/actions.js +1 -1
  54. package/plugins/dashboard-store/resource-class.js +17 -2
  55. package/plugins/steve/steve-pagination-utils.ts +2 -2
  56. package/scripts/extension/publish +1 -1
  57. package/store/auth.js +8 -3
  58. package/store/aws.js +8 -6
  59. package/store/features.js +1 -0
  60. package/store/index.js +9 -3
  61. package/store/prefs.js +6 -0
  62. package/types/kube/kube-api.ts +2 -1
  63. package/types/rancher/index.d.ts +1 -0
  64. package/types/resources/settings.d.ts +29 -7
  65. package/types/shell/index.d.ts +59 -0
  66. package/utils/__tests__/cluster.test.ts +379 -1
  67. package/utils/cluster.js +157 -3
  68. package/utils/dynamic-content/__tests__/config.test.ts +187 -0
  69. package/utils/dynamic-content/__tests__/index.test.ts +390 -0
  70. package/utils/dynamic-content/__tests__/info.test.ts +263 -0
  71. package/utils/dynamic-content/__tests__/new-release.test.ts +216 -0
  72. package/utils/dynamic-content/__tests__/support-notice.test.ts +262 -0
  73. package/utils/dynamic-content/__tests__/util.test.ts +235 -0
  74. package/utils/dynamic-content/config.ts +55 -0
  75. package/utils/dynamic-content/index.ts +273 -0
  76. package/utils/dynamic-content/info.ts +219 -0
  77. package/utils/dynamic-content/new-release.ts +126 -0
  78. package/utils/dynamic-content/support-notice.ts +169 -0
  79. package/utils/dynamic-content/types.d.ts +101 -0
  80. package/utils/dynamic-content/util.ts +122 -0
  81. package/utils/inactivity.ts +104 -0
  82. package/utils/pagination-utils.ts +19 -4
  83. package/utils/release-notes.ts +1 -1
package/store/prefs.js CHANGED
@@ -114,6 +114,12 @@ export const PROVISIONER = create('provisioner', _RKE2, { options: [_RKE1, _RKE2
114
114
  export const MENU_MAX_CLUSTERS = 10;
115
115
  // Prompt for confirm when scaling down node pool in GUI and save the pref
116
116
  export const SCALE_POOL_PROMPT = create('scale-pool-prompt', null, { parseJSON });
117
+
118
+ // Dynamic content
119
+ export const READ_NEW_RELEASE = create('read-new-release', '', { parseJSON });
120
+ export const READ_SUPPORT_NOTICE = create('read-support-notice', '', { parseJSON });
121
+ export const READ_UPCOMING_SUPPORT_NOTICE = create('read-upcoming-support-notice', '', { parseJSON });
122
+
117
123
  // --------------------
118
124
 
119
125
  const cookiePrefix = 'R_';
@@ -30,5 +30,6 @@ export interface KubeMetadata {
30
30
  }
31
31
 
32
32
  export interface RancherKubeMetadata extends KubeMetadata {
33
- namespace: string,
33
+ namespace?: string,
34
+ name: string
34
35
  }
@@ -4,6 +4,7 @@ declare module '@rancher/auto-import' {
4
4
 
5
5
  declare module '@shell/store/type-map' {
6
6
  export function DSL(store: any, name: string): any;
7
+ export function isAdminUser(getters: any): boolean;
7
8
  }
8
9
 
9
10
  declare module '@shell/plugins/dashboard-store';
@@ -20,14 +20,39 @@ export interface PaginationSettingsStore {
20
20
  }
21
21
  }
22
22
 
23
- /**
23
+ /*
24
24
  * Determine which resources can utilise server-side pagination
25
25
  */
26
26
  export interface PaginationSettingsStores {
27
27
  [store: string]: PaginationSettingsStore
28
28
  }
29
29
 
30
- export type PaginationFeature = 'listAutoRefreshToggle' | 'listManualRefresh'
30
+ /**
31
+ * Names of pagination features used in pagination settings (not featureflags)
32
+ */
33
+ export type PaginationFeatureName = 'listAutoRefreshToggle' | 'listManualRefresh' | 'homePageCluster'
34
+
35
+ export type PaginationFeatureHomePageClusterConfig = {
36
+ threshold: number,
37
+ results: number,
38
+ pagesPerRow: number
39
+ }
40
+
41
+ /**
42
+ * Details of a specific pagination feature
43
+ */
44
+ export type PaginationFeature<Config = any> = {
45
+ version: number,
46
+ enabled: boolean,
47
+ configuration?: Config,
48
+ }
49
+
50
+ /**
51
+ * List of specific features that can be enabled / disabled
52
+ */
53
+ export type PaginationSettingsFeatures = {
54
+ [key in PaginationFeatureName]?: PaginationFeature
55
+ }
31
56
 
32
57
  /**
33
58
  * Settings to handle server side pagination
@@ -45,11 +70,7 @@ export interface PaginationSettings {
45
70
  /**
46
71
  * List of specific features that can be enabled / disabled
47
72
  */
48
- features?: {
49
- [key in PaginationFeature]: {
50
- enabled: boolean,
51
- }
52
- },
73
+ features?: PaginationSettingsFeatures,
53
74
 
54
75
  /**
55
76
  * Debounce the amount of time between a resource changing and the backend sending a resource.changes message
@@ -82,6 +103,7 @@ type ManagedFields = {
82
103
  time: string;
83
104
  };
84
105
 
106
+ // Note - this should now be @RancherKubeMetadata
85
107
  type Metadata = {
86
108
  creationTimestamp: string;
87
109
  fields: string[];
@@ -174,6 +174,7 @@ export const STEP: "step";
174
174
  export const LOGGED_OUT: "logged-out";
175
175
  export const IS_SSO: "is-sso";
176
176
  export const IS_SLO: "is-slo";
177
+ export const IS_SESSION_IDLE: "is-session-idle";
177
178
  export const UPGRADED: "upgraded";
178
179
  export const TIMED_OUT: "timed-out";
179
180
  export const AUTH_TEST: "test";
@@ -2190,6 +2191,9 @@ export namespace MANAGEMENT {
2190
2191
  export let CLUSTER_PROXY_CONFIG: string;
2191
2192
  export let OIDC_CLIENT: string;
2192
2193
  }
2194
+ export namespace EXT {
2195
+ let USER_ACTIVITY: string;
2196
+ }
2193
2197
  export namespace CAPI {
2194
2198
  let CAPI_CLUSTER: string;
2195
2199
  let MACHINE_DEPLOYMENT: string;
@@ -3671,6 +3675,7 @@ export const STEVE_CACHE: any;
3671
3675
  export const UIEXTENSION: any;
3672
3676
  export const PROVISIONING_PRE_BOOTSTRAP: any;
3673
3677
  export const SCHEDULING_CUSTOMIZATION: any;
3678
+ export const SCC: any;
3674
3679
  export namespace getters {
3675
3680
  function get(state: any, getters: any, rootState: any, rootGetters: any): (name: any) => any;
3676
3681
  }
@@ -3754,6 +3759,9 @@ export const _RKE2: "rke2";
3754
3759
  export const PROVISIONER: any;
3755
3760
  export const MENU_MAX_CLUSTERS: 10;
3756
3761
  export const SCALE_POOL_PROMPT: any;
3762
+ export const READ_NEW_RELEASE: any;
3763
+ export const READ_SUPPORT_NOTICE: any;
3764
+ export const READ_UPCOMING_SUPPORT_NOTICE: any;
3757
3765
  export function state(): {
3758
3766
  cookiesLoaded: boolean;
3759
3767
  data: {};
@@ -3980,6 +3988,57 @@ export function initSchedulingCustomization(value: any, features: any, store: an
3980
3988
  schedulingCustomizationOriginallyEnabled: boolean;
3981
3989
  errors: any[];
3982
3990
  }>;
3991
+ /**
3992
+ * Recursively filters a `diffs` object to only include differences that are relevant to the user.
3993
+ * A difference is considered relevant if the user has provided a custom value for that specific field.
3994
+ *
3995
+ * @param {object} diffs - The object representing the differences between two chart versions' default values.
3996
+ * @param {object} userVals - The object containing the user's custom values.
3997
+ * @returns {object} A new object containing only the relevant differences.
3998
+ */
3999
+ export function _addonConfigPreserveFilter(diffs: object, userVals: object): object;
4000
+ /**
4001
+ * @typedef {object} AddonPreserveContext
4002
+ * @property {object} addonConfigDiffs - An object that stores the diffs.
4003
+ * @property {string[]} addonNames - An array of addon names to check.
4004
+ * @property {object} $store - The Vuex store.
4005
+ * @property {object} userChartValues - The user's customized chart values.
4006
+ *
4007
+ * When the Kubernetes version is changed, this method is called to handle the add-on configurations
4008
+ * for all enabled addons. It checks if an addon's version has changed and, if so, determines if the
4009
+ * user's custom configurations should be preserved for the new version.
4010
+ *
4011
+ * The goal is to avoid showing a confirmation dialog for changes in default values that the user has not customized.
4012
+ *
4013
+ * @param {AddonPreserveContext} context The context object from the component.
4014
+ * @param {object} oldCharts The charts object from the K8s release object being changed from.
4015
+ * @param {object} newCharts The charts object from the K8s release object being changed to.
4016
+ */
4017
+ export function addonConfigPreserve(context: AddonPreserveContext, oldCharts: object, newCharts: object): Promise<void>;
4018
+ export type AddonPreserveContext = {
4019
+ /**
4020
+ * - An object that stores the diffs.
4021
+ */
4022
+ addonConfigDiffs: object;
4023
+ /**
4024
+ * - An array of addon names to check.
4025
+ */
4026
+ addonNames: string[];
4027
+ /**
4028
+ * - The Vuex store.
4029
+ */
4030
+ $store: object;
4031
+ /**
4032
+ * - The user's customized chart values.
4033
+ *
4034
+ * When the Kubernetes version is changed, this method is called to handle the add-on configurations
4035
+ * for all enabled addons. It checks if an addon's version has changed and, if so, determines if the
4036
+ * user's custom configurations should be preserved for the new version.
4037
+ *
4038
+ * The goal is to avoid showing a confirmation dialog for changes in default values that the user has not customized.
4039
+ */
4040
+ userChartValues: object;
4041
+ };
3983
4042
  }
3984
4043
 
3985
4044
  // @shell/utils/color
@@ -1,4 +1,5 @@
1
- import { abbreviateClusterName } from '@shell/utils/cluster';
1
+ import { abbreviateClusterName, _addonConfigPreserveFilter, addonConfigPreserve } from '@shell/utils/cluster';
2
+ import { diff } from '@shell/utils/object';
2
3
 
3
4
  describe('fx: abbreviateClusterName', () => {
4
5
  it.each([
@@ -55,3 +56,380 @@ describe('fx: abbreviateClusterName', () => {
55
56
  expect(result).toStrictEqual(expected);
56
57
  });
57
58
  });
59
+
60
+ describe('fx: _addonConfigPreserveFilter', () => {
61
+ it('should return an empty object if there are no differences between default values', () => {
62
+ const oldDefaults = { replicaCount: 1 };
63
+ const newDefaults = { replicaCount: 1 };
64
+ const userVals = { replicaCount: 3 };
65
+ const diffs = diff(oldDefaults, newDefaults);
66
+ const expected = {};
67
+
68
+ expect(_addonConfigPreserveFilter(diffs, userVals)).toStrictEqual(expected);
69
+ });
70
+
71
+ it('should return an empty object if no user values are provided', () => {
72
+ const oldDefaults = { replicaCount: 1 };
73
+ const newDefaults = { replicaCount: 2 };
74
+ const userVals = {};
75
+ const diffs = diff(oldDefaults, newDefaults);
76
+ const expected = {};
77
+
78
+ expect(_addonConfigPreserveFilter(diffs, userVals)).toStrictEqual(expected);
79
+ });
80
+
81
+ it('should filter out diffs for fields not customized by the user', () => {
82
+ const oldDefaults = {
83
+ replicaCount: 1,
84
+ persistence: false
85
+ };
86
+ const newDefaults = {
87
+ replicaCount: 2,
88
+ persistence: true
89
+ };
90
+ const userVals = { replicaCount: 3 };
91
+ const diffs = diff(oldDefaults, newDefaults);
92
+ const expected = { replicaCount: 2 };
93
+
94
+ expect(_addonConfigPreserveFilter(diffs, userVals)).toStrictEqual(expected);
95
+ });
96
+
97
+ it('should include diffs for fields customized by the user', () => {
98
+ const oldDefaults = { replicaCount: 1 };
99
+ const newDefaults = { replicaCount: 2 };
100
+ const userVals = { replicaCount: 3 };
101
+ const diffs = diff(oldDefaults, newDefaults);
102
+ const expected = { replicaCount: 2 };
103
+
104
+ expect(_addonConfigPreserveFilter(diffs, userVals)).toStrictEqual(expected);
105
+ });
106
+
107
+ it('should handle nested objects: include diff if user customized nested property', () => {
108
+ const oldDefaults = { service: { port: 80 } };
109
+ const newDefaults = { service: { port: 8080 } };
110
+ const userVals = { service: { port: 9000 } };
111
+ const diffs = diff(oldDefaults, newDefaults);
112
+ const expected = { service: { port: 8080 } };
113
+
114
+ expect(_addonConfigPreserveFilter(diffs, userVals)).toStrictEqual(expected);
115
+ });
116
+
117
+ it('should handle nested objects: exclude diff if user did not customize nested property', () => {
118
+ const oldDefaults = { service: { port: 80, type: 'ClusterIP' } };
119
+ const newDefaults = { service: { port: 8080, type: 'ClusterIP' } };
120
+ const userVals = { service: { type: 'NodePort' } };
121
+ const diffs = diff(oldDefaults, newDefaults);
122
+ const expected = {};
123
+
124
+ expect(_addonConfigPreserveFilter(diffs, userVals)).toStrictEqual(expected);
125
+ });
126
+
127
+ it('should handle nested objects: include diff if user customized a removed nested object', () => {
128
+ const oldDefaults = { service: { port: 80 } };
129
+ const newDefaults = {};
130
+ const userVals = { service: { port: 9000 } };
131
+ const diffs = diff(oldDefaults, newDefaults);
132
+ const expected = { service: { port: null } };
133
+
134
+ expect(_addonConfigPreserveFilter(diffs, userVals)).toStrictEqual(expected);
135
+ });
136
+
137
+ it('should handle nested objects: exclude diff if a new property is added to default nested object and user did not customize it', () => {
138
+ const oldDefaults = { service: { port: 80 } };
139
+ const newDefaults = { service: { port: 80, type: 'ClusterIP' } };
140
+ const userVals = { service: { port: 80 } };
141
+ const diffs = diff(oldDefaults, newDefaults);
142
+ const expected = {};
143
+
144
+ expect(_addonConfigPreserveFilter(diffs, userVals)).toStrictEqual(expected);
145
+ });
146
+
147
+ it('should handle arrays of primitives: include diff if user customized array and default array changed', () => {
148
+ const oldDefaults = { ingress: { hosts: ['host1.com', 'host2.com'] } };
149
+ const newDefaults = { ingress: { hosts: ['host1.com', 'host3.com'] } };
150
+ const userVals = { ingress: { hosts: ['user.host.com'] } };
151
+ const diffs = diff(oldDefaults, newDefaults);
152
+ const expected = { ingress: { hosts: ['host1.com', 'host3.com'] } };
153
+
154
+ expect(_addonConfigPreserveFilter(diffs, userVals)).toStrictEqual(expected);
155
+ });
156
+
157
+ it('should handle arrays of primitives: exclude diff if user did not customize array', () => {
158
+ const oldDefaults = { ingress: { hosts: ['host1.com', 'host2.com'] } };
159
+ const newDefaults = { ingress: { hosts: ['host1.com', 'host3.com'] } };
160
+ const userVals = { ingress: { enabled: true } };
161
+ const diffs = diff(oldDefaults, newDefaults);
162
+ const expected = {};
163
+
164
+ expect(_addonConfigPreserveFilter(diffs, userVals)).toStrictEqual(expected);
165
+ });
166
+
167
+ it('should handle arrays of objects: include diff if user customized array and default array changed', () => {
168
+ const oldDefaults = { service: { ports: [{ name: 'http', port: 80 }] } };
169
+ const newDefaults = { service: { ports: [{ name: 'http', port: 80 }, { name: 'https', port: 443 }] } };
170
+ const userVals = { service: { ports: [{ name: 'http', port: 8080 }] } };
171
+ const diffs = diff(oldDefaults, newDefaults);
172
+ const expected = { service: { ports: [{ name: 'http', port: 80 }, { name: 'https', port: 443 }] } };
173
+
174
+ expect(_addonConfigPreserveFilter(diffs, userVals)).toStrictEqual(expected);
175
+ });
176
+
177
+ it('should handle arrays of objects: exclude diff if user did not customize array', () => {
178
+ const oldDefaults = { service: { ports: [{ name: 'http', port: 80 }] } };
179
+ const newDefaults = { service: { ports: [{ name: 'http', port: 80 }, { name: 'https', port: 443 }] } };
180
+ const userVals = { service: { type: 'ClusterIP' } };
181
+ const diffs = diff(oldDefaults, newDefaults);
182
+ const expected = {};
183
+
184
+ expect(_addonConfigPreserveFilter(diffs, userVals)).toStrictEqual(expected);
185
+ });
186
+
187
+ it('should handle properties added/removed: include diff if user customized a removed property', () => {
188
+ const oldDefaults = { oldProperty: 'defaultValue' };
189
+ const newDefaults = {};
190
+ const userVals = { oldProperty: 'customValue' };
191
+ const diffs = diff(oldDefaults, newDefaults);
192
+ const expected = { oldProperty: null };
193
+
194
+ expect(_addonConfigPreserveFilter(diffs, userVals)).toStrictEqual(expected);
195
+ });
196
+
197
+ it('should handle properties added/removed: exclude diff if user did not customize an added property', () => {
198
+ const oldDefaults = {};
199
+ const newDefaults = { newProperty: 'defaultValue' };
200
+ const userVals = { otherProp: 'value' };
201
+ const diffs = diff(oldDefaults, newDefaults);
202
+ const expected = {};
203
+
204
+ expect(_addonConfigPreserveFilter(diffs, userVals)).toStrictEqual(expected);
205
+ });
206
+
207
+ it('should handle complex nested structures with multiple changes', () => {
208
+ const oldDefaults = {
209
+ replicaCount: 1,
210
+ image: {
211
+ repository: 'nginx',
212
+ tag: 'stable'
213
+ },
214
+ service: {
215
+ type: 'ClusterIP',
216
+ port: 80
217
+ },
218
+ ingress: {
219
+ enabled: false,
220
+ hosts: ['chart-example.local']
221
+ }
222
+ };
223
+ const newDefaults = {
224
+ replicaCount: 2,
225
+ image: {
226
+ repository: 'nginx',
227
+ tag: 'mainline'
228
+ },
229
+ service: {
230
+ type: 'ClusterIP',
231
+ port: 80
232
+ },
233
+ ingress: {
234
+ enabled: true,
235
+ hosts: ['chart-example.local', 'new.chart-example.local'],
236
+ tls: true
237
+ }
238
+ };
239
+ const userVals = {
240
+ replicaCount: 3,
241
+ ingress: { hosts: ['my.custom.host'] }
242
+ };
243
+ const diffs = diff(oldDefaults, newDefaults);
244
+ const expected = {
245
+ replicaCount: 2,
246
+ ingress: { hosts: ['chart-example.local', 'new.chart-example.local'] }
247
+ };
248
+
249
+ expect(_addonConfigPreserveFilter(diffs, userVals)).toStrictEqual(expected);
250
+ });
251
+ });
252
+
253
+ describe('fx: addonConfigPreserve', () => {
254
+ const ADDON_NAME = 'rke2-my-addon';
255
+ const mockOldVersionCharts = {
256
+ [ADDON_NAME]: {
257
+ repo: 'repo',
258
+ version: '1.0.0'
259
+ }
260
+ };
261
+
262
+ const mockNewVersionCharts = {
263
+ [ADDON_NAME]: {
264
+ repo: 'repo',
265
+ version: '1.1.0'
266
+ }
267
+ };
268
+
269
+ const mockOldVersionInfo = {
270
+ values: {
271
+ replicas: 1,
272
+ service: { type: 'ClusterIP' }
273
+ }
274
+ };
275
+
276
+ const mockNewVersionInfo = {
277
+ values: {
278
+ replicas: 2, // changed
279
+ service: { type: 'ClusterIP' },
280
+ persistence: true // new
281
+ }
282
+ };
283
+
284
+ let mockStore: any;
285
+ let addonConfigDiffs: any;
286
+ let userChartValues: any;
287
+ let context: any;
288
+
289
+ beforeEach(() => {
290
+ mockStore = {
291
+ dispatch: jest.fn((action, payload) => {
292
+ if (action === 'catalog/getVersionInfo') {
293
+ if (payload.versionName === '1.0.0') {
294
+ return Promise.resolve(mockOldVersionInfo);
295
+ }
296
+ if (payload.versionName === '1.1.0') {
297
+ return Promise.resolve(mockNewVersionInfo);
298
+ }
299
+ }
300
+
301
+ return Promise.resolve({});
302
+ })
303
+ };
304
+ addonConfigDiffs = {};
305
+ userChartValues = {};
306
+ context = {
307
+ addonConfigDiffs,
308
+ addonNames: [ADDON_NAME],
309
+ $store: mockStore,
310
+ userChartValues,
311
+ };
312
+ });
313
+
314
+ it('should identify no relevant changes if user has no custom values', async() => {
315
+ // No user overrides means no relevant differences.
316
+ context.userChartValues = {};
317
+ await addonConfigPreserve(context, mockOldVersionCharts, mockNewVersionCharts);
318
+
319
+ expect(context.addonConfigDiffs[ADDON_NAME]).toStrictEqual({});
320
+ expect(context.userChartValues[`${ ADDON_NAME }-1.1.0`]).toBeUndefined();
321
+ });
322
+
323
+ it('should identify no relevant changes if user custom values are for different fields', async() => {
324
+ // User overrides for non-differing fields should not be considered relevant.
325
+ context.userChartValues = { [`${ ADDON_NAME }-1.0.0`]: { service: { type: 'NodePort' } } };
326
+ await addonConfigPreserve(context, mockOldVersionCharts, mockNewVersionCharts);
327
+
328
+ expect(context.addonConfigDiffs[ADDON_NAME]).toStrictEqual({});
329
+ // Since diffs are empty, user values should be preserved
330
+ expect(context.userChartValues[`${ ADDON_NAME }-1.1.0`]).toStrictEqual({ service: { type: 'NodePort' } });
331
+ });
332
+
333
+ it('should identify relevant changes if user has customized a changed field', async() => {
334
+ // A user override on a changed default should be flagged as a relevant difference.
335
+ context.userChartValues = { [`${ ADDON_NAME }-1.0.0`]: { replicas: 3 } };
336
+ await addonConfigPreserve(context, mockOldVersionCharts, mockNewVersionCharts);
337
+
338
+ expect(context.addonConfigDiffs[ADDON_NAME]).toStrictEqual({ replicas: 2 });
339
+ // Since diffs are NOT empty, user values should NOT be preserved
340
+ expect(context.userChartValues[`${ ADDON_NAME }-1.1.0`]).toBeUndefined();
341
+ });
342
+
343
+ it('should not identify relevant changes for new fields not customized by user', async() => {
344
+ // New fields in the addon default values are not relevant if not customized by the user.
345
+ context.userChartValues = { [`${ ADDON_NAME }-1.0.0`]: {} };
346
+ await addonConfigPreserve(context, mockOldVersionCharts, mockNewVersionCharts);
347
+
348
+ // The only diff is `replicas`, which user didn't touch. `persistence` is new but also not touched.
349
+ // So final diffs should be empty.
350
+ expect(context.addonConfigDiffs[ADDON_NAME]).toStrictEqual({});
351
+ expect(context.userChartValues[`${ ADDON_NAME }-1.1.0`]).toStrictEqual({});
352
+ });
353
+
354
+ it('should preserve user values if there are no relevant differences', async() => {
355
+ // User values should be carried over to the new version if no relevant diffs are found.
356
+ context.userChartValues = { [`${ ADDON_NAME }-1.0.0`]: { service: { type: 'NodePort' } } };
357
+ await addonConfigPreserve(context, mockOldVersionCharts, mockNewVersionCharts);
358
+
359
+ expect(context.addonConfigDiffs[ADDON_NAME]).toStrictEqual({});
360
+ expect(context.userChartValues[`${ ADDON_NAME }-1.1.0`]).toStrictEqual({ service: { type: 'NodePort' } });
361
+ });
362
+
363
+ it('should not preserve user values if there are relevant differences', async() => {
364
+ // User values should not be carried over if relevant diffs are found.
365
+ context.userChartValues = { [`${ ADDON_NAME }-1.0.0`]: { replicas: 3 } };
366
+ await addonConfigPreserve(context, mockOldVersionCharts, mockNewVersionCharts);
367
+
368
+ expect(context.addonConfigDiffs[ADDON_NAME]).toStrictEqual({ replicas: 2 });
369
+ expect(context.userChartValues[`${ ADDON_NAME }-1.1.0`]).toBeUndefined();
370
+ });
371
+
372
+ it('should handle catalog API errors gracefully', async() => {
373
+ // Errors from fetching chart info should be caught and handled.
374
+ const error = new Error('catalog fetch failed');
375
+
376
+ jest.spyOn(context.$store, 'dispatch').mockImplementation().mockRejectedValue(error);
377
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
378
+
379
+ await addonConfigPreserve(context, mockOldVersionCharts, mockNewVersionCharts);
380
+
381
+ expect(context.addonConfigDiffs[ADDON_NAME]).toBeUndefined();
382
+ expect(errorSpy).toHaveBeenCalledWith(`Failed to get chart version info for diff for chart ${ ADDON_NAME }`, error);
383
+ errorSpy.mockRestore();
384
+ });
385
+
386
+ it('should do nothing if oldCharts is not provided', async() => {
387
+ await addonConfigPreserve(context, {}, mockNewVersionCharts);
388
+ expect(context.addonConfigDiffs).toStrictEqual({});
389
+ expect(mockStore.dispatch).not.toHaveBeenCalled();
390
+ });
391
+
392
+ it('should do nothing if newCharts is not provided', async() => {
393
+ await addonConfigPreserve(context, mockOldVersionCharts, {});
394
+ expect(context.addonConfigDiffs).toStrictEqual({});
395
+ expect(mockStore.dispatch).not.toHaveBeenCalled();
396
+ });
397
+
398
+ it('should do nothing if addon version is the same', async() => {
399
+ const mockNewVersionSame = {
400
+ [ADDON_NAME]: {
401
+ repo: 'repo',
402
+ version: '1.0.0'
403
+ }
404
+ };
405
+
406
+ await addonConfigPreserve(context, mockOldVersionCharts, mockNewVersionSame);
407
+ expect(context.addonConfigDiffs[ADDON_NAME]).toBeUndefined();
408
+ expect(mockStore.dispatch).not.toHaveBeenCalled();
409
+ });
410
+
411
+ it('should handle multiple addons', async() => {
412
+ const ADDON2_NAME = 'rke2-my-addon2';
413
+ const ADDON3_NAME = 'rke2-my-addon3'; // same version
414
+
415
+ const oldCharts = {
416
+ ...mockOldVersionCharts,
417
+ [ADDON2_NAME]: { repo: 'repo', version: '1.0.0' },
418
+ [ADDON3_NAME]: { repo: 'repo', version: '1.0.0' },
419
+ };
420
+ const newCharts = {
421
+ ...mockNewVersionCharts,
422
+ [ADDON2_NAME]: { repo: 'repo', version: '1.1.0' }, // changed version, but no user values
423
+ [ADDON3_NAME]: { repo: 'repo', version: '1.0.0' }, // same version
424
+ };
425
+
426
+ context.addonNames = [ADDON_NAME, ADDON2_NAME, ADDON3_NAME];
427
+ context.userChartValues = { [`${ ADDON_NAME }-1.0.0`]: { replicas: 3 } };
428
+
429
+ await addonConfigPreserve(context, oldCharts, newCharts);
430
+
431
+ expect(context.addonConfigDiffs[ADDON_NAME]).toStrictEqual({ replicas: 2 });
432
+ expect(context.addonConfigDiffs[ADDON2_NAME]).toStrictEqual({});
433
+ expect(context.addonConfigDiffs[ADDON3_NAME]).toBeUndefined();
434
+ });
435
+ });