@rancher/shell 3.0.11 → 3.0.12-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 (219) hide show
  1. package/assets/images/providers/entraid-black.svg +4 -0
  2. package/assets/images/providers/entraid.svg +9 -0
  3. package/assets/images/vendor/entraid.svg +9 -0
  4. package/assets/styles/app.scss +0 -1
  5. package/assets/styles/base/_mixins.scss +31 -0
  6. package/assets/styles/base/_variables.scss +2 -0
  7. package/assets/styles/themes/_modern.scss +6 -5
  8. package/assets/translations/en-us.yaml +24 -21
  9. package/assets/translations/zh-hans.yaml +4 -11
  10. package/chart/__tests__/S3.test.ts +10 -3
  11. package/components/CountBox.vue +20 -0
  12. package/components/CreateDriver.vue +0 -12
  13. package/components/DetailText.vue +12 -3
  14. package/components/EmptyProductPage.vue +76 -0
  15. package/components/Resource/Detail/CopyToClipboard.vue +1 -2
  16. package/components/Resource/Detail/Metadata/KeyValueRow.vue +9 -3
  17. package/components/Resource/Detail/TitleBar/__tests__/__snapshots__/index.test.ts.snap +31 -0
  18. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +45 -1
  19. package/components/Resource/Detail/TitleBar/index.vue +1 -1
  20. package/components/Resource/Detail/ViewOptions/__tests__/__snapshots__/index.test.ts.snap +9 -0
  21. package/components/Resource/Detail/ViewOptions/__tests__/index.test.ts +62 -0
  22. package/components/Resource/Detail/ViewOptions/index.vue +2 -1
  23. package/components/ResourceList/Masthead.vue +25 -2
  24. package/components/SelectIconGrid.vue +5 -0
  25. package/components/SideNav.vue +13 -0
  26. package/components/__tests__/CountBox.test.ts +72 -0
  27. package/components/__tests__/DetailText.test.ts +113 -0
  28. package/components/__tests__/PromptModal.test.ts +2 -0
  29. package/components/fleet/FleetClusterTargets/index.vue +18 -1
  30. package/components/fleet/FleetClusters.vue +1 -0
  31. package/components/fleet/__tests__/FleetClusters.test.ts +71 -0
  32. package/components/form/InputWithSelect.vue +18 -10
  33. package/components/form/KeyValue.vue +17 -1
  34. package/components/form/LabeledSelect.vue +82 -24
  35. package/components/form/NodeScheduling.vue +17 -3
  36. package/components/form/PrivateRegistry.vue +69 -0
  37. package/components/form/Select.vue +73 -56
  38. package/components/form/ServiceNameSelect.vue +13 -11
  39. package/components/form/__tests__/KeyValue.test.ts +66 -0
  40. package/components/form/__tests__/NodeScheduling.test.ts +9 -0
  41. package/components/form/__tests__/PrivateRegistry.test.ts +133 -0
  42. package/components/form/labeled-select-utils/useLabeledSelectPagination.ts +138 -0
  43. package/components/formatter/WorkloadHealthScale.vue +3 -1
  44. package/components/nav/Group.vue +33 -9
  45. package/components/nav/Header.vue +56 -10
  46. package/components/nav/NotificationCenter/Notification.vue +4 -1
  47. package/components/nav/NotificationCenter/NotificationHeader.vue +20 -8
  48. package/components/nav/NotificationCenter/__tests__/NotificationHeader.test.ts +80 -0
  49. package/components/nav/TopLevelMenu.vue +15 -1
  50. package/components/nav/Type.vue +8 -7
  51. package/components/nav/WindowManager/index.vue +2 -1
  52. package/components/nav/WorkspaceSwitcher.vue +13 -0
  53. package/components/nav/__tests__/Group.test.ts +67 -0
  54. package/components/nav/__tests__/Header.test.ts +235 -0
  55. package/components/nav/__tests__/Type.test.ts +20 -3
  56. package/components/templates/default.vue +34 -4
  57. package/components/templates/home.vue +12 -25
  58. package/components/templates/plain.vue +13 -26
  59. package/composables/useLabeledFormElement.ts +10 -2
  60. package/composables/useLabeledSelect.ts +60 -0
  61. package/composables/useUserRetentionValidation.ts +1 -49
  62. package/config/cookies.js +0 -1
  63. package/config/labels-annotations.js +1 -0
  64. package/config/pagination-table-headers.js +8 -1
  65. package/config/product/apps.js +2 -1
  66. package/config/product/auth.js +1 -0
  67. package/config/product/backup.js +1 -0
  68. package/config/product/compliance.js +1 -1
  69. package/config/product/explorer.js +25 -6
  70. package/config/product/fleet.js +1 -0
  71. package/config/product/gatekeeper.js +1 -0
  72. package/config/product/istio.js +1 -0
  73. package/config/product/logging.js +1 -0
  74. package/config/product/longhorn.js +2 -1
  75. package/config/product/manager.js +1 -0
  76. package/config/product/monitoring.js +1 -0
  77. package/config/product/navlinks.js +1 -0
  78. package/config/product/neuvector.js +2 -1
  79. package/config/product/settings.js +1 -0
  80. package/config/product/uiplugins.js +1 -0
  81. package/config/query-params.js +1 -0
  82. package/config/router/routes.js +0 -8
  83. package/core/__tests__/plugin-products-helpers.test.ts +454 -0
  84. package/core/__tests__/plugin-products.test.ts +3810 -0
  85. package/core/extension-manager-impl.js +30 -1
  86. package/core/plugin-products-base.ts +392 -0
  87. package/core/plugin-products-extending.ts +44 -0
  88. package/core/plugin-products-helpers.ts +263 -0
  89. package/core/plugin-products-top-level.ts +66 -0
  90. package/core/plugin-products-type-guards.ts +33 -0
  91. package/core/plugin-products.ts +50 -0
  92. package/core/plugin-types.ts +237 -0
  93. package/core/plugin.ts +45 -10
  94. package/core/productDebugger.js +48 -0
  95. package/core/types.ts +97 -11
  96. package/detail/__tests__/__snapshots__/fleet.cattle.io.bundle.test.ts.snap +52 -0
  97. package/detail/__tests__/fleet.cattle.io.bundle.test.ts +171 -0
  98. package/detail/__tests__/management.cattle.io.fleetworkspace.test.ts +128 -0
  99. package/detail/fleet.cattle.io.bundle.vue +21 -34
  100. package/detail/management.cattle.io.fleetworkspace.vue +49 -0
  101. package/dialog/ExtensionCatalogInstallDialog.vue +1 -1
  102. package/dialog/InstallExtensionDialog.vue +6 -27
  103. package/dialog/UninstallExistingExtensionDialog.vue +141 -0
  104. package/dialog/UninstallExtensionDialog.vue +4 -26
  105. package/dialog/__tests__/UninstallExistingExtensionDialog.test.ts +114 -0
  106. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -0
  107. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +9 -0
  108. package/edit/__tests__/kontainerDriver.test.ts +0 -13
  109. package/edit/__tests__/nodeDriver.test.ts +5 -11
  110. package/edit/__tests__/resources.cattle.io.restore.test.ts +9 -0
  111. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
  112. package/edit/auth/__tests__/oidc.test.ts +54 -0
  113. package/edit/auth/azuread.vue +1 -1
  114. package/edit/auth/oidc.vue +8 -0
  115. package/edit/kontainerDriver.vue +1 -2
  116. package/edit/nodeDriver.vue +0 -2
  117. package/edit/provisioning.cattle.io.cluster/AgentEnv.vue +1 -0
  118. package/edit/provisioning.cattle.io.cluster/__tests__/AgentEnv.test.ts +25 -0
  119. package/edit/provisioning.cattle.io.cluster/__tests__/Ingress.test.ts +176 -0
  120. package/edit/provisioning.cattle.io.cluster/index.vue +70 -99
  121. package/edit/provisioning.cattle.io.cluster/rke2.vue +4 -1
  122. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +6 -0
  123. package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +7 -2
  124. package/initialize/App.vue +29 -2
  125. package/initialize/install-plugins.js +0 -2
  126. package/list/__tests__/management.cattle.io.feature.test.ts +105 -0
  127. package/list/catalog.cattle.io.app.vue +25 -5
  128. package/list/management.cattle.io.feature.vue +1 -1
  129. package/list/management.cattle.io.fleetworkspace.vue +8 -0
  130. package/list/provisioning.cattle.io.cluster.vue +0 -1
  131. package/list/workload.vue +11 -4
  132. package/machine-config/amazonec2.vue +1 -0
  133. package/mixins/chart.js +40 -9
  134. package/mixins/resource-fetch.js +12 -3
  135. package/models/__tests__/catalog.cattle.io.app.test.ts +15 -1
  136. package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +84 -0
  137. package/models/__tests__/chart.test.ts +99 -6
  138. package/models/__tests__/management.cattle.io.feature.test.ts +131 -0
  139. package/models/__tests__/monitoring.coreos.com.alertmanagerconfig.test.ts +98 -0
  140. package/models/catalog.cattle.io.app.js +21 -17
  141. package/models/catalog.cattle.io.clusterrepo.js +39 -11
  142. package/models/chart.js +33 -19
  143. package/models/fleet-application.js +1 -1
  144. package/models/fleet.cattle.io.bundle.js +1 -1
  145. package/models/kontainerdriver.js +11 -0
  146. package/models/management.cattle.io.authconfig.js +5 -1
  147. package/models/management.cattle.io.cluster.js +0 -53
  148. package/models/management.cattle.io.feature.js +3 -3
  149. package/models/management.cattle.io.kontainerdriver.js +1 -26
  150. package/models/monitoring.coreos.com.alertmanagerconfig.js +31 -17
  151. package/models/nodedriver.js +7 -0
  152. package/models/pod.js +18 -0
  153. package/models/workload.js +20 -2
  154. package/package.json +13 -13
  155. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +0 -1
  156. package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +189 -0
  157. package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +55 -0
  158. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +53 -0
  159. package/pages/c/_cluster/apps/charts/chart.vue +217 -33
  160. package/pages/c/_cluster/apps/charts/index.vue +2 -2
  161. package/pages/c/_cluster/apps/charts/install.vue +8 -3
  162. package/pages/c/_cluster/auth/user.retention/index.vue +55 -22
  163. package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +5 -7
  164. package/pages/c/_cluster/settings/brand.vue +4 -4
  165. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +39 -2
  166. package/pages/c/_cluster/uiplugins/__tests__/PluginInfoPanel.test.ts +61 -0
  167. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +246 -23
  168. package/pages/c/_cluster/uiplugins/index.vue +166 -62
  169. package/plugins/dashboard-store/__tests__/resource-class.test.ts +1 -0
  170. package/plugins/dashboard-store/actions.js +3 -2
  171. package/plugins/dashboard-store/resource-class.js +62 -6
  172. package/plugins/plugin.js +16 -0
  173. package/plugins/steve/steve-pagination-utils.ts +7 -0
  174. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +205 -1
  175. package/rancher-components/Form/LabeledInput/LabeledInput.vue +82 -4
  176. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +1 -1
  177. package/scripts/test-plugins-build.sh +5 -2
  178. package/scripts/typegen.sh +13 -1
  179. package/server/server-middleware.js +2 -2
  180. package/static/humans.txt +1 -0
  181. package/static/robots.txt +34 -0
  182. package/static/welcome-cow.svg +18 -0
  183. package/store/__tests__/catalog.test.ts +161 -11
  184. package/store/__tests__/type-map.test.ts +84 -24
  185. package/store/auth.js +0 -3
  186. package/store/catalog.js +60 -8
  187. package/store/type-map.js +42 -3
  188. package/tsconfig.paths.json +1 -0
  189. package/types/resources/pod.ts +18 -0
  190. package/types/shell/index.d.ts +8539 -2938
  191. package/types/store/dashboard-store.types.ts +5 -0
  192. package/types/store/pagination.types.ts +6 -0
  193. package/utils/__tests__/git.test.ts +270 -0
  194. package/utils/__tests__/inactivity.test.ts +316 -0
  195. package/utils/__tests__/object.test.ts +77 -0
  196. package/utils/__tests__/time.test.ts +14 -1
  197. package/utils/__tests__/url.test.ts +246 -0
  198. package/utils/axios.js +1 -4
  199. package/utils/dynamic-importer.js +3 -2
  200. package/utils/object.js +33 -2
  201. package/utils/pagination-utils.ts +1 -1
  202. package/utils/time.ts +5 -0
  203. package/utils/uiplugins.ts +12 -16
  204. package/utils/validators/__tests__/private-registry.test.ts +76 -0
  205. package/utils/validators/private-registry.ts +28 -0
  206. package/vue.config.js +0 -9
  207. package/assets/images/providers/azuread-black.svg +0 -22
  208. package/assets/images/providers/azuread.svg +0 -25
  209. package/assets/images/vendor/azuread.svg +0 -18
  210. package/assets/styles/fonts/_dots.scss +0 -18
  211. package/components/EmberPage.vue +0 -622
  212. package/components/EmberPageView.vue +0 -39
  213. package/components/form/labeled-select-utils/labeled-select-pagination.ts +0 -116
  214. package/mixins/labeled-form-element.ts +0 -225
  215. package/pages/c/_cluster/explorer/tools/pages/_page.vue +0 -28
  216. package/pages/c/_cluster/manager/pages/_page.vue +0 -22
  217. package/pages/c/_cluster/mcapps/pages/_page.vue +0 -22
  218. package/plugins/ember-cookie.js +0 -17
  219. package/utils/ember-page.js +0 -30
@@ -1,6 +1,7 @@
1
1
  import { shallowMount, VueWrapper } from '@vue/test-utils';
2
2
  import UiPluginsPage from '@shell/pages/c/_cluster/uiplugins/index.vue';
3
3
  import { UI_PLUGIN_NAMESPACE } from '@shell/config/uiplugins';
4
+ import { CATALOG as CATALOG_ANNOTATIONS } from '@shell/config/labels-annotations';
4
5
 
5
6
  const t = (key: string, args: Object) => {
6
7
  if (args) {
@@ -207,6 +208,68 @@ describe('page: UI plugins/Extensions', () => {
207
208
 
208
209
  expect(items).toHaveLength(0);
209
210
  });
211
+
212
+ it('should display repository name for non-installed plugins with chart info', () => {
213
+ const plugin = {
214
+ installed: false,
215
+ chart: { repoNameDisplay: 'rancher-charts' },
216
+ certified: true
217
+ };
218
+ const items = wrapper.vm.getFooterItems(plugin);
219
+
220
+ expect(items).toHaveLength(1);
221
+ expect(items[0].icon).toBe('repository-alt');
222
+ expect(items[0].labels).toStrictEqual(['rancher-charts']);
223
+ });
224
+
225
+ it('should display repository name for installed plugins when chart info is present (original repo exists)', () => {
226
+ const plugin = {
227
+ installed: true,
228
+ chart: { repoNameDisplay: 'rancher-charts' },
229
+ certified: true
230
+ };
231
+ const items = wrapper.vm.getFooterItems(plugin);
232
+
233
+ expect(items).toHaveLength(1);
234
+ expect(items[0].icon).toBe('repository-alt');
235
+ expect(items[0].labels).toStrictEqual(['rancher-charts']);
236
+ });
237
+
238
+ it('should display original repository name for installed plugins when original repo was removed', () => {
239
+ const plugin = {
240
+ installed: true,
241
+ originalRepoNameDisplay: 'removed-repo',
242
+ certified: true
243
+ };
244
+ const items = wrapper.vm.getFooterItems(plugin);
245
+
246
+ expect(items).toHaveLength(1);
247
+ expect(items[0].icon).toBe('repository-alt');
248
+ expect(items[0].labels).toStrictEqual(['removed-repo']);
249
+ });
250
+
251
+ it('should prefer chart.repoNameDisplay over originalRepoNameDisplay when both are present', () => {
252
+ const plugin = {
253
+ installed: true,
254
+ chart: { repoNameDisplay: 'current-repo' },
255
+ originalRepoNameDisplay: 'original-repo',
256
+ certified: true
257
+ };
258
+ const items = wrapper.vm.getFooterItems(plugin);
259
+
260
+ expect(items).toHaveLength(1);
261
+ expect(items[0].labels).toStrictEqual(['current-repo']);
262
+ });
263
+
264
+ it('should NOT display repository name when neither chart nor originalRepoNameDisplay are present', () => {
265
+ const plugin = {
266
+ installed: true,
267
+ certified: true
268
+ };
269
+ const items = wrapper.vm.getFooterItems(plugin);
270
+
271
+ expect(items).toHaveLength(0);
272
+ });
210
273
  });
211
274
 
212
275
  describe('getStatuses', () => {
@@ -230,10 +293,11 @@ describe('page: UI plugins/Extensions', () => {
230
293
  });
231
294
 
232
295
  it('should return "deprecated" status for deprecated plugins', () => {
233
- const plugin = { chart: { deprecated: true } };
296
+ const plugin = { chart: { versions: [{ annotations: { [CATALOG_ANNOTATIONS.DEPRECATED]: 'true' } }] } };
234
297
  const statuses = wrapper.vm.getStatuses(plugin);
235
298
 
236
- expect(statuses[0].tooltip.key).toBe('generic.deprecated');
299
+ expect(statuses[0].tooltip.text).toBe('generic.deprecated');
300
+ expect(statuses[0].color).toBe('error');
237
301
  });
238
302
 
239
303
  it('should return error status for installedError', () => {
@@ -241,15 +305,17 @@ describe('page: UI plugins/Extensions', () => {
241
305
  const statuses = wrapper.vm.getStatuses(plugin);
242
306
 
243
307
  expect(statuses[0].icon).toBe('icon-alert-alt');
244
- expect(statuses[0].tooltip.text).toBe('generic.error: An error occurred');
308
+ expect(statuses[0].color).toBe('error');
309
+ expect(statuses[0].tooltip.text).toBe('An error occurred');
245
310
  });
246
311
 
247
- it('should return error status for incompatibilityMessage', () => {
312
+ it('should return warning status for incompatibilityMessage', () => {
248
313
  const plugin = { incompatibilityMessage: 'Incompatible version' };
249
314
  const statuses = wrapper.vm.getStatuses(plugin);
250
315
 
251
316
  expect(statuses[0].icon).toBe('icon-alert-alt');
252
- expect(statuses[0].tooltip.text).toBe('generic.error: Incompatible version');
317
+ expect(statuses[0].color).toBe('error');
318
+ expect(statuses[0].tooltip.text).toBe('Incompatible version');
253
319
  });
254
320
 
255
321
  it('should return error status for helmError', () => {
@@ -257,15 +323,172 @@ describe('page: UI plugins/Extensions', () => {
257
323
  const statuses = wrapper.vm.getStatuses(plugin);
258
324
 
259
325
  expect(statuses[0].icon).toBe('icon-alert-alt');
260
- expect(statuses[0].tooltip.text).toBe('generic.error: plugins.helmError');
326
+ expect(statuses[0].color).toBe('error');
327
+ expect(statuses[0].tooltip.text).toBe('plugins.helmError');
261
328
  });
262
329
 
263
- it('should combine deprecated and other errors in tooltip', () => {
264
- const plugin = { chart: { deprecated: true }, helmError: true };
330
+ it('should combine deprecated and error messages in a single tooltip', () => {
331
+ const plugin = { chart: { versions: [{ annotations: { [CATALOG_ANNOTATIONS.DEPRECATED]: 'true' } }] }, helmError: true };
265
332
  const statuses = wrapper.vm.getStatuses(plugin);
266
- const warningStatus = statuses.find((status: any) => status.icon === 'icon-alert-alt');
333
+ const errorStatus = statuses.find((status: any) => status.color === 'error');
334
+
335
+ expect(errorStatus.tooltip.text).toBe('generic.deprecated<br/>plugins.helmError');
336
+ });
337
+ });
338
+
339
+ describe('showInstallDialog', () => {
340
+ let dispatchMock: jest.Mock;
341
+
342
+ const createWrapper = (availablePlugins: any[] = []) => {
343
+ dispatchMock = jest.fn().mockResolvedValue(true);
344
+
345
+ const store = {
346
+ getters: {
347
+ 'prefs/get': jest.fn(),
348
+ 'catalog/rawCharts': {},
349
+ 'uiplugins/plugins': [],
350
+ 'uiplugins/errors': {},
351
+ 'management/all': () => [],
352
+ },
353
+ dispatch: dispatchMock,
354
+ };
355
+
356
+ const wrapper = shallowMount(UiPluginsPage, {
357
+ global: {
358
+ mocks: {
359
+ $store: store,
360
+ t,
361
+ },
362
+ stubs: { ActionMenu: { template: '<div />' } }
363
+ }
364
+ });
365
+
366
+ // Mock the available computed property
367
+ Object.defineProperty(wrapper.vm, 'available', { get: () => availablePlugins });
368
+
369
+ return wrapper;
370
+ };
371
+
372
+ it('should open UninstallExistingExtensionDialog when installing a plugin that is already installed from a different source', () => {
373
+ const installedPlugin = {
374
+ id: 'other-repo/my-plugin',
375
+ name: 'my-plugin',
376
+ installed: true
377
+ };
378
+ const pluginToInstall = {
379
+ id: 'new-repo/my-plugin',
380
+ name: 'my-plugin'
381
+ };
382
+
383
+ const wrapper = createWrapper([installedPlugin, pluginToInstall]);
384
+
385
+ wrapper.vm.showInstallDialog(pluginToInstall, 'install', {});
386
+
387
+ expect(dispatchMock).toHaveBeenCalledWith(
388
+ 'management/promptModal',
389
+ expect.objectContaining({ component: 'UninstallExistingExtensionDialog' })
390
+ );
391
+ });
392
+
393
+ it('should NOT open InstallExtensionDialog when plugin is already installed from a different source', () => {
394
+ const installedPlugin = {
395
+ id: 'other-repo/my-plugin',
396
+ name: 'my-plugin',
397
+ installed: true
398
+ };
399
+ const pluginToInstall = {
400
+ id: 'new-repo/my-plugin',
401
+ name: 'my-plugin'
402
+ };
403
+
404
+ const wrapper = createWrapper([installedPlugin, pluginToInstall]);
405
+
406
+ wrapper.vm.showInstallDialog(pluginToInstall, 'install', {});
407
+
408
+ expect(dispatchMock).not.toHaveBeenCalledWith(
409
+ 'management/promptModal',
410
+ expect.objectContaining({ component: 'InstallExtensionDialog' })
411
+ );
412
+ });
413
+
414
+ it('should pass the installedPlugin to UninstallExistingExtensionDialog', () => {
415
+ const installedPlugin = {
416
+ id: 'other-repo/my-plugin',
417
+ name: 'my-plugin',
418
+ installed: true
419
+ };
420
+ const pluginToInstall = {
421
+ id: 'new-repo/my-plugin',
422
+ name: 'my-plugin'
423
+ };
424
+
425
+ const wrapper = createWrapper([installedPlugin, pluginToInstall]);
426
+
427
+ wrapper.vm.showInstallDialog(pluginToInstall, 'install', {});
428
+
429
+ expect(dispatchMock).toHaveBeenCalledWith(
430
+ 'management/promptModal',
431
+ expect.objectContaining({
432
+ component: 'UninstallExistingExtensionDialog',
433
+ componentProps: expect.objectContaining({ installedPlugin })
434
+ })
435
+ );
436
+ });
437
+
438
+ it('should open InstallExtensionDialog when installing a plugin that is NOT installed from another source', () => {
439
+ const pluginToInstall = {
440
+ id: 'repo/my-plugin',
441
+ name: 'my-plugin'
442
+ };
443
+
444
+ const wrapper = createWrapper([pluginToInstall]);
445
+
446
+ wrapper.vm.showInstallDialog(pluginToInstall, 'install', {});
447
+
448
+ expect(dispatchMock).toHaveBeenCalledWith(
449
+ 'management/promptModal',
450
+ expect.objectContaining({ component: 'InstallExtensionDialog' })
451
+ );
452
+ });
453
+
454
+ it('should open InstallExtensionDialog when the same plugin id is being re-installed (same source)', () => {
455
+ const installedPlugin = {
456
+ id: 'repo/my-plugin',
457
+ name: 'my-plugin',
458
+ installed: true
459
+ };
460
+
461
+ const wrapper = createWrapper([installedPlugin]);
462
+
463
+ wrapper.vm.showInstallDialog(installedPlugin, 'install', {});
464
+
465
+ expect(dispatchMock).toHaveBeenCalledWith(
466
+ 'management/promptModal',
467
+ expect.objectContaining({ component: 'InstallExtensionDialog' })
468
+ );
469
+ });
470
+
471
+ it('should open InstallExtensionDialog for upgrade action even if same name exists in another repo', () => {
472
+ const installedPlugin = {
473
+ id: 'repo-a/my-plugin',
474
+ name: 'my-plugin',
475
+ installed: true
476
+ };
477
+ const otherPlugin = {
478
+ id: 'repo-b/my-plugin',
479
+ name: 'my-plugin',
480
+ installed: false
481
+ };
482
+
483
+ const wrapper = createWrapper([installedPlugin, otherPlugin]);
484
+
485
+ // Upgrade action should always go to InstallExtensionDialog
486
+ wrapper.vm.showInstallDialog(installedPlugin, 'upgrade', {});
267
487
 
268
- expect(warningStatus.tooltip.text).toBe('generic.deprecated. generic.error: plugins.helmError');
488
+ expect(dispatchMock).toHaveBeenCalledWith(
489
+ 'management/promptModal',
490
+ expect.objectContaining({ component: 'InstallExtensionDialog' })
491
+ );
269
492
  });
270
493
  });
271
494
 
@@ -296,10 +519,10 @@ describe('page: UI plugins/Extensions', () => {
296
519
  computed: {
297
520
  // Override the computed property for this test suite
298
521
  available: () => [
299
- { name: 'plugin1' },
300
- { name: 'plugin2' },
301
- { name: 'plugin3' },
302
- { name: 'plugin4' },
522
+ { name: 'plugin1', id: 'repo/plugin1' },
523
+ { name: 'plugin2', id: 'repo/plugin2' },
524
+ { name: 'plugin3', id: 'repo/plugin3' },
525
+ { name: 'plugin4', id: 'repo/plugin4' },
303
526
  ],
304
527
  hasMenuActions: () => true,
305
528
  menuActions: () => []
@@ -311,9 +534,9 @@ describe('page: UI plugins/Extensions', () => {
311
534
 
312
535
  // Set the 'installing' status on the component instance
313
536
  wrapper.vm.installing = {
314
- plugin1: 'install',
315
- plugin2: 'downgrade',
316
- plugin3: 'uninstall',
537
+ 'repo/plugin1': 'install',
538
+ 'repo/plugin2': 'downgrade',
539
+ 'repo/plugin3': 'uninstall',
317
540
  };
318
541
 
319
542
  // Reset errors
@@ -330,7 +553,7 @@ describe('page: UI plugins/Extensions', () => {
330
553
  wrapper.vm.helmOps = helmOps;
331
554
  await wrapper.vm.$nextTick();
332
555
 
333
- expect(updatePluginInstallStatusMock).toHaveBeenCalledWith('plugin1', 'install');
556
+ expect(updatePluginInstallStatusMock).toHaveBeenCalledWith('repo/plugin1', 'install');
334
557
  });
335
558
 
336
559
  it('should not update status for an upgrade op when a downgrade was initiated', async() => {
@@ -344,7 +567,7 @@ describe('page: UI plugins/Extensions', () => {
344
567
  await wrapper.vm.$nextTick();
345
568
 
346
569
  // It should not be called with 'upgrade' for plugin2
347
- expect(updatePluginInstallStatusMock).not.toHaveBeenCalledWith('plugin2', 'upgrade');
570
+ expect(updatePluginInstallStatusMock).not.toHaveBeenCalledWith('repo/plugin2', 'upgrade');
348
571
  });
349
572
 
350
573
  it('should clear status for a completed uninstall operation', async() => {
@@ -357,7 +580,7 @@ describe('page: UI plugins/Extensions', () => {
357
580
  wrapper.vm.helmOps = helmOps;
358
581
  await wrapper.vm.$nextTick();
359
582
 
360
- expect(updatePluginInstallStatusMock).toHaveBeenCalledWith('plugin3', false);
583
+ expect(updatePluginInstallStatusMock).toHaveBeenCalledWith('repo/plugin3', false);
361
584
  });
362
585
 
363
586
  it('should set error and clear status for a failed operation', async() => {
@@ -370,8 +593,8 @@ describe('page: UI plugins/Extensions', () => {
370
593
  wrapper.vm.helmOps = helmOps;
371
594
  await wrapper.vm.$nextTick();
372
595
 
373
- expect(wrapper.vm.errors.plugin1).toBe(true);
374
- expect(updatePluginInstallStatusMock).toHaveBeenCalledWith('plugin1', false);
596
+ expect(wrapper.vm.errors['repo/plugin1']).toBe(true);
597
+ expect(updatePluginInstallStatusMock).toHaveBeenCalledWith('repo/plugin1', false);
375
598
  });
376
599
 
377
600
  it('should clear status for plugins with no active operation', async() => {
@@ -385,7 +608,7 @@ describe('page: UI plugins/Extensions', () => {
385
608
  await wrapper.vm.$nextTick();
386
609
 
387
610
  // plugin4 has no op, so its status should be cleared
388
- expect(updatePluginInstallStatusMock).toHaveBeenCalledWith('plugin4', false);
611
+ expect(updatePluginInstallStatusMock).toHaveBeenCalledWith('repo/plugin4', false);
389
612
  });
390
613
  });
391
614
  });