@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
@@ -0,0 +1,44 @@
1
+ import { formatAWSError } from '@shell/utils/error';
2
+ const SOME_ERROR = 'Some error';
3
+
4
+ describe('formatAWSError', () => {
5
+ it('should return the extracted reason when error is a TypeError and message includes "Deserialization error:"', () => {
6
+ const error = Object.assign(new TypeError("Cannot read property of undefined (reading 'Error'): Deserialization error:"), { $response: { reason: SOME_ERROR } });
7
+
8
+ const result = formatAWSError(error);
9
+
10
+ expect(result).toBe(SOME_ERROR);
11
+ });
12
+
13
+ it('should return the original error if it is not an instance of TypeError', () => {
14
+ const error = Object.assign(new Error('...Deserialization error:'), { $response: { reason: SOME_ERROR } });
15
+
16
+ const result = formatAWSError(error);
17
+
18
+ expect(result).toBe(error);
19
+ });
20
+
21
+ it('should return the original error if the message does not contain "Deserialization error:"', () => {
22
+ const error = Object.assign(new TypeError('Some other type error occurred'), { $response: { reason: SOME_ERROR } });
23
+
24
+ const result = formatAWSError(error);
25
+
26
+ expect(result).toBe(error);
27
+ });
28
+
29
+ it('should return the original input if it is a plain object (not an Error instance)', () => {
30
+ const error = {
31
+ message: 'Deserialization error:',
32
+ $response: { reason: 'Should not be returned' }
33
+ };
34
+
35
+ const result = formatAWSError(error);
36
+
37
+ expect(result).toBe(error);
38
+ });
39
+
40
+ it('should return null or undefined as-is without crashing', () => {
41
+ expect(formatAWSError(null)).toBeNull();
42
+ expect(formatAWSError(undefined)).toBeUndefined();
43
+ });
44
+ });
@@ -123,10 +123,17 @@ describe('fx: util.getTargetMode', () => {
123
123
  expect(util.getTargetMode(targets, namespace)).toBe('clusters');
124
124
  });
125
125
 
126
- it('should return "advanced" if one target has clusterGroup but others have clusterName or clusterSelector', () => {
126
+ it('should return "clusters" if one target has clusterGroup but others have clusterName or clusterSelector', () => {
127
127
  const targets = [{ clusterName: 'cluster-x' }, { clusterGroup: 'my-group' }, { clusterSelector: { matchLabels: { env: 'prod' } } }];
128
128
  const namespace = 'ws1';
129
129
 
130
+ expect(util.getTargetMode(targets, namespace)).toBe('clusters');
131
+ });
132
+
133
+ it('should return "advanced" if one target has clusterGroupSelector but others have clusterName or clusterSelector', () => {
134
+ const targets = [{ clusterName: 'cluster-x' }, { clusterGroupSelector: {} }, { clusterSelector: { matchLabels: { env: 'prod' } } }];
135
+ const namespace = 'ws1';
136
+
130
137
  expect(util.getTargetMode(targets, namespace)).toBe('advanced');
131
138
  });
132
139
 
@@ -0,0 +1,167 @@
1
+ import PaginationWrapper from '@shell/utils/pagination-wrapper';
2
+ import paginationUtils from '@shell/utils/pagination-utils';
3
+ import backOff from '@shell/utils/back-off';
4
+ import { STEVE_RESPONSE_CODE } from '@shell/types/rancher/steve.api';
5
+
6
+ jest.mock('@shell/utils/pagination-utils');
7
+ jest.mock('@shell/utils/back-off');
8
+
9
+ describe('paginationWrapper', () => {
10
+ let store: any;
11
+ let args: any;
12
+
13
+ beforeEach(() => {
14
+ store = {
15
+ getters: {},
16
+ dispatch: jest.fn(),
17
+ $extension: {}
18
+ };
19
+ args = {
20
+ $store: store,
21
+ id: 'test-id',
22
+ enabledFor: {
23
+ store: 'management',
24
+ resource: {
25
+ id: 'cluster',
26
+ context: 'side-bar'
27
+ }
28
+ },
29
+ onChange: jest.fn(),
30
+ };
31
+
32
+ (paginationUtils.isEnabled as jest.Mock).mockReturnValue(true);
33
+ (backOff.getBackOff as jest.Mock).mockReturnValue(undefined);
34
+ (backOff.recurse as jest.Mock).mockImplementation(async({ delayedFn }) => {
35
+ return await delayedFn();
36
+ });
37
+ (backOff.reset as jest.Mock).mockImplementation();
38
+ });
39
+
40
+ afterEach(() => {
41
+ jest.clearAllMocks();
42
+ });
43
+
44
+ it('should initialize correctly', () => {
45
+ const wrapper = new PaginationWrapper(args);
46
+
47
+ expect(wrapper.isEnabled).toBe(true);
48
+ expect(paginationUtils.isEnabled).toHaveBeenCalledWith({ rootGetters: store.getters, $extension: store.$extension }, args.enabledFor);
49
+ });
50
+
51
+ it('request should throw if not enabled', async() => {
52
+ (paginationUtils.isEnabled as jest.Mock).mockReturnValue(false);
53
+ const wrapper = new PaginationWrapper(args);
54
+
55
+ await expect(wrapper.request({ pagination: {} as any })).rejects.toThrow('not supported');
56
+ });
57
+
58
+ it('request should handle HA scenario 1,2 case 1: active revision newer than target', async() => {
59
+ (backOff.getBackOff as jest.Mock).mockReturnValue({ metadata: { revision: '10' } });
60
+ const wrapper = new PaginationWrapper(args);
61
+
62
+ await expect(wrapper.request({ pagination: {} as any, revision: '5' })).rejects.toThrow('Ignoring current request');
63
+ });
64
+
65
+ it('request should handle HA scenario 1,2 case 2: cached revision newer than target', async() => {
66
+ const wrapper = new PaginationWrapper(args);
67
+
68
+ // Mock internal state if possible or simulate a previous request
69
+ // Since cachedRevision is private, we can simulate it by making a request first
70
+
71
+ store.dispatch.mockResolvedValue({ pagination: { result: { revision: '10' } }, data: [] });
72
+ await wrapper.request({ pagination: {} as any, revision: '10' }); // Sets cachedRevision to 10
73
+
74
+ await expect(wrapper.request({ pagination: {} as any, revision: '5' })).resolves.toBeDefined(); // Should return cached result
75
+ // Note: The implementation warns but returns cachedResult if available.
76
+ });
77
+
78
+ it('request should handle HA scenario 1,2 case 3: target revision newer than current', async() => {
79
+ (backOff.getBackOff as jest.Mock).mockReturnValue({ metadata: { revision: '5' } });
80
+ const wrapper = new PaginationWrapper(args);
81
+
82
+ store.dispatch.mockResolvedValue({ pagination: { result: { revision: '10' } }, data: [] });
83
+ await wrapper.request({ pagination: {} as any, revision: '10' });
84
+
85
+ expect(backOff.reset).toHaveBeenCalledWith('test-id');
86
+ });
87
+
88
+ it('request should call backOff.recurse and dispatch findPage', async() => {
89
+ const wrapper = new PaginationWrapper(args);
90
+ const pagination = { page: 1 } as any;
91
+
92
+ store.dispatch.mockResolvedValue({ pagination: { result: { revision: '1' } }, data: [] });
93
+
94
+ await wrapper.request({ pagination });
95
+
96
+ expect(backOff.recurse).toHaveBeenCalledWith(expect.objectContaining({
97
+ id: 'test-id',
98
+ metadata: { revision: undefined }
99
+ }));
100
+ expect(store.dispatch).toHaveBeenCalledWith('management/findPage', expect.objectContaining({
101
+ opt: expect.objectContaining({ pagination }),
102
+ type: 'cluster'
103
+ }));
104
+ });
105
+
106
+ it('request should setup watch on first call if onChange provided', async() => {
107
+ const wrapper = new PaginationWrapper(args);
108
+
109
+ store.dispatch.mockResolvedValue({ pagination: { result: { revision: '1' } }, data: [] });
110
+
111
+ await wrapper.request({ pagination: {} as any });
112
+
113
+ expect(store.dispatch).toHaveBeenCalledWith('management/watchEvent', expect.objectContaining({
114
+ id: 'test-id',
115
+ params: expect.objectContaining({ type: 'cluster' })
116
+ }));
117
+ });
118
+
119
+ it('request should classify data if requested', async() => {
120
+ args.formatResponse = { classify: true };
121
+ const wrapper = new PaginationWrapper(args);
122
+ const rawData = [{ id: '1' }];
123
+ const classifiedData = [{ id: '1', _classified: true }];
124
+
125
+ store.dispatch.mockImplementation((action: string) => {
126
+ if (action === 'management/findPage') {
127
+ return { pagination: { result: { revision: '1' } }, data: rawData };
128
+ }
129
+ if (action === 'management/createMany') {
130
+ return classifiedData;
131
+ }
132
+ });
133
+
134
+ const res = await wrapper.request({ pagination: {} as any });
135
+
136
+ expect(res.data).toStrictEqual(classifiedData);
137
+ expect(store.dispatch).toHaveBeenCalledWith('management/createMany', rawData);
138
+ });
139
+
140
+ it('onDestroy should unwatch', async() => {
141
+ const wrapper = new PaginationWrapper(args);
142
+
143
+ store.dispatch.mockResolvedValue({ pagination: { result: { revision: '1' } }, data: [] });
144
+
145
+ await wrapper.request({ pagination: {} as any }); // Setup watch
146
+ await wrapper.onDestroy();
147
+
148
+ expect(store.dispatch).toHaveBeenCalledWith('management/unwatchEvent', expect.objectContaining({ id: 'test-id' }));
149
+ });
150
+
151
+ describe('backOff configuration', () => {
152
+ it('continueOnError should return true for UNKNOWN_REVISION', async() => {
153
+ const wrapper = new PaginationWrapper(args);
154
+
155
+ store.dispatch.mockResolvedValue({ pagination: { result: { revision: '1' } }, data: [] });
156
+
157
+ await wrapper.request({ pagination: {} as any });
158
+
159
+ const recurseCall = (backOff.recurse as jest.Mock).mock.calls[0][0];
160
+ const continueOnError = recurseCall.continueOnError;
161
+
162
+ expect(await continueOnError({ status: 200 })).toBe(false);
163
+ expect(await continueOnError({ status: 400, code: STEVE_RESPONSE_CODE.UNKNOWN_REVISION })).toBe(true);
164
+ expect(await continueOnError({ status: 500 })).toBe(false);
165
+ });
166
+ });
167
+ });
@@ -1,4 +1,4 @@
1
- import { isDevBuild, isUpgradeFromPreToStable } from '@shell/utils/version';
1
+ import { isDevBuild, isUpgradeFromPreToStable, getReleaseNotesURL } from '@shell/utils/version';
2
2
 
3
3
  describe('fx: isDevBuild', () => {
4
4
  it.each([
@@ -34,3 +34,57 @@ describe('fx: isUpgradeFromPreToStable', () => {
34
34
  expect(isUpgradeFromPreToStable('1.0.0-rc1', '1.0.0-rc2')).toBe(false);
35
35
  });
36
36
  });
37
+
38
+ describe('fx: getReleaseNotesURL', () => {
39
+ describe('when version is not provided', () => {
40
+ it('should return the community dev URL', () => {
41
+ expect(getReleaseNotesURL(false, undefined)).toBe('https://github.com/rancher/rancher/releases/latest');
42
+ expect(getReleaseNotesURL(false, '')).toBe('https://github.com/rancher/rancher/releases/latest');
43
+ expect(getReleaseNotesURL(true, undefined)).toBe('https://github.com/rancher/rancher/releases/latest');
44
+ });
45
+ });
46
+
47
+ describe('community (non-prime) URLs', () => {
48
+ it('should return dev URL for dev builds', () => {
49
+ expect(getReleaseNotesURL(false, 'dev')).toBe('https://github.com/rancher/rancher/releases/latest');
50
+ expect(getReleaseNotesURL(false, 'master')).toBe('https://github.com/rancher/rancher/releases/latest');
51
+ expect(getReleaseNotesURL(false, 'head')).toBe('https://github.com/rancher/rancher/releases/latest');
52
+ expect(getReleaseNotesURL(false, '2.8.0-head')).toBe('https://github.com/rancher/rancher/releases/latest');
53
+ expect(getReleaseNotesURL(false, '2.8.0-rc1')).toBe('https://github.com/rancher/rancher/releases/latest');
54
+ expect(getReleaseNotesURL(false, '2.8.0-alpha1')).toBe('https://github.com/rancher/rancher/releases/latest');
55
+ });
56
+
57
+ it('should return release URL with version for stable releases', () => {
58
+ expect(getReleaseNotesURL(false, '2.8.0')).toBe('https://github.com/rancher/rancher/releases/tag/v2.8.0');
59
+ expect(getReleaseNotesURL(false, '2.8.1')).toBe('https://github.com/rancher/rancher/releases/tag/v2.8.1');
60
+ expect(getReleaseNotesURL(false, '2.10.3')).toBe('https://github.com/rancher/rancher/releases/tag/v2.10.3');
61
+ });
62
+
63
+ it('should handle version with v prefix', () => {
64
+ expect(getReleaseNotesURL(false, 'v2.8.0')).toBe('https://github.com/rancher/rancher/releases/tag/v2.8.0');
65
+ expect(getReleaseNotesURL(false, 'v2.10.3')).toBe('https://github.com/rancher/rancher/releases/tag/v2.10.3');
66
+ });
67
+ });
68
+
69
+ describe('prime URLs', () => {
70
+ it('should return dev URL for dev builds', () => {
71
+ expect(getReleaseNotesURL(true, 'dev')).toBe('https://documentation.suse.com/cloudnative/rancher-manager/latest/en/release-notes.html');
72
+ expect(getReleaseNotesURL(true, 'master')).toBe('https://documentation.suse.com/cloudnative/rancher-manager/latest/en/release-notes.html');
73
+ expect(getReleaseNotesURL(true, 'head')).toBe('https://documentation.suse.com/cloudnative/rancher-manager/latest/en/release-notes.html');
74
+ expect(getReleaseNotesURL(true, '2.8.0-head')).toBe('https://documentation.suse.com/cloudnative/rancher-manager/latest/en/release-notes.html');
75
+ expect(getReleaseNotesURL(true, '2.8.0-rc1')).toBe('https://documentation.suse.com/cloudnative/rancher-manager/latest/en/release-notes.html');
76
+ expect(getReleaseNotesURL(true, '2.8.0-alpha1')).toBe('https://documentation.suse.com/cloudnative/rancher-manager/latest/en/release-notes.html');
77
+ });
78
+
79
+ it('should return release URL with version and major.minor for stable releases', () => {
80
+ expect(getReleaseNotesURL(true, '2.8.0')).toBe('https://documentation.suse.com/cloudnative/rancher-manager/v2.8/en/release-notes/v2.8.0.html');
81
+ expect(getReleaseNotesURL(true, '2.8.1')).toBe('https://documentation.suse.com/cloudnative/rancher-manager/v2.8/en/release-notes/v2.8.1.html');
82
+ expect(getReleaseNotesURL(true, '2.10.3')).toBe('https://documentation.suse.com/cloudnative/rancher-manager/v2.10/en/release-notes/v2.10.3.html');
83
+ });
84
+
85
+ it('should handle version with v prefix', () => {
86
+ expect(getReleaseNotesURL(true, 'v2.8.0')).toBe('https://documentation.suse.com/cloudnative/rancher-manager/v2.8/en/release-notes/v2.8.0.html');
87
+ expect(getReleaseNotesURL(true, 'v2.10.3')).toBe('https://documentation.suse.com/cloudnative/rancher-manager/v2.10/en/release-notes/v2.10.3.html');
88
+ });
89
+ });
90
+ });
package/utils/azure.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const AZURE_ERROR_MSG_REGEX = /^.*Message=\"(.*)\"$/;
2
2
  const AZURE_ERROR_JSON_REGEX = /^.*Response body: ({.*})/;
3
+ const AZURE_ERROR_JSON_REGEX_MULTILINE = /({[\s\S]*"error":[\s\S]*})/;
3
4
 
4
5
  export const parseAzureError = (err) => {
5
6
  // Try and parse the response from Azure a couple of ways
@@ -17,6 +18,17 @@ export const parseAzureError = (err) => {
17
18
  return errorObj.error_description;
18
19
  } catch (e) {}
19
20
  }
21
+ const jsonMatchMultiline = err.match(AZURE_ERROR_JSON_REGEX_MULTILINE);
22
+
23
+ if (jsonMatchMultiline?.length === 2) {
24
+ try {
25
+ const errorObj = JSON.parse(jsonMatchMultiline[1]);
26
+
27
+ if (errorObj?.error?.message) {
28
+ return errorObj.error.message;
29
+ }
30
+ } catch (e) {}
31
+ }
20
32
  }
21
33
 
22
34
  // Can't parse error