@rancher/shell 3.0.7 → 3.0.8-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.
- package/assets/images/vendor/githubapp.svg +13 -0
- package/assets/styles/base/_typography.scss +1 -1
- package/assets/styles/global/_layout.scss +21 -35
- package/assets/styles/themes/_modern.scss +5 -5
- package/assets/translations/en-us.yaml +102 -17
- package/assets/translations/zh-hans.yaml +0 -4
- package/components/EmberPage.vue +1 -1
- package/components/Inactivity.vue +222 -106
- package/components/InstallHelmCharts.vue +2 -2
- package/components/Resource/Detail/CopyToClipboard.vue +1 -1
- package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +0 -2
- package/components/Resource/Detail/TitleBar/index.vue +10 -6
- package/components/ResourceDetail/index.vue +4 -1
- package/components/SortableTable/index.vue +18 -2
- package/components/{nav/WindowManager → Window}/ContainerLogs.vue +1 -1
- package/components/{nav/WindowManager → Window}/ContainerLogsActions.vue +1 -0
- package/components/{nav/WindowManager → Window}/__tests__/ContainerLogs.test.ts +1 -1
- package/components/{nav/WindowManager → Window}/__tests__/ContainerShell.test.ts +2 -2
- package/components/fleet/FleetConfigMapSelector.vue +117 -0
- package/components/fleet/FleetSecretSelector.vue +127 -0
- package/components/fleet/__tests__/FleetConfigMapSelector.test.ts +125 -0
- package/components/fleet/__tests__/FleetSecretSelector.test.ts +82 -0
- package/components/form/FileImageSelector.vue +13 -4
- package/components/form/FileSelector.vue +11 -2
- package/components/form/ResourceLabeledSelect.vue +1 -0
- package/components/form/__tests__/ResourceLabeledSelect.test.ts +90 -0
- package/components/nav/Header.vue +34 -13
- package/components/{DraggableZone.vue → nav/WindowManager/PinArea.vue} +47 -80
- package/components/nav/WindowManager/composables/useComponentsMount.ts +70 -0
- package/components/nav/WindowManager/composables/useDimensionsHandler.ts +105 -0
- package/components/nav/WindowManager/composables/useDragHandler.ts +99 -0
- package/components/nav/WindowManager/composables/usePanelHandler.ts +72 -0
- package/components/nav/WindowManager/composables/usePanelsHandler.ts +14 -0
- package/components/nav/WindowManager/composables/useResizeHandler.ts +167 -0
- package/components/nav/WindowManager/composables/useTabsHandler.ts +51 -0
- package/components/nav/WindowManager/constants.ts +23 -0
- package/components/nav/WindowManager/index.vue +61 -575
- package/components/nav/WindowManager/panels/HorizontalPanel.vue +265 -0
- package/components/nav/WindowManager/panels/TabBodyContainer.vue +39 -0
- package/components/nav/WindowManager/panels/VerticalPanel.vue +308 -0
- package/components/templates/default.vue +4 -40
- package/components/templates/home.vue +31 -5
- package/config/product/auth.js +1 -0
- package/config/query-params.js +1 -0
- package/config/settings.ts +8 -1
- package/config/store.js +4 -2
- package/config/types.js +2 -0
- package/detail/pod.vue +1 -0
- package/dialog/AddonConfigConfirmationDialog.vue +45 -1
- package/directives/ui-context.ts +97 -0
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +52 -11
- package/edit/auth/AuthProviderWarningBanners.vue +14 -1
- package/edit/auth/github-app-steps.vue +97 -0
- package/edit/auth/github-steps.vue +75 -0
- package/edit/auth/github.vue +94 -65
- package/edit/fleet.cattle.io.helmop.vue +51 -2
- package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +15 -5
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +11 -9
- package/edit/provisioning.cattle.io.cluster/rke2.vue +56 -9
- package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +28 -2
- package/initialize/install-directives.js +2 -0
- package/list/projectsecret.vue +1 -1
- package/machine-config/azure.vue +1 -1
- package/mixins/chart.js +1 -1
- package/models/__tests__/chart.test.ts +17 -9
- package/models/__tests__/compliance.cattle.io.clusterscanprofile.spec.js +30 -0
- package/models/catalog.cattle.io.app.js +1 -1
- package/models/chart.js +3 -1
- package/models/compliance.cattle.io.clusterscanprofile.js +1 -1
- package/models/management.cattle.io.authconfig.js +1 -0
- package/package.json +2 -2
- package/pages/auth/login.vue +5 -2
- package/pages/auth/verify.vue +1 -1
- package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +3 -2
- package/pages/c/_cluster/apps/charts/chart.vue +2 -2
- package/pages/c/_cluster/explorer/EventsTable.vue +89 -3
- package/pages/c/_cluster/explorer/tools/index.vue +3 -3
- package/pages/c/_cluster/settings/performance.vue +12 -25
- package/pages/home.vue +313 -12
- package/plugins/axios.js +2 -1
- package/plugins/dashboard-store/actions.js +1 -1
- package/plugins/dashboard-store/resource-class.js +17 -2
- package/plugins/steve/steve-pagination-utils.ts +2 -2
- package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +5 -1
- package/scripts/extension/publish +1 -1
- package/store/auth.js +8 -3
- package/store/aws.js +8 -6
- package/store/features.js +1 -0
- package/store/index.js +9 -3
- package/store/prefs.js +6 -0
- package/store/ui-context.ts +86 -0
- package/store/wm.ts +244 -0
- package/types/kube/kube-api.ts +2 -1
- package/types/rancher/index.d.ts +1 -0
- package/types/resources/settings.d.ts +29 -7
- package/types/shell/index.d.ts +59 -0
- package/types/window-manager.ts +22 -0
- package/utils/__tests__/cluster.test.ts +379 -1
- package/utils/cluster.js +157 -3
- package/utils/dynamic-content/__tests__/config.test.ts +187 -0
- package/utils/dynamic-content/__tests__/index.test.ts +390 -0
- package/utils/dynamic-content/__tests__/info.test.ts +263 -0
- package/utils/dynamic-content/__tests__/new-release.test.ts +216 -0
- package/utils/dynamic-content/__tests__/support-notice.test.ts +262 -0
- package/utils/dynamic-content/__tests__/util.test.ts +235 -0
- package/utils/dynamic-content/config.ts +55 -0
- package/utils/dynamic-content/index.ts +273 -0
- package/utils/dynamic-content/info.ts +219 -0
- package/utils/dynamic-content/new-release.ts +126 -0
- package/utils/dynamic-content/support-notice.ts +169 -0
- package/utils/dynamic-content/types.d.ts +101 -0
- package/utils/dynamic-content/util.ts +122 -0
- package/utils/dynamic-importer.js +2 -2
- package/utils/inactivity.ts +104 -0
- package/utils/pagination-utils.ts +19 -4
- package/utils/release-notes.ts +1 -1
- package/assets/images/icons/document.svg +0 -3
- package/store/wm.js +0 -95
- /package/components/{nav/WindowManager → Window}/ChartReadme.vue +0 -0
- /package/components/{nav/WindowManager → Window}/ContainerShell.vue +0 -0
- /package/components/{nav/WindowManager → Window}/KubectlShell.vue +0 -0
- /package/components/{nav/WindowManager → Window}/MachineSsh.vue +0 -0
- /package/components/{nav/WindowManager → Window}/Window.vue +0 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { SystemInfoProvider } from '../info';
|
|
2
|
+
import { MANAGEMENT, COUNT } from '@shell/config/types';
|
|
3
|
+
import { SETTING } from '@shell/config/settings';
|
|
4
|
+
import * as version from '@shell/config/version';
|
|
5
|
+
import { sha256 } from '@shell/utils/crypto';
|
|
6
|
+
|
|
7
|
+
// Mock dependencies from @shell
|
|
8
|
+
jest.mock('@shell/config/version', () => ({ getVersionData: jest.fn(), isRancherPrime: jest.fn() }));
|
|
9
|
+
|
|
10
|
+
jest.mock('@shell/utils/crypto', () => ({ sha256: jest.fn((val: string) => `hashed_${ val }`) }));
|
|
11
|
+
|
|
12
|
+
describe('systemInfoProvider', () => {
|
|
13
|
+
let mockGetters: any;
|
|
14
|
+
let mockSettings: any[];
|
|
15
|
+
let mockClusters: any[];
|
|
16
|
+
let mockCounts: any;
|
|
17
|
+
let mockPlugins: any[];
|
|
18
|
+
let originalWindowLocation: Location;
|
|
19
|
+
|
|
20
|
+
beforeAll(() => {
|
|
21
|
+
originalWindowLocation = window.location;
|
|
22
|
+
// Mock window.location
|
|
23
|
+
delete (window as any).location;
|
|
24
|
+
(window as any).location = { host: 'fallback.host' };
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterAll(() => {
|
|
28
|
+
(window as any).location = originalWindowLocation;
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
// Reset mocks
|
|
33
|
+
(version.getVersionData as jest.Mock).mockClear();
|
|
34
|
+
(version.isRancherPrime as jest.Mock).mockClear();
|
|
35
|
+
(sha256 as jest.Mock).mockClear();
|
|
36
|
+
|
|
37
|
+
// Mock window properties
|
|
38
|
+
Object.defineProperty(window, 'screen', {
|
|
39
|
+
value: { width: 1920, height: 1080 },
|
|
40
|
+
writable: true
|
|
41
|
+
});
|
|
42
|
+
Object.defineProperty(window, 'innerWidth', { value: 1024, writable: true });
|
|
43
|
+
Object.defineProperty(window, 'innerHeight', { value: 768, writable: true });
|
|
44
|
+
Object.defineProperty(window.navigator, 'language', { value: 'en-US', writable: true });
|
|
45
|
+
|
|
46
|
+
mockSettings = [
|
|
47
|
+
{ id: SETTING.SERVER_URL, value: 'https://rancher.test' },
|
|
48
|
+
{ id: 'install-uuid', value: 'test-uuid' },
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
mockClusters = [
|
|
52
|
+
{
|
|
53
|
+
id: 'local',
|
|
54
|
+
isLocal: true,
|
|
55
|
+
kubernetesVersionBase: '1.25',
|
|
56
|
+
provisioner: 'k3s',
|
|
57
|
+
status: { nodeCount: 3 },
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 'c-12345',
|
|
61
|
+
isLocal: false,
|
|
62
|
+
}
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
mockCounts = [{ counts: { [MANAGEMENT.CLUSTER]: { summary: { count: 2 } } } }];
|
|
66
|
+
|
|
67
|
+
mockPlugins = [
|
|
68
|
+
{ name: 'harvester', builtin: false },
|
|
69
|
+
{ name: 'some-custom-ext', builtin: false },
|
|
70
|
+
{ name: 'core', builtin: true },
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
mockGetters = {
|
|
74
|
+
'management/typeRegistered': jest.fn().mockReturnValue(true),
|
|
75
|
+
'management/all': jest.fn((type: string) => {
|
|
76
|
+
if (type === MANAGEMENT.SETTING) {
|
|
77
|
+
return mockSettings;
|
|
78
|
+
}
|
|
79
|
+
if (type === MANAGEMENT.CLUSTER) {
|
|
80
|
+
return mockClusters;
|
|
81
|
+
}
|
|
82
|
+
if (type === COUNT) {
|
|
83
|
+
return mockCounts;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return [];
|
|
87
|
+
}),
|
|
88
|
+
'auth/principalId': 'user-123',
|
|
89
|
+
'uiplugins/plugins': mockPlugins,
|
|
90
|
+
isSingleProduct: false,
|
|
91
|
+
'management/byId': jest.fn((type: string, id: string) => {
|
|
92
|
+
if (type === MANAGEMENT.SETTING) {
|
|
93
|
+
return mockSettings.find((s) => s.id === id) || null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return undefined;
|
|
97
|
+
}),
|
|
98
|
+
'management/schemaFor': jest.fn(),
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
(version.getVersionData as jest.Mock).mockReturnValue({
|
|
102
|
+
Version: '2.8.0-rc1',
|
|
103
|
+
RancherPrime: 'false',
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
(version.isRancherPrime as jest.Mock).mockReturnValue(false);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should build a complete query string with all available data', () => {
|
|
110
|
+
const infoProvider = new SystemInfoProvider(mockGetters, {});
|
|
111
|
+
const qs = infoProvider.buildQueryString();
|
|
112
|
+
|
|
113
|
+
expect(qs).toContain('dcv=v1');
|
|
114
|
+
expect(qs).toContain('s=hashed_https://rancher.test');
|
|
115
|
+
expect(qs).toContain('u=hashed_user-123');
|
|
116
|
+
expect(qs).toContain('uuid=test-uuid');
|
|
117
|
+
expect(qs).toContain('v=2.8.0');
|
|
118
|
+
expect(qs).toContain('dev=true');
|
|
119
|
+
expect(qs).toContain('p=false');
|
|
120
|
+
// Add back when LTS is added
|
|
121
|
+
// expect(qs).toContain('lts=false');
|
|
122
|
+
expect(qs).toContain('cc=2');
|
|
123
|
+
expect(qs).toContain('lkv=1.25');
|
|
124
|
+
expect(qs).toContain('lcp=k3s');
|
|
125
|
+
expect(qs).toContain('lnc=3');
|
|
126
|
+
expect(qs).toContain('xkn=harvester');
|
|
127
|
+
expect(qs).toContain('xcc=1');
|
|
128
|
+
expect(qs).toContain('bl=en-US');
|
|
129
|
+
expect(qs).toContain('bs=1024x768');
|
|
130
|
+
expect(qs).toContain('ss=1920x1080');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should handle missing or partial data gracefully', () => {
|
|
134
|
+
// Override mocks for this test
|
|
135
|
+
(version.getVersionData as jest.Mock).mockReturnValue({
|
|
136
|
+
Version: '2.9.0', // Not a dev version
|
|
137
|
+
RancherPrime: 'true',
|
|
138
|
+
});
|
|
139
|
+
(version.isRancherPrime as jest.Mock).mockReturnValue(true);
|
|
140
|
+
|
|
141
|
+
mockGetters['management/all'].mockImplementation((type: string) => {
|
|
142
|
+
if (type === MANAGEMENT.SETTING) {
|
|
143
|
+
return [{ id: 'install-uuid', value: 'only-uuid' }]; // No server-url
|
|
144
|
+
}
|
|
145
|
+
if (type === COUNT) {
|
|
146
|
+
return [{ counts: { [MANAGEMENT.CLUSTER]: { summary: { count: 27 } } } }];
|
|
147
|
+
}
|
|
148
|
+
if (type === MANAGEMENT.CLUSTER) {
|
|
149
|
+
return []; // No clusters
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return [];
|
|
153
|
+
});
|
|
154
|
+
mockGetters['management/byId'].mockImplementation((type: string, id: string) => {
|
|
155
|
+
if (type === MANAGEMENT.SETTING && id === 'install-uuid') {
|
|
156
|
+
return { id: 'install-uuid', value: 'only-uuid' }; // No server-url
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
mockGetters['uiplugins/plugins'] = null; // No plugins
|
|
161
|
+
mockGetters['auth/principalId'] = null; // No user
|
|
162
|
+
|
|
163
|
+
const infoProvider = new SystemInfoProvider(mockGetters, {});
|
|
164
|
+
const qs = infoProvider.buildQueryString();
|
|
165
|
+
|
|
166
|
+
expect(qs).toContain('s=hashed_fallback.host');
|
|
167
|
+
expect(qs).toContain('u=hashed_unknown');
|
|
168
|
+
expect(qs).toContain('uuid=only-uuid');
|
|
169
|
+
expect(qs).toContain('v=2.9.0');
|
|
170
|
+
expect(qs).toContain('dev=false');
|
|
171
|
+
expect(qs).toContain('p=true');
|
|
172
|
+
expect(qs).toContain('cc=27');
|
|
173
|
+
expect(qs).not.toContain('lkv=');
|
|
174
|
+
expect(qs).not.toContain('lcp=');
|
|
175
|
+
expect(qs).not.toContain('lnc=');
|
|
176
|
+
expect(qs).not.toContain('xkn=');
|
|
177
|
+
expect(qs).not.toContain('xcc=');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should handle getAll returning undefined when types are not registered', () => {
|
|
181
|
+
// Override mocks for this test
|
|
182
|
+
(version.getVersionData as jest.Mock).mockReturnValue({
|
|
183
|
+
Version: '2.9.1',
|
|
184
|
+
RancherPrime: 'false',
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Simulate that the management types are not registered in the store
|
|
188
|
+
mockGetters['management/typeRegistered'].mockReturnValue(false);
|
|
189
|
+
mockGetters['management/all'].mockImplementation();
|
|
190
|
+
mockGetters['management/byId'].mockImplementation();
|
|
191
|
+
|
|
192
|
+
mockGetters['auth/principalId'] = 'user-456';
|
|
193
|
+
mockGetters['uiplugins/plugins'] = []; // No plugins
|
|
194
|
+
|
|
195
|
+
const infoProvider = new SystemInfoProvider(mockGetters, {});
|
|
196
|
+
const qs = infoProvider.buildQueryString();
|
|
197
|
+
|
|
198
|
+
expect(mockGetters['management/byId']).toHaveBeenCalledWith(MANAGEMENT.SETTING, 'server-url');
|
|
199
|
+
expect(mockGetters['management/byId']).toHaveBeenCalledWith(MANAGEMENT.SETTING, 'install-uuid');
|
|
200
|
+
expect(mockGetters['management/byId']).toHaveBeenCalledWith(MANAGEMENT.SETTING, 'server-version-type');
|
|
201
|
+
expect(mockGetters['management/typeRegistered']).toHaveBeenCalledWith(COUNT);
|
|
202
|
+
expect(mockGetters['management/typeRegistered']).toHaveBeenCalledWith(MANAGEMENT.CLUSTER);
|
|
203
|
+
expect(mockGetters['management/all']).not.toHaveBeenCalled();
|
|
204
|
+
|
|
205
|
+
// Verify the query string is built with fallback or empty values
|
|
206
|
+
expect(qs).toContain('s=hashed_fallback.host');
|
|
207
|
+
expect(qs).toContain('u=hashed_user-456');
|
|
208
|
+
expect(qs).toContain('v=2.9.1');
|
|
209
|
+
expect(qs).toContain('p=false');
|
|
210
|
+
expect(qs).toContain('xcc=0');
|
|
211
|
+
expect(qs).not.toContain('uuid=');
|
|
212
|
+
expect(qs).not.toContain('lkv=');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should use UNKNOWN for missing system properties', () => {
|
|
216
|
+
// Override mocks for this test
|
|
217
|
+
(version.getVersionData as jest.Mock).mockReturnValue({
|
|
218
|
+
Version: '2.9.0',
|
|
219
|
+
RancherPrime: 'false',
|
|
220
|
+
});
|
|
221
|
+
(version.isRancherPrime as jest.Mock).mockReturnValue(false);
|
|
222
|
+
|
|
223
|
+
mockGetters['management/byId'].mockImplementation((type: string, id: string) => {
|
|
224
|
+
if (type === MANAGEMENT.SETTING) {
|
|
225
|
+
return { id, value: '' }; // Empty values for all settings
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
mockGetters['management/all'].mockImplementation((type: string) => {
|
|
229
|
+
if (type === MANAGEMENT.SETTING) {
|
|
230
|
+
// Return settings, but with empty values
|
|
231
|
+
return [
|
|
232
|
+
{ id: SETTING.SERVER_URL, value: '' },
|
|
233
|
+
{ id: 'install-uuid', value: '' },
|
|
234
|
+
];
|
|
235
|
+
}
|
|
236
|
+
if (type === COUNT) {
|
|
237
|
+
return [{ counts: { [MANAGEMENT.CLUSTER]: { summary: { count: 1 } } } }];
|
|
238
|
+
}
|
|
239
|
+
if (type === MANAGEMENT.CLUSTER) {
|
|
240
|
+
// local cluster with missing properties
|
|
241
|
+
return [{
|
|
242
|
+
id: 'local',
|
|
243
|
+
isLocal: true,
|
|
244
|
+
status: { nodeCount: 1 },
|
|
245
|
+
// kubernetesVersionBase is missing
|
|
246
|
+
// provisioner is missing
|
|
247
|
+
}];
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return [];
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
mockGetters['auth/principalId'] = null; // No user
|
|
254
|
+
|
|
255
|
+
const infoProvider = new SystemInfoProvider(mockGetters, {});
|
|
256
|
+
const qs = infoProvider.buildQueryString();
|
|
257
|
+
|
|
258
|
+
expect(qs).toContain('u=hashed_unknown');
|
|
259
|
+
expect(qs).toContain('lkv=unknown');
|
|
260
|
+
expect(qs).toContain('lcp=unknown');
|
|
261
|
+
expect(qs).not.toContain('uuid='); // systemUUID is UNKNOWN, so it's skipped
|
|
262
|
+
});
|
|
263
|
+
});
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { processReleaseVersion } from '../new-release';
|
|
2
|
+
import { NotificationLevel } from '@shell/types/notifications';
|
|
3
|
+
import { READ_NEW_RELEASE } from '@shell/store/prefs';
|
|
4
|
+
import { Context, VersionInfo } from '../types';
|
|
5
|
+
import * as util from '../util'; // Import util to mock removeMatchingNotifications
|
|
6
|
+
import semver from 'semver';
|
|
7
|
+
|
|
8
|
+
describe('processReleaseVersion', () => {
|
|
9
|
+
let mockContext: Context;
|
|
10
|
+
let mockDispatch: jest.Mock;
|
|
11
|
+
let mockGetters: any;
|
|
12
|
+
let mockLogger: any;
|
|
13
|
+
let mockRemoveMatchingNotifications: jest.SpyInstance;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
mockDispatch = jest.fn();
|
|
17
|
+
mockGetters = {
|
|
18
|
+
'prefs/get': jest.fn(),
|
|
19
|
+
'i18n/t': jest.fn((key: string, params?: any) => {
|
|
20
|
+
// Simple mock for i18n/t to return predictable strings
|
|
21
|
+
const { version, version1, version2 } = params || {};
|
|
22
|
+
|
|
23
|
+
if (key === 'dynamicContent.newRelease.title') return `A new Rancher release is available`;
|
|
24
|
+
if (key === 'dynamicContent.newRelease.message') return `Rancher ${ version } has been released`;
|
|
25
|
+
if (key === 'dynamicContent.newRelease.moreInfo') return `More Info`;
|
|
26
|
+
if (key === 'dynamicContent.multipleNewReleases.title') return 'New Rancher releases are available';
|
|
27
|
+
if (key === 'dynamicContent.multipleNewReleases.message') return `Message for ${ version1 } and ${ version2 }`;
|
|
28
|
+
if (key === 'dynamicContent.multipleNewReleases.moreInfo') return `More Info for ${ version }`;
|
|
29
|
+
|
|
30
|
+
return key;
|
|
31
|
+
}),
|
|
32
|
+
};
|
|
33
|
+
mockLogger = {
|
|
34
|
+
info: jest.fn(),
|
|
35
|
+
debug: jest.fn(),
|
|
36
|
+
error: jest.fn(),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
mockContext = {
|
|
40
|
+
dispatch: mockDispatch,
|
|
41
|
+
getters: mockGetters,
|
|
42
|
+
axios: {},
|
|
43
|
+
logger: mockLogger,
|
|
44
|
+
isAdmin: true,
|
|
45
|
+
config: {
|
|
46
|
+
enabled: true,
|
|
47
|
+
debug: false,
|
|
48
|
+
log: false,
|
|
49
|
+
endpoint: '',
|
|
50
|
+
prime: false,
|
|
51
|
+
distribution: 'community',
|
|
52
|
+
},
|
|
53
|
+
settings: {
|
|
54
|
+
releaseNotesUrl: 'https://example.com/releases/v$version',
|
|
55
|
+
suseExtensions: [],
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Mock the utility function. Default: notification does not exist, so add it.
|
|
60
|
+
mockRemoveMatchingNotifications = jest.spyOn(util, 'removeMatchingNotifications')
|
|
61
|
+
.mockResolvedValue(false);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
afterEach(() => {
|
|
65
|
+
jest.restoreAllMocks();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should return early if releaseInfo is null/undefined or empty', async() => {
|
|
69
|
+
const versionInfo: VersionInfo = { version: semver.coerce('2.12.0')!, isPrime: false };
|
|
70
|
+
|
|
71
|
+
await processReleaseVersion(mockContext, null as any, versionInfo);
|
|
72
|
+
expect(mockDispatch).not.toHaveBeenCalled();
|
|
73
|
+
expect(mockLogger.info).not.toHaveBeenCalled();
|
|
74
|
+
|
|
75
|
+
await processReleaseVersion(mockContext, [], versionInfo);
|
|
76
|
+
expect(mockDispatch).not.toHaveBeenCalled();
|
|
77
|
+
expect(mockLogger.info).not.toHaveBeenCalled();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should return early if versionInfo is null/undefined or version is missing', async() => {
|
|
81
|
+
const releaseInfo = [{ name: '2.12.1' }];
|
|
82
|
+
|
|
83
|
+
await processReleaseVersion(mockContext, releaseInfo, null as any);
|
|
84
|
+
expect(mockDispatch).not.toHaveBeenCalled();
|
|
85
|
+
expect(mockLogger.info).not.toHaveBeenCalled();
|
|
86
|
+
|
|
87
|
+
await processReleaseVersion(mockContext, releaseInfo, { version: null as any, isPrime: false });
|
|
88
|
+
expect(mockDispatch).not.toHaveBeenCalled();
|
|
89
|
+
expect(mockLogger.info).not.toHaveBeenCalled();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should not add notification if no newer release exists', async() => {
|
|
93
|
+
const releaseInfo = [{ name: '2.12.0' }, { name: '2.11.0' }];
|
|
94
|
+
const versionInfo: VersionInfo = { version: semver.coerce('2.12.0')!, isPrime: false };
|
|
95
|
+
|
|
96
|
+
await processReleaseVersion(mockContext, releaseInfo, versionInfo);
|
|
97
|
+
|
|
98
|
+
expect(mockLogger.info).not.toHaveBeenCalledWith(expect.stringContaining('Found a newer release'));
|
|
99
|
+
expect(mockDispatch).not.toHaveBeenCalled();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should add a single new release notification for a newer patch version', async() => {
|
|
103
|
+
const releaseInfo = [{ name: '2.12.1' }, { name: '2.12.0' }, { name: '2.11.0' }];
|
|
104
|
+
const versionInfo: VersionInfo = { version: semver.coerce('2.12.0')!, isPrime: false };
|
|
105
|
+
|
|
106
|
+
await processReleaseVersion(mockContext, releaseInfo, versionInfo);
|
|
107
|
+
|
|
108
|
+
expect(mockLogger.info).toHaveBeenCalledWith('Found a newer release: 2.12.1');
|
|
109
|
+
expect(mockLogger.info).not.toHaveBeenCalledWith(expect.stringContaining('Also found a newer patch release')); // Because newer and newerPatch are the same
|
|
110
|
+
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
|
111
|
+
expect(mockDispatch).toHaveBeenCalledWith('notifications/add', {
|
|
112
|
+
id: 'new-release-2.12.1',
|
|
113
|
+
level: NotificationLevel.Announcement,
|
|
114
|
+
title: 'A new Rancher release is available',
|
|
115
|
+
message: 'Rancher 2.12.1 has been released',
|
|
116
|
+
preference: {
|
|
117
|
+
key: READ_NEW_RELEASE,
|
|
118
|
+
value: '2.12.1',
|
|
119
|
+
},
|
|
120
|
+
primaryAction: {
|
|
121
|
+
label: 'More Info',
|
|
122
|
+
target: 'https://example.com/releases/v2.12.1',
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
expect(mockRemoveMatchingNotifications).toHaveBeenCalledWith(mockContext, 'new-release-', '2.12.1');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should add a single new release notification for a newer major/minor version (no patch)', async() => {
|
|
129
|
+
const releaseInfo = [{ name: '2.13.0' }, { name: '2.12.0' }];
|
|
130
|
+
const versionInfo: VersionInfo = { version: semver.coerce('2.12.0')!, isPrime: false };
|
|
131
|
+
|
|
132
|
+
await processReleaseVersion(mockContext, releaseInfo, versionInfo);
|
|
133
|
+
|
|
134
|
+
expect(mockLogger.info).toHaveBeenCalledWith('Found a newer release: 2.13.0');
|
|
135
|
+
expect(mockLogger.info).not.toHaveBeenCalledWith(expect.stringContaining('Also found a newer patch release'));
|
|
136
|
+
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
|
137
|
+
expect(mockDispatch).toHaveBeenCalledWith('notifications/add', {
|
|
138
|
+
id: 'new-release-2.13.0',
|
|
139
|
+
level: NotificationLevel.Announcement,
|
|
140
|
+
title: 'A new Rancher release is available',
|
|
141
|
+
message: 'Rancher 2.13.0 has been released',
|
|
142
|
+
preference: {
|
|
143
|
+
key: READ_NEW_RELEASE,
|
|
144
|
+
value: '2.13.0',
|
|
145
|
+
},
|
|
146
|
+
primaryAction: {
|
|
147
|
+
label: 'More Info',
|
|
148
|
+
target: 'https://example.com/releases/v2.13.0',
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
expect(mockRemoveMatchingNotifications).toHaveBeenCalledWith(mockContext, 'new-release-', '2.13.0');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should add a multiple new releases notification when both newer patch and newer major/minor exist', async() => {
|
|
155
|
+
const releaseInfo = [{ name: '2.13.0' }, { name: '2.12.1' }, { name: '2.12.0' }];
|
|
156
|
+
const versionInfo: VersionInfo = { version: semver.coerce('2.12.0')!, isPrime: false };
|
|
157
|
+
|
|
158
|
+
await processReleaseVersion(mockContext, releaseInfo, versionInfo);
|
|
159
|
+
|
|
160
|
+
expect(mockLogger.info).toHaveBeenCalledWith('Found a newer release: 2.13.0');
|
|
161
|
+
expect(mockLogger.info).toHaveBeenCalledWith('Also found a newer patch release: 2.12.1');
|
|
162
|
+
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
|
163
|
+
expect(mockDispatch).toHaveBeenCalledWith('notifications/add', {
|
|
164
|
+
id: 'new-release-2.12.1-2.13.0',
|
|
165
|
+
level: NotificationLevel.Announcement,
|
|
166
|
+
title: 'New Rancher releases are available',
|
|
167
|
+
message: 'Message for 2.12.1 and 2.13.0',
|
|
168
|
+
preference: {
|
|
169
|
+
key: READ_NEW_RELEASE,
|
|
170
|
+
value: '2.12.1-2.13.0',
|
|
171
|
+
},
|
|
172
|
+
primaryAction: {
|
|
173
|
+
label: 'More Info for 2.12.1',
|
|
174
|
+
target: 'https://example.com/releases/v2.12.1',
|
|
175
|
+
},
|
|
176
|
+
secondaryAction: {
|
|
177
|
+
label: 'More Info for 2.13.0',
|
|
178
|
+
target: 'https://example.com/releases/v2.13.0',
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
expect(mockRemoveMatchingNotifications).toHaveBeenCalledWith(mockContext, 'new-release-', '2.12.1-2.13.0');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should not add notification if it was already read (single release)', async() => {
|
|
185
|
+
mockGetters['prefs/get'].mockReturnValue('2.12.1'); // Simulate already read
|
|
186
|
+
const releaseInfo = [{ name: '2.12.1' }];
|
|
187
|
+
const versionInfo: VersionInfo = { version: semver.coerce('2.12.0')!, isPrime: false };
|
|
188
|
+
|
|
189
|
+
await processReleaseVersion(mockContext, releaseInfo, versionInfo);
|
|
190
|
+
|
|
191
|
+
expect(mockDispatch).not.toHaveBeenCalled();
|
|
192
|
+
expect(mockLogger.debug).not.toHaveBeenCalledWith(expect.stringContaining('Adding new release notification'));
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('should not add notification if it was already read (multiple releases)', async() => {
|
|
196
|
+
mockGetters['prefs/get'].mockReturnValue('2.12.1-2.13.0'); // Simulate already read
|
|
197
|
+
const releaseInfo = [{ name: '2.13.0' }, { name: '2.12.1' }];
|
|
198
|
+
const versionInfo: VersionInfo = { version: semver.coerce('2.12.0')!, isPrime: false };
|
|
199
|
+
|
|
200
|
+
await processReleaseVersion(mockContext, releaseInfo, versionInfo);
|
|
201
|
+
|
|
202
|
+
expect(mockDispatch).not.toHaveBeenCalled();
|
|
203
|
+
expect(mockLogger.info).not.toHaveBeenCalledWith(expect.stringContaining('Adding new multiple release notification'));
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('should not add notification if removeMatchingNotifications indicates it exists', async() => {
|
|
207
|
+
mockRemoveMatchingNotifications.mockResolvedValue(true); // Simulate notification already exists
|
|
208
|
+
const releaseInfo = [{ name: '2.12.1' }];
|
|
209
|
+
const versionInfo: VersionInfo = { version: semver.coerce('2.12.0')!, isPrime: false };
|
|
210
|
+
|
|
211
|
+
await processReleaseVersion(mockContext, releaseInfo, versionInfo);
|
|
212
|
+
|
|
213
|
+
expect(mockDispatch).not.toHaveBeenCalled();
|
|
214
|
+
expect(mockLogger.debug).not.toHaveBeenCalledWith(expect.stringContaining('Adding new release notification'));
|
|
215
|
+
});
|
|
216
|
+
});
|