@rancher/shell 3.0.8 → 3.0.9-rc.2

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 (192) hide show
  1. package/apis/intf/modal.ts +38 -0
  2. package/apis/intf/slide-in.ts +3 -1
  3. package/apis/shell/__tests__/slide-in.test.ts +36 -0
  4. package/apis/shell/slide-in.ts +5 -1
  5. package/assets/styles/base/_color.scss +1 -0
  6. package/assets/styles/base/_typography.scss +14 -5
  7. package/assets/styles/themes/_light.scss +1 -1
  8. package/assets/styles/themes/_modern.scss +1 -1
  9. package/assets/translations/en-us.yaml +94 -33
  10. package/assets/translations/zh-hans.yaml +0 -2
  11. package/components/ActionMenuShell.vue +4 -4
  12. package/components/CodeMirror.vue +4 -3
  13. package/components/DetailText.vue +54 -7
  14. package/components/Drawer/Chrome.vue +11 -4
  15. package/components/Drawer/DrawerCard.vue +19 -0
  16. package/components/Drawer/ResourceDetailDrawer/ConfigTab.vue +3 -11
  17. package/components/Drawer/ResourceDetailDrawer/__tests__/ConfigTab.test.ts +2 -2
  18. package/components/Drawer/ResourceDetailDrawer/index.vue +3 -20
  19. package/components/Drawer/types.ts +1 -0
  20. package/components/DynamicContent/DynamicContentCloseButton.vue +2 -2
  21. package/components/LocaleSelector.vue +1 -1
  22. package/components/Markdown.vue +1 -1
  23. package/components/PopoverCard.vue +3 -3
  24. package/components/Resource/Detail/Card/ExtrasCard.vue +39 -0
  25. package/components/Resource/Detail/Card/StateCard/__tests__/composables.test.ts +142 -0
  26. package/components/Resource/Detail/Card/StateCard/composables.ts +41 -11
  27. package/components/Resource/Detail/Card/StateCard/index.vue +3 -9
  28. package/components/Resource/Detail/Card/StateCard/types.ts +6 -0
  29. package/components/Resource/Detail/Card/{PodsCard → StatusCard}/index.vue +11 -10
  30. package/components/Resource/Detail/Card/__tests__/PodsCard.test.ts +24 -25
  31. package/components/Resource/Detail/Cards.vue +27 -0
  32. package/components/Resource/Detail/Masthead/__tests__/index.test.ts +70 -0
  33. package/components/Resource/Detail/Masthead/index.vue +5 -0
  34. package/components/Resource/Detail/Metadata/KeyValueRow.vue +4 -2
  35. package/components/Resource/Detail/ResourcePopover/ResourcePopoverCard.vue +2 -2
  36. package/components/Resource/Detail/ResourceRow.types.ts +14 -0
  37. package/components/Resource/Detail/ResourceRow.vue +23 -35
  38. package/components/Resource/Detail/StatusRow.vue +5 -2
  39. package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +38 -7
  40. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +106 -2
  41. package/components/Resource/Detail/TitleBar/composables.ts +2 -1
  42. package/components/Resource/Detail/TitleBar/index.vue +41 -6
  43. package/components/ResourceDetail/Masthead/__tests__/index.test.ts +49 -1
  44. package/components/ResourceDetail/Masthead/__tests__/latest.test.ts +85 -0
  45. package/components/ResourceDetail/Masthead/index.vue +1 -0
  46. package/components/ResourceDetail/Masthead/latest.vue +8 -1
  47. package/components/ResourceDetail/Masthead/legacy.vue +1 -1
  48. package/components/Setting.vue +1 -1
  49. package/components/SortableTable/index.vue +25 -0
  50. package/components/SortableTable/selection.js +25 -12
  51. package/components/SortableTable/sorting.js +1 -1
  52. package/components/Tabbed/Tab.vue +1 -0
  53. package/components/Tabbed/index.vue +29 -6
  54. package/components/Window/ContainerShell.vue +10 -13
  55. package/components/fleet/FleetClusterTargets/TargetsList.vue +47 -29
  56. package/components/fleet/FleetClusterTargets/index.vue +82 -29
  57. package/components/fleet/FleetClusters.vue +26 -12
  58. package/components/fleet/FleetGitRepoPaths.vue +2 -2
  59. package/components/fleet/FleetResources.vue +14 -0
  60. package/components/fleet/FleetValuesFrom.vue +2 -2
  61. package/components/fleet/__tests__/FleetClusterTargets.test.ts +531 -0
  62. package/components/fleet/__tests__/FleetClusters.test.ts +576 -0
  63. package/components/fleet/dashboard/ResourceDetails.vue +96 -123
  64. package/components/form/Conditions.vue +1 -15
  65. package/components/form/HookOption.vue +5 -0
  66. package/components/form/LabeledSelect.vue +1 -1
  67. package/components/form/LifecycleHooks.vue +2 -6
  68. package/components/form/ResourceLabeledSelect.vue +12 -1
  69. package/components/form/SeccompProfile.vue +113 -0
  70. package/components/form/Security.vue +244 -133
  71. package/components/form/__tests__/LabeledSelect.test.ts +1 -1
  72. package/components/form/__tests__/SeccompProfile.test.js +124 -0
  73. package/components/form/__tests__/Security.test.ts +125 -37
  74. package/components/formatter/Autoscaler.vue +2 -2
  75. package/components/formatter/FleetSummaryGraph.vue +4 -1
  76. package/components/nav/Group.vue +5 -0
  77. package/components/nav/Header.vue +3 -3
  78. package/components/nav/HeaderPageActionMenu.vue +1 -1
  79. package/components/nav/NamespaceFilter.vue +6 -6
  80. package/components/nav/NotificationCenter/index.vue +1 -1
  81. package/components/nav/TopLevelMenu.helper.ts +41 -16
  82. package/components/nav/TopLevelMenu.vue +45 -25
  83. package/components/nav/WorkspaceSwitcher.vue +1 -1
  84. package/components/nav/__tests__/TopLevelMenu.helper.test.ts +277 -0
  85. package/components/nav/__tests__/TopLevelMenu.test.ts +160 -4
  86. package/components/templates/default.vue +0 -3
  87. package/components/templates/home.vue +0 -3
  88. package/components/templates/plain.vue +0 -3
  89. package/composables/useClickOutside.ts +1 -1
  90. package/config/product/explorer.js +1 -2
  91. package/config/types.js +41 -8
  92. package/detail/__tests__/workload.test.ts +8 -16
  93. package/detail/catalog.cattle.io.app.vue +6 -0
  94. package/detail/fleet.cattle.io.cluster.vue +6 -0
  95. package/detail/workload/index.vue +7 -109
  96. package/edit/__tests__/projectsecret.test.ts +42 -0
  97. package/edit/auth/__tests__/oidc.test.ts +50 -0
  98. package/edit/auth/oidc.vue +68 -44
  99. package/edit/autoscaling.horizontalpodautoscaler/index.vue +140 -59
  100. package/edit/autoscaling.horizontalpodautoscaler/metrics-row.vue +41 -5
  101. package/edit/projectsecret.vue +29 -0
  102. package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +89 -200
  103. package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +58 -17
  104. package/edit/provisioning.cattle.io.cluster/rke2.vue +11 -0
  105. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +3 -63
  106. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +82 -14
  107. package/edit/workload/__tests__/index.test.ts +122 -85
  108. package/edit/workload/index.vue +48 -29
  109. package/edit/workload/mixins/workload.js +85 -32
  110. package/list/catalog.cattle.io.clusterrepo.vue +1 -1
  111. package/list/projectsecret.vue +2 -2
  112. package/machine-config/__tests__/vmwarevsphere.test.ts +64 -0
  113. package/machine-config/amazonec2.vue +2 -2
  114. package/machine-config/vmwarevsphere.vue +58 -4
  115. package/mixins/__tests__/brand.spec.ts +18 -13
  116. package/mixins/__tests__/chart.test.ts +63 -0
  117. package/mixins/chart.js +56 -51
  118. package/models/__tests__/catalog.cattle.io.app.test.ts +33 -0
  119. package/models/__tests__/workload.test.ts +333 -0
  120. package/models/catalog.cattle.io.app.js +8 -0
  121. package/models/pod.js +14 -0
  122. package/models/secret.js +1 -1
  123. package/models/workload.js +93 -27
  124. package/package.json +4 -4
  125. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +91 -0
  126. package/pages/c/_cluster/apps/charts/install.vue +4 -4
  127. package/pages/c/_cluster/explorer/EventsTable.vue +2 -2
  128. package/pages/c/_cluster/fleet/index.vue +18 -12
  129. package/pages/c/_cluster/manager/hostedprovider/index.vue +1 -19
  130. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -1
  131. package/pages/c/_cluster/uiplugins/index.vue +1 -1
  132. package/plugins/dashboard-store/__tests__/resource-class.test.ts +234 -0
  133. package/plugins/dashboard-store/actions.js +9 -8
  134. package/plugins/dashboard-store/resource-class.js +97 -1
  135. package/plugins/steve/__tests__/revision.test.ts +84 -0
  136. package/plugins/steve/__tests__/steve-pagination-utils.test.ts +30 -0
  137. package/plugins/steve/__tests__/subscribe.spec.ts +134 -0
  138. package/plugins/steve/mutations.js +9 -0
  139. package/plugins/steve/revision.ts +26 -0
  140. package/plugins/steve/steve-pagination-utils.ts +6 -5
  141. package/plugins/steve/subscribe.js +211 -51
  142. package/plugins/subscribe-events.ts +2 -2
  143. package/rancher-components/Form/Checkbox/Checkbox.vue +13 -0
  144. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +1 -1
  145. package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.vue +1 -1
  146. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +3 -1
  147. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +3 -1
  148. package/rancher-components/Pill/RcTag/RcTag.vue +1 -1
  149. package/rancher-components/Pill/index.ts +4 -0
  150. package/rancher-components/RcButton/RcButton.test.ts +53 -9
  151. package/rancher-components/RcButton/RcButton.vue +217 -25
  152. package/rancher-components/RcButton/types.ts +27 -1
  153. package/rancher-components/RcDropdown/RcDropdownMenu.vue +4 -4
  154. package/rancher-components/RcDropdown/types.ts +3 -3
  155. package/rancher-components/RcIcon/RcIcon.test.ts +42 -0
  156. package/rancher-components/RcIcon/RcIcon.vue +9 -6
  157. package/rancher-components/RcIcon/types.ts +13 -9
  158. package/rancher-components/utils/status.test.ts +10 -15
  159. package/rancher-components/utils/status.ts +5 -6
  160. package/store/aws.js +18 -12
  161. package/store/index.js +4 -8
  162. package/store/type-map.utils.ts +1 -1
  163. package/types/kube/kube-api.ts +29 -3
  164. package/types/rancher/steve.api.ts +40 -0
  165. package/types/shell/index.d.ts +99 -0
  166. package/types/store/dashboard-store.types.ts +29 -7
  167. package/types/store/pagination.types.ts +1 -0
  168. package/types/store/subscribe-events.types.ts +1 -0
  169. package/utils/__tests__/azure.test.ts +56 -0
  170. package/utils/__tests__/back-off.test.ts +364 -245
  171. package/utils/__tests__/error.test.ts +44 -0
  172. package/utils/__tests__/fleet.test.ts +8 -1
  173. package/utils/__tests__/pagination-wrapper.test.ts +167 -0
  174. package/utils/__tests__/version.test.ts +55 -1
  175. package/utils/azure.js +12 -0
  176. package/utils/back-off.ts +302 -69
  177. package/utils/cspAdaptor.ts +32 -14
  178. package/utils/dynamic-content/__tests__/index.test.ts +1 -1
  179. package/utils/dynamic-content/__tests__/new-release.test.ts +48 -7
  180. package/utils/dynamic-content/__tests__/support-notice.test.ts +1 -4
  181. package/utils/dynamic-content/index.ts +1 -6
  182. package/utils/dynamic-content/new-release.ts +5 -3
  183. package/utils/dynamic-content/types.d.ts +0 -1
  184. package/utils/error.js +9 -0
  185. package/utils/fleet.ts +2 -2
  186. package/utils/inactivity.ts +2 -3
  187. package/utils/pagination-wrapper.ts +101 -17
  188. package/utils/validators/formRules/index.ts +3 -0
  189. package/utils/version.js +38 -0
  190. package/components/auth/AzureWarning.vue +0 -77
  191. /package/components/Resource/Detail/{Card/PodsCard/Bubble.vue → Bubble.vue} +0 -0
  192. /package/components/Resource/Detail/Card/{PodsCard → StatusCard}/composable.ts +0 -0
package/utils/back-off.ts CHANGED
@@ -1,11 +1,99 @@
1
- type BackOffEntry = {
2
- timeoutId?: NodeJS.Timeout,
1
+ import { randomStr } from '@shell/utils/string';
2
+
3
+ type BackOffEntry<MetadataType = any> = {
3
4
  try: number,
4
5
  retries: number,
5
6
  description: string,
6
- metadata: any,
7
+ metadata: MetadataType,
8
+ execute?: {
9
+ timeoutId?: NodeJS.Timeout,
10
+ },
11
+ recurse?: {
12
+ id: string,
13
+ }
7
14
  }
8
15
 
16
+ interface BackOffArgs<MetadataType = any> {
17
+ /**
18
+ * Unique id for the execution of this function.
19
+ *
20
+ * This will be used to delay further executions, and also to cancel it
21
+ */
22
+ id: string,
23
+ /**
24
+ * Basic text description to use in logging
25
+ */
26
+ description: string,
27
+ /**
28
+ * Number of executions allowed before flatly refusing to call more. Defaults to 10
29
+ */
30
+ retries?: number,
31
+ /**
32
+ * Before calling delayedFn check if it can still run
33
+ *
34
+ * Useful for checking state after a looong delay
35
+ */
36
+ canFn?: () => Promise<boolean>,
37
+ /**
38
+ * Call this function
39
+ * - if it's not already waiting to run
40
+ * - if it's passed canFn
41
+ * - if it hasn't been tried over `retries` amount
42
+ *
43
+ * The function will be increasingly (exponentially) delayed if it has previously been called
44
+ */
45
+ delayedFn: () => Promise<any>,
46
+ /**
47
+ * Anything that might be important outside of this file (used with `getBackOff`)
48
+ */
49
+ metadata?: MetadataType,
50
+ /**
51
+ *
52
+ * RESET_ON_SUCCESS
53
+ */
54
+ mode?: ''
55
+ }
56
+
57
+ const metadataToString = (metadata: any) => {
58
+ if (!metadata) {
59
+ return '';
60
+ }
61
+
62
+ return JSON.stringify(metadata, (_, value) => {
63
+ return value === undefined ? '' : value;
64
+ });
65
+ };
66
+
67
+ export type BackOffExecuteArgs<MetadataType> = BackOffArgs<MetadataType>
68
+
69
+ export interface BackOffRecurseArgs<MetadataType> extends BackOffArgs<MetadataType> {
70
+ /**
71
+ * Should we continue to to try even if the previous attempt failed?
72
+ */
73
+ continueOnError: (arg: any) => Promise<boolean>,
74
+ }
75
+
76
+ const logStyle = 'font-weight: bold; font-style: italic;';
77
+ const logStyleReset = 'font-weight: normal; font-style: normal;';
78
+
79
+ enum LOG_TYPE { // eslint-disable-line no-unused-vars
80
+ /** Aligns with `execute` method */
81
+ EXECUTE = 'delay', // eslint-disable-line no-unused-vars
82
+ /** Aligns with `recurse` method */
83
+ RECURSE = 'recurse', // eslint-disable-line no-unused-vars
84
+ }
85
+ type LogLevel = 'error' | 'info' | 'debug' | 'warn' | undefined;
86
+ type LogArgs = { id: string, status: string, description: string, metadata?: any, type: string }
87
+
88
+ const logInitialBackOffRequest = false;
89
+ const calcLogLevel = (iteration: number): LogLevel => {
90
+ if (!logInitialBackOffRequest && iteration === 0) {
91
+ return undefined;
92
+ }
93
+
94
+ return 'info';
95
+ };
96
+
9
97
  /**
10
98
  * Helper class which handles backing off making the supplied request
11
99
  *
@@ -16,21 +104,65 @@ class BackOff {
16
104
  [id: string]: BackOffEntry
17
105
  } = {};
18
106
 
19
- private log(level: 'error' | 'info' | 'debug', id: string, classDescription: string, description: string, ...args: any[]) {
20
- console[level](`BackOff... Id: "${ id }". Description: "${ description }"\nStatus: ${ classDescription }\n`, ...args); // eslint-disable-line no-console
107
+ private getLogTypeFromMap(id: string): string {
108
+ const entry = this.getBackOff(id);
109
+
110
+ let safeType = '';
111
+
112
+ if (!!entry?.execute) {
113
+ safeType = LOG_TYPE.EXECUTE;
114
+ } else if (!!entry?.recurse) {
115
+ safeType = LOG_TYPE.RECURSE;
116
+ }
117
+
118
+ return safeType;
119
+ }
120
+
121
+ private log(level: LogLevel, {
122
+ id, status, description, metadata, type
123
+ }: LogArgs, ...args: any[]) {
124
+ if (!level) {
125
+ return;
126
+ }
127
+
128
+ let safeType = type || this.getLogTypeFromMap(id);
129
+
130
+ safeType = safeType ? ` (${ safeType })` : '';
131
+
132
+ // eslint-disable-next-line no-console
133
+ console[level](
134
+ `%cBackOff${ safeType }%c... \n%cId%c: ${ id }\n%cDescription%c: ${ description }\n%cStatus%c: ${ status }\n%cMetadata%c: ${ metadataToString(metadata) }\n%cCache %c: ${ Object.keys(this.map).map((e) => `"${ e }"`).join(' + ') }`,
135
+ logStyle, logStyleReset,
136
+ logStyle, logStyleReset,
137
+ logStyle, logStyleReset,
138
+ logStyle, logStyleReset,
139
+ logStyle, logStyleReset,
140
+ logStyle, logStyleReset,
141
+ ...args
142
+ );
143
+ }
144
+
145
+ private logAndError(level: LogLevel, {
146
+ id, status, description, metadata, type
147
+ }: LogArgs, ...args: any[]): Promise<undefined> {
148
+ this.log(level, {
149
+ id, status, description, metadata, type
150
+ }, ...args);
151
+
152
+ return Promise.reject(new Error(status));
21
153
  }
22
154
 
23
155
  /**
24
156
  * Get a specific back off process
25
157
  */
26
- getBackOff(id: string): BackOffEntry {
158
+ public getBackOff(id: string): BackOffEntry {
27
159
  return this.map[id];
28
160
  }
29
161
 
30
162
  /**
31
163
  * Stop ALL back off processes started since the ui was loaded
32
164
  */
33
- resetAll() {
165
+ public resetAll() {
34
166
  Object.keys(this.map).forEach((id) => {
35
167
  this.reset(id);
36
168
  });
@@ -39,7 +171,7 @@ class BackOff {
39
171
  /**
40
172
  * Stop all back off process with a specific prefix
41
173
  */
42
- resetPrefix(prefix:string) {
174
+ public resetPrefix(prefix:string) {
43
175
  Object.keys(this.map).forEach((id) => {
44
176
  if (id.startsWith(prefix)) {
45
177
  this.reset(id);
@@ -50,23 +182,148 @@ class BackOff {
50
182
  /**
51
183
  * Stop a back off process with a specific id
52
184
  */
53
- reset(id: string) {
185
+ public reset(id: string) {
54
186
  const backOff: BackOffEntry = this.map[id];
55
187
 
56
- if (backOff) {
57
- if (backOff?.timeoutId) {
58
- this.log('info', id, 'Stopping (cancelling active back-off)', backOff.description);
188
+ if (!backOff) {
189
+ return;
190
+ }
191
+
192
+ const logType = this.getLogTypeFromMap(id);
193
+
194
+ if (backOff?.execute?.timeoutId) {
195
+ this.log('info', {
196
+ id, status: 'Stopping (cancelling active back-off)', description: backOff.description, metadata: backOff.metadata, type: logType
197
+ });
198
+
199
+ clearTimeout(backOff.execute.timeoutId);
200
+ }
201
+ const backOffTry = backOff?.try || 0;
202
+ const logLevel = backOffTry <= 1 ? undefined : 'debug';
59
203
 
60
- clearTimeout(backOff.timeoutId);
204
+ delete this.map[id];
205
+
206
+ this.log(logLevel, {
207
+ id, status: 'Reset', description: backOff.description, metadata: backOff.metadata, type: logType
208
+ });
209
+ }
210
+
211
+ private sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
212
+
213
+ private calcDelay = (iteration: number) => {
214
+ // First step is immediate (0.001s)
215
+ // Second and others are exponential
216
+ // Iteration: 1, 2, 3, 4, 5, 6, 7, 8, 9
217
+ // Delay: 0.25s, 1s, 2.25s, 4s, 6.25s, 9s, 12.25s, 16s, 20.25s
218
+ return iteration === 0 ? 1 : Math.pow(iteration, 2) * 250;
219
+ }
220
+
221
+ private canRecurse = async(backOffEntry: BackOffEntry, {
222
+ id, description, metadata, canFn = async() => true
223
+ }: BackOffRecurseArgs<any>) => {
224
+ if (!this.map[id]) {
225
+ // was reset, don't care now, abort
226
+ // could be a pagination-wrapper request with a stale revision, which can be safely ignored
227
+ return this.logAndError('info', {
228
+ id, status: 'Aborting (backoff was reset, do not continue to process)', description, metadata, type: LOG_TYPE.RECURSE
229
+ });
230
+ }
231
+
232
+ if (this.map[id].recurse?.id !== backOffEntry.recurse?.id) {
233
+ return this.logAndError('info', {
234
+ id, status: 'Aborting (stale backoff, a new one exists)', description, metadata, type: LOG_TYPE.RECURSE
235
+ });
236
+ }
237
+
238
+ const cont = await canFn();
239
+
240
+ if (!cont) {
241
+ return this.logAndError('info', {
242
+ id, status: 'Skipping (canFn test failed)', description, metadata, type: LOG_TYPE.RECURSE
243
+ });
244
+ }
245
+ };
246
+
247
+ /**
248
+ * Call a function, if it fails keep trying but with a delay (aka back off)
249
+ *
250
+ * Return the successful result, or error if reached the max number of retries
251
+ *
252
+ * @template MetadataType - Type of configuration that can be internally stored with the backoff record
253
+ */
254
+ public async recurse<MetadataType = any, ResponseType = any>(args: BackOffRecurseArgs<MetadataType>): Promise<ResponseType | undefined> {
255
+ const {
256
+ id, description, retries = 10, delayedFn, continueOnError, metadata
257
+ } = args;
258
+
259
+ if (this.map[id]) {
260
+ return this.logAndError('info', {
261
+ id, status: 'Skipping (previous recurse back off process still running)', description, metadata, type: 'recurse',
262
+ });
263
+ }
264
+
265
+ this.map[id] = {
266
+ try: 1,
267
+ retries,
268
+ description,
269
+ metadata,
270
+ recurse: { id: randomStr() }
271
+ };
272
+
273
+ for (let i = 0; i < retries; i++) {
274
+ await this.canRecurse(this.map[id], args); // Check that we can start the process
275
+
276
+ this.map[id].try = i + 1;
277
+
278
+ const delay = this.calcDelay(i);
279
+ const logLevel = calcLogLevel(i);
280
+
281
+ this.log(logLevel, {
282
+ id, status: `Delaying call (attempt ${ i + 1 }, delayed by ${ delay }ms)`, description, metadata, type: LOG_TYPE.RECURSE
283
+ });
284
+
285
+ await this.sleep(delay);
286
+
287
+ await this.canRecurse(this.map[id], args); // Check that we can call the function (things could have changed after delay...)
288
+
289
+ this.log(logLevel, {
290
+ id, status: `Executing call`, description, metadata, type: LOG_TYPE.RECURSE
291
+ });
292
+
293
+ let res: ResponseType | undefined;
294
+
295
+ try {
296
+ res = await delayedFn();
297
+ } catch (e) {
298
+ const cont = await continueOnError(e);
299
+
300
+ if (!cont) {
301
+ this.reset(id); // Allow future calls to execute
302
+
303
+ const errorMessage = 'Failed call';
304
+
305
+ return this.logAndError('error', {
306
+ id, status: errorMessage, description, metadata, type: LOG_TYPE.RECURSE
307
+ }, e);
308
+ }
61
309
  }
62
- this.log('debug', id, 'Reset', backOff.description);
63
310
 
64
- delete this.map[id];
311
+ if (res) {
312
+ await this.canRecurse(this.map[id], args); // Check that we can return a result (things could have changed after delayedFn...)
313
+
314
+ this.reset(id); // Allow future calls to execute
315
+
316
+ this.log(logLevel, {
317
+ id, status: 'Successful call', description, metadata, type: LOG_TYPE.RECURSE
318
+ });
319
+
320
+ return res;
321
+ }
65
322
  }
66
323
  }
67
324
 
68
325
  /**
69
- * Call a function, but if it's recently been called delay execution aka back off
326
+ * Call a function, but if it's recently been called delay execution (aka back off)
70
327
  *
71
328
  * This can be used in a totally disjoined asynchronous way
72
329
  *
@@ -76,91 +333,67 @@ class BackOff {
76
333
  * 4. Repeat steps 2 and 3, with an exponential increasing delay
77
334
  *
78
335
  * This can be called repeatedly, if the previous delay is still running new requests will be ignored
336
+ *
337
+ * @template MetadataType - Type of configuration that can be internally stored with the backoff record
79
338
  */
80
- async execute<T = any>({
339
+ public async execute<MetadataType = any>({
81
340
  id, description, retries = 10, delayedFn, canFn = async() => true, metadata
82
- }: {
83
- /**
84
- * Unique id for the execution of this function.
85
- *
86
- * This will be used to delay further executions, and also to cancel it
87
- */
88
- id: string,
89
- /**
90
- * Basic text description to use in logging
91
- */
92
- description: string,
93
- /**
94
- * Number of executions allowed before flatly refusing to call more. Defaults to 10
95
- */
96
- retries?: number,
97
- /**
98
- * Before calling delayedFn check if it can still run
99
- *
100
- * Useful for checking state after a looong delay
101
- */
102
- canFn?: () => Promise<boolean>,
103
- /**
104
- * Call this function
105
- * - if it's not already waiting to run
106
- * - if it's passed canFn
107
- * - if it hasn't been tried over `retries` amount
108
- *
109
- * The function will be increasingly (exponentially) delayed if it has previously been called
110
- */
111
- delayedFn: () => Promise<any>,
112
- /**
113
- * Anything that might be important outside of this file (used with `getBackOff`)
114
- */
115
- metadata?: T,
116
- }): Promise<NodeJS.Timeout | undefined> {
341
+ }: BackOffExecuteArgs<MetadataType>): Promise<NodeJS.Timeout | undefined> {
117
342
  const backOff: BackOffEntry = this.map[id];
118
343
 
119
344
  const cont = await canFn();
120
345
 
121
346
  if (!cont) {
122
- this.log('info', id, 'Skipping (can execute fn test failed)', description);
347
+ this.log('info', {
348
+ id, status: 'Skipping (canExecute test failed)', description, metadata, type: LOG_TYPE.EXECUTE
349
+ });
123
350
 
124
351
  return undefined;
125
- } else if (backOff?.timeoutId) {
126
- this.log('info', id, 'Skipping (previous back off process still running)', description);
352
+ } else if (backOff?.execute?.timeoutId) {
353
+ this.log('info', {
354
+ id, status: 'Skipping (previous back off process still running)', description, metadata, type: LOG_TYPE.EXECUTE
355
+ });
127
356
 
128
- return backOff.timeoutId;
357
+ return backOff?.execute?.timeoutId;
129
358
  } else {
130
359
  const backOffTry = backOff?.try || 0;
131
360
 
132
361
  if (backOffTry + 1 > retries) {
133
- this.log('error', id, 'Aborting (too many retries)', description);
362
+ this.log('error', {
363
+ id, status: 'Aborting (too many retries)', description, metadata, type: LOG_TYPE.EXECUTE
364
+ });
134
365
 
135
366
  return undefined;
136
367
  }
137
368
 
138
- // First step is immediate (0.001s)
139
- // Second and others are exponential
140
- // Try: 1, 2, 3, 4, 5, 6, 7, 8, 9
141
- // Multiple: 1, 4, 9, 16, 25, 36, 49, 64, 81
142
- // Actual Time: 0.25s, 1s, 2.25s, 4s, 6.25s, 9s, 12.25s, 16s, 20.25s
143
- const delay = backOffTry === 0 ? 1 : Math.pow(backOffTry, 2) * 250;
369
+ const delay = this.calcDelay(backOffTry);
370
+ const logLevel = calcLogLevel(backOffTry);
144
371
 
145
- this.log('info', id, `Delaying call (attempt ${ backOffTry + 1 }, delayed by ${ delay }ms)`, description);
372
+ this.log(logLevel, {
373
+ id, status: `Delaying call (attempt ${ backOffTry + 1 }, delayed by ${ delay }ms)`, description, metadata, type: LOG_TYPE.EXECUTE
374
+ });
146
375
 
147
376
  const timeout = setTimeout(async() => {
148
377
  try {
149
- this.log('info', id, `Executing call`, description);
378
+ this.log(logLevel, {
379
+ id, status: `Executing call`, description, metadata, type: LOG_TYPE.EXECUTE
380
+ });
150
381
 
151
382
  await delayedFn();
152
383
  } catch (e) {
153
384
  // Error occurred. Don't clear the map. Next time this is called we'll back off before trying ...
154
- this.log('error', id, 'Failed call', description, e);
385
+ this.log('error', {
386
+ id, status: 'Failed call', description, metadata, type: LOG_TYPE.EXECUTE
387
+ });
155
388
  }
156
389
 
157
390
  // Unblock future calls
158
- delete this.map[id]?.timeoutId;
391
+ delete this.map[id]?.execute?.timeoutId;
159
392
  }, delay);
160
393
 
161
394
  this.map[id] = {
162
- timeoutId: timeout,
163
- try: backOff?.try ? backOff.try + 1 : 1,
395
+ execute: { timeoutId: timeout },
396
+ try: backOff?.try ? backOff.try + 1 : 1,
164
397
  retries,
165
398
  description,
166
399
  metadata
@@ -1,12 +1,13 @@
1
1
  // For testing these could be changed to something like...
2
2
 
3
3
  import { CATALOG } from '@shell/config/types';
4
+ import { ActionFindPageArgs, ActionFindPageTransientResponse } from '@shell/types/store/dashboard-store.types';
4
5
  import { FilterArgs, PaginationFilterField, PaginationParamFilter } from '@shell/types/store/pagination.types';
5
6
  import { VuexStore } from '@shell/types/store/vuex';
6
7
 
7
8
  const CSP_ADAPTER_APPS = ['rancher-csp-adapter', 'rancher-csp-billing-adapter'];
8
9
  // For testing above line could be replaced with below line...
9
- // const cspAdaptorApp = ['rancher-webhooka', 'rancher-webhook'];
10
+ // const CSP_ADAPTER_APPS = ['rancher-webhooka', 'rancher-webhook'];
10
11
 
11
12
  /**
12
13
  * Helpers in order to
@@ -16,27 +17,44 @@ class CspAdapterUtils {
16
17
  return $store.getters[`management/paginationEnabled`]({ id: CATALOG.APP, context: 'branding' });
17
18
  }
18
19
 
19
- static fetchCspAdaptorApp($store: VuexStore): Promise<any> {
20
+ private static apps?: any[] = undefined;
21
+ public static resetState() {
22
+ this.apps = undefined;
23
+ }
24
+
25
+ static async fetchCspAdaptorApp($store: VuexStore): Promise<any> {
26
+ if (this.apps) {
27
+ return this.apps;
28
+ }
29
+
20
30
  // For the login page, the schemas won't be loaded - we don't need the apps in this case
21
31
  if ($store.getters['management/canList'](CATALOG.APP)) {
22
32
  if (CspAdapterUtils.canPagination($store)) {
23
33
  // Restrict the amount of apps we need to fetch
24
- return $store.dispatch('management/findPage', {
34
+ const opt: ActionFindPageArgs = {
35
+ pagination: new FilterArgs({
36
+ filters: PaginationParamFilter.createMultipleFields(CSP_ADAPTER_APPS.map(
37
+ (t) => new PaginationFilterField({
38
+ field: 'metadata.name',
39
+ value: t,
40
+ })
41
+ )),
42
+ }),
43
+ watch: false,
44
+ transient: true
45
+ };
46
+
47
+ const resp: ActionFindPageTransientResponse = await $store.dispatch('management/findPage', {
25
48
  type: CATALOG.APP,
26
- opt: { // Of type ActionFindPageArgs
27
- pagination: new FilterArgs({
28
- filters: PaginationParamFilter.createMultipleFields(CSP_ADAPTER_APPS.map(
29
- (t) => new PaginationFilterField({
30
- field: 'metadata.name',
31
- value: t,
32
- })
33
- )),
34
- })
35
- }
49
+ opt
36
50
  });
51
+
52
+ this.apps = resp.data;
53
+ } else {
54
+ this.apps = await $store.dispatch('management/findAll', { type: CATALOG.APP });
37
55
  }
38
56
 
39
- return $store.dispatch('management/findAll', { type: CATALOG.APP });
57
+ return this.apps;
40
58
  }
41
59
 
42
60
  return Promise.resolve([]);
@@ -244,7 +244,7 @@ describe('dynamic content', () => {
244
244
  logger: mockLogger,
245
245
  config: mockConfig,
246
246
  isAdmin: true,
247
- settings: { releaseNotesUrl: '', suseExtensions: [] },
247
+ settings: { suseExtensions: [] },
248
248
  };
249
249
  });
250
250
 
@@ -7,6 +7,7 @@ import semver from 'semver';
7
7
 
8
8
  describe('processReleaseVersion', () => {
9
9
  let mockContext: Context;
10
+ let mockContextPrime: Context;
10
11
  let mockDispatch: jest.Mock;
11
12
  let mockGetters: any;
12
13
  let mockLogger: any;
@@ -50,10 +51,24 @@ describe('processReleaseVersion', () => {
50
51
  prime: false,
51
52
  distribution: 'community',
52
53
  },
53
- settings: {
54
- releaseNotesUrl: 'https://example.com/releases/v$version',
55
- suseExtensions: [],
54
+ settings: { suseExtensions: [] },
55
+ };
56
+
57
+ mockContextPrime = {
58
+ dispatch: mockDispatch,
59
+ getters: mockGetters,
60
+ axios: {},
61
+ logger: mockLogger,
62
+ isAdmin: true,
63
+ config: {
64
+ enabled: true,
65
+ debug: false,
66
+ log: false,
67
+ endpoint: '',
68
+ prime: true,
69
+ distribution: 'prime',
56
70
  },
71
+ settings: { suseExtensions: [] },
57
72
  };
58
73
 
59
74
  // Mock the utility function. Default: notification does not exist, so add it.
@@ -119,7 +134,7 @@ describe('processReleaseVersion', () => {
119
134
  },
120
135
  primaryAction: {
121
136
  label: 'More Info',
122
- target: 'https://example.com/releases/v2.12.1',
137
+ target: 'https://github.com/rancher/rancher/releases/tag/v2.12.1',
123
138
  },
124
139
  });
125
140
  expect(mockRemoveMatchingNotifications).toHaveBeenCalledWith(mockContext, 'new-release-', '2.12.1');
@@ -145,12 +160,38 @@ describe('processReleaseVersion', () => {
145
160
  },
146
161
  primaryAction: {
147
162
  label: 'More Info',
148
- target: 'https://example.com/releases/v2.13.0',
163
+ target: 'https://github.com/rancher/rancher/releases/tag/v2.13.0',
149
164
  },
150
165
  });
151
166
  expect(mockRemoveMatchingNotifications).toHaveBeenCalledWith(mockContext, 'new-release-', '2.13.0');
152
167
  });
153
168
 
169
+ it('should add a single new release notification for a newer major/minor version (no patch) (Prime)', async() => {
170
+ const releaseInfo = [{ name: '2.13.0' }, { name: '2.12.0' }];
171
+ const versionInfo: VersionInfo = { version: semver.coerce('2.12.0')!, isPrime: true };
172
+
173
+ await processReleaseVersion(mockContextPrime, releaseInfo, versionInfo);
174
+
175
+ expect(mockLogger.info).toHaveBeenCalledWith('Found a newer release: 2.13.0');
176
+ expect(mockLogger.info).not.toHaveBeenCalledWith(expect.stringContaining('Also found a newer patch release'));
177
+ expect(mockDispatch).toHaveBeenCalledTimes(1);
178
+ expect(mockDispatch).toHaveBeenCalledWith('notifications/add', {
179
+ id: 'new-release-2.13.0',
180
+ level: NotificationLevel.Announcement,
181
+ title: 'A new Rancher release is available',
182
+ message: 'Rancher 2.13.0 has been released',
183
+ preference: {
184
+ key: READ_NEW_RELEASE,
185
+ value: '2.13.0',
186
+ },
187
+ primaryAction: {
188
+ label: 'More Info',
189
+ target: 'https://documentation.suse.com/cloudnative/rancher-manager/v2.13/en/release-notes/v2.13.0.html',
190
+ },
191
+ });
192
+ expect(mockRemoveMatchingNotifications).toHaveBeenCalledWith(mockContextPrime, 'new-release-', '2.13.0');
193
+ });
194
+
154
195
  it('should add a multiple new releases notification when both newer patch and newer major/minor exist', async() => {
155
196
  const releaseInfo = [{ name: '2.13.0' }, { name: '2.12.1' }, { name: '2.12.0' }];
156
197
  const versionInfo: VersionInfo = { version: semver.coerce('2.12.0')!, isPrime: false };
@@ -171,11 +212,11 @@ describe('processReleaseVersion', () => {
171
212
  },
172
213
  primaryAction: {
173
214
  label: 'More Info for 2.12.1',
174
- target: 'https://example.com/releases/v2.12.1',
215
+ target: 'https://github.com/rancher/rancher/releases/tag/v2.12.1',
175
216
  },
176
217
  secondaryAction: {
177
218
  label: 'More Info for 2.13.0',
178
- target: 'https://example.com/releases/v2.13.0',
219
+ target: 'https://github.com/rancher/rancher/releases/tag/v2.13.0',
179
220
  },
180
221
  });
181
222
  expect(mockRemoveMatchingNotifications).toHaveBeenCalledWith(mockContext, 'new-release-', '2.12.1-2.13.0');
@@ -52,10 +52,7 @@ describe('processSupportNotices', () => {
52
52
  prime: false,
53
53
  distribution: 'community',
54
54
  },
55
- settings: {
56
- releaseNotesUrl: '',
57
- suseExtensions: [],
58
- },
55
+ settings: { suseExtensions: [] },
59
56
  };
60
57
 
61
58
  // Mock the utility function. Default: notification does not exist, so add it.
@@ -30,8 +30,6 @@ const LOCAL_STORAGE_UPDATE_FETCHING = 'rancher-updates-fetching'; // Local stora
30
30
 
31
31
  const BACKOFFS = [1, 1, 1, 2, 2, 3, 5]; // Backoff in days for the contiguous number of errors (i.e. after 1 errors, we wait 1 day, after 3 errors, we wait 2 days, etc.)
32
32
 
33
- const DEFAULT_RELEASE_NOTES_URL = 'https://github.com/rancher/rancher/releases/tag/v$version'; // Default release notes URL
34
-
35
33
  /**
36
34
  * Fetch dynamic content if needed and process it if it has changed since we last checked
37
35
  */
@@ -63,10 +61,7 @@ export async function fetchAndProcessDynamicContent(dispatch: Function, getters:
63
61
  logger,
64
62
  config,
65
63
  isAdmin: isAdminUser(getters),
66
- settings: {
67
- releaseNotesUrl: DEFAULT_RELEASE_NOTES_URL,
68
- suseExtensions: [],
69
- }
64
+ settings: { suseExtensions: [] }
70
65
  };
71
66
 
72
67
  logger.debug('Read configuration', context.config);