@rancher/shell 3.0.11 → 3.0.12-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/assets/styles/base/_mixins.scss +31 -0
  2. package/assets/styles/base/_variables.scss +2 -0
  3. package/assets/styles/themes/_modern.scss +6 -5
  4. package/assets/translations/en-us.yaml +5 -4
  5. package/assets/translations/zh-hans.yaml +0 -3
  6. package/components/EmptyProductPage.vue +76 -0
  7. package/components/Resource/Detail/CopyToClipboard.vue +1 -2
  8. package/components/Resource/Detail/Metadata/KeyValueRow.vue +9 -3
  9. package/components/Resource/Detail/TitleBar/__tests__/__snapshots__/index.test.ts.snap +31 -0
  10. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +45 -1
  11. package/components/Resource/Detail/TitleBar/index.vue +1 -1
  12. package/components/Resource/Detail/ViewOptions/__tests__/__snapshots__/index.test.ts.snap +9 -0
  13. package/components/Resource/Detail/ViewOptions/__tests__/index.test.ts +62 -0
  14. package/components/Resource/Detail/ViewOptions/index.vue +2 -1
  15. package/components/ResourceList/Masthead.vue +25 -2
  16. package/components/SideNav.vue +13 -0
  17. package/components/__tests__/PromptModal.test.ts +2 -0
  18. package/components/fleet/FleetClusters.vue +1 -0
  19. package/components/fleet/__tests__/FleetClusters.test.ts +71 -0
  20. package/components/form/NodeScheduling.vue +17 -3
  21. package/components/form/PrivateRegistry.vue +69 -0
  22. package/components/form/__tests__/PrivateRegistry.test.ts +133 -0
  23. package/components/formatter/WorkloadHealthScale.vue +3 -1
  24. package/components/nav/Group.vue +26 -3
  25. package/components/nav/Header.vue +32 -7
  26. package/components/nav/TopLevelMenu.vue +15 -1
  27. package/config/pagination-table-headers.js +8 -1
  28. package/config/product/apps.js +2 -1
  29. package/config/product/auth.js +1 -0
  30. package/config/product/backup.js +1 -0
  31. package/config/product/compliance.js +1 -1
  32. package/config/product/explorer.js +25 -6
  33. package/config/product/fleet.js +1 -0
  34. package/config/product/gatekeeper.js +1 -0
  35. package/config/product/istio.js +1 -0
  36. package/config/product/logging.js +1 -0
  37. package/config/product/longhorn.js +2 -1
  38. package/config/product/manager.js +1 -0
  39. package/config/product/monitoring.js +1 -0
  40. package/config/product/navlinks.js +1 -0
  41. package/config/product/neuvector.js +2 -1
  42. package/config/product/settings.js +1 -0
  43. package/config/product/uiplugins.js +1 -0
  44. package/core/__tests__/plugin-products-helpers.test.ts +454 -0
  45. package/core/__tests__/plugin-products.test.ts +3219 -0
  46. package/core/extension-manager-impl.js +30 -1
  47. package/core/plugin-products-base.ts +375 -0
  48. package/core/plugin-products-extending.ts +44 -0
  49. package/core/plugin-products-helpers.ts +262 -0
  50. package/core/plugin-products-top-level.ts +66 -0
  51. package/core/plugin-products-type-guards.ts +33 -0
  52. package/core/plugin-products.ts +50 -0
  53. package/core/plugin-types.ts +222 -0
  54. package/core/plugin.ts +45 -10
  55. package/core/productDebugger.js +48 -0
  56. package/core/types.ts +95 -11
  57. package/detail/__tests__/__snapshots__/fleet.cattle.io.bundle.test.ts.snap +52 -0
  58. package/detail/__tests__/fleet.cattle.io.bundle.test.ts +171 -0
  59. package/detail/fleet.cattle.io.bundle.vue +21 -34
  60. package/dialog/ExtensionCatalogInstallDialog.vue +1 -1
  61. package/dialog/InstallExtensionDialog.vue +6 -27
  62. package/dialog/UninstallExistingExtensionDialog.vue +141 -0
  63. package/dialog/UninstallExtensionDialog.vue +4 -26
  64. package/dialog/__tests__/UninstallExistingExtensionDialog.test.ts +114 -0
  65. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -0
  66. package/edit/provisioning.cattle.io.cluster/__tests__/Ingress.test.ts +176 -0
  67. package/edit/provisioning.cattle.io.cluster/rke2.vue +4 -1
  68. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +6 -0
  69. package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +7 -2
  70. package/list/provisioning.cattle.io.cluster.vue +0 -1
  71. package/list/workload.vue +11 -4
  72. package/mixins/resource-fetch.js +12 -3
  73. package/models/pod.js +18 -0
  74. package/models/workload.js +20 -2
  75. package/package.json +1 -2
  76. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +0 -1
  77. package/pages/c/_cluster/settings/brand.vue +4 -4
  78. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +231 -13
  79. package/pages/c/_cluster/uiplugins/index.vue +143 -37
  80. package/plugins/dashboard-store/__tests__/resource-class.test.ts +1 -0
  81. package/plugins/dashboard-store/actions.js +3 -2
  82. package/plugins/dashboard-store/resource-class.js +62 -6
  83. package/plugins/plugin.js +16 -0
  84. package/plugins/steve/steve-pagination-utils.ts +7 -0
  85. package/scripts/typegen.sh +13 -1
  86. package/store/__tests__/type-map.test.ts +84 -24
  87. package/store/type-map.js +42 -3
  88. package/tsconfig.paths.json +1 -0
  89. package/types/resources/pod.ts +18 -0
  90. package/types/shell/index.d.ts +8506 -2909
  91. package/types/store/dashboard-store.types.ts +5 -0
  92. package/types/store/pagination.types.ts +6 -0
  93. package/utils/axios.js +1 -4
  94. package/utils/dynamic-importer.js +3 -2
  95. package/utils/pagination-utils.ts +1 -1
  96. package/utils/uiplugins.ts +12 -16
  97. package/utils/validators/__tests__/private-registry.test.ts +76 -0
  98. package/utils/validators/private-registry.ts +28 -0
@@ -87,6 +87,14 @@ export default {
87
87
  type: Function,
88
88
  default: null,
89
89
  },
90
+
91
+ /**
92
+ * When making a supporting HTTP request include associated resource data
93
+ */
94
+ includeAssociatedData: {
95
+ type: Boolean,
96
+ default: false,
97
+ },
90
98
  },
91
99
 
92
100
  computed: {
@@ -185,9 +193,10 @@ export default {
185
193
  return;
186
194
  }
187
195
  const opt = {
188
- hasManualRefresh: this.hasManualRefresh,
189
- pagination: { ...this.pagination },
190
- force: this.paginating !== null // Fix for manual refresh (before ripped out).
196
+ hasManualRefresh: this.hasManualRefresh,
197
+ pagination: { ...this.pagination },
198
+ force: this.paginating !== null, // Fix for manual refresh (before ripped out).
199
+ includeAssociatedData: this.includeAssociatedData,
191
200
  };
192
201
 
193
202
  if (this.apiFilter) {
package/models/pod.js CHANGED
@@ -4,6 +4,7 @@ import { NODE, WORKLOAD_TYPES } from '@shell/config/types';
4
4
  import { escapeHtml, shortenedImage } from '@shell/utils/string';
5
5
  import WorkloadService from '@shell/models/workload.service';
6
6
  import { deleteProperty } from '@shell/utils/object';
7
+ import { POD_RESTARTS_REG_EX } from '@shell/types/resources/pod';
7
8
 
8
9
  export const WORKLOAD_PRIORITY = {
9
10
  [WORKLOAD_TYPES.DEPLOYMENT]: 1,
@@ -222,6 +223,9 @@ export default class Pod extends WorkloadService {
222
223
  return this.$rootGetters['i18n/t']('resourceTable.groupLabel.node', { name: escapeHtml(name) });
223
224
  }
224
225
 
226
+ /**
227
+ * How many times has the first container restarted
228
+ */
225
229
  get restartCount() {
226
230
  if (this.status.containerStatuses) {
227
231
  return this.status?.containerStatuses[0].restartCount || 0;
@@ -230,6 +234,20 @@ export default class Pod extends WorkloadService {
230
234
  return 0;
231
235
  }
232
236
 
237
+ /**
238
+ * How many times does native kube report this pod has restarted
239
+ */
240
+ get restartsCount() {
241
+ return this.metadata?.fields?.[3]?.match(POD_RESTARTS_REG_EX)?.[1] || '';
242
+ }
243
+
244
+ /**
245
+ * When does native kube think the last pod restart happen?
246
+ */
247
+ get restartsLaster() {
248
+ return this.metadata?.fields?.[3]?.match(POD_RESTARTS_REG_EX)?.[2] || '';
249
+ }
250
+
233
251
  processSaveResponse(res) {
234
252
  if (res._headers && res._headers.warning) {
235
253
  const warnings = res._headers.warning.split('299') || [];
@@ -9,6 +9,7 @@ import WorkloadService from '@shell/models/workload.service';
9
9
  import { matching } from '@shell/utils/selector-typed';
10
10
  import { defineAsyncComponent, markRaw } from 'vue';
11
11
  import { useResourceCardRow } from '@shell/components/Resource/Detail/Card/StateCard/composables';
12
+ import { colorForState as colorForStateFn, stateDisplay as stateDisplayFn } from '@shell/plugins/dashboard-store/resource-class';
12
13
 
13
14
  export const defaultContainer = {
14
15
  imagePullPolicy: 'Always',
@@ -622,12 +623,29 @@ export default class Workload extends WorkloadService {
622
623
 
623
624
  calcPodGauges(pods) {
624
625
  const out = { };
626
+ let refPods = pods;
625
627
 
626
- if (!pods) {
628
+ if (this.metadata.associatedData) {
629
+ refPods = [];
630
+ this.metadata.associatedData.forEach((w) => {
631
+ if (w.gvk.kind.toLowerCase() !== POD) {
632
+ return;
633
+ }
634
+
635
+ return w.data.forEach((p) => {
636
+ refPods.push({
637
+ stateColor: colorForStateFn(p.state.name, p.state.error === 'true', p.state.transitioning === 'true'),
638
+ stateDisplay: stateDisplayFn(p.state.name),
639
+ });
640
+ });
641
+ });
642
+ }
643
+
644
+ if (!refPods) {
627
645
  return out;
628
646
  }
629
647
 
630
- pods.map((pod) => {
648
+ refPods.map((pod) => {
631
649
  const { stateColor, stateDisplay } = pod;
632
650
 
633
651
  if (out[stateDisplay]) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rancher/shell",
3
- "version": "3.0.11",
3
+ "version": "3.0.12-rc.1",
4
4
  "description": "Rancher Dashboard Shell",
5
5
  "repository": "https://github.com/rancher/dashboard",
6
6
  "license": "Apache-2.0",
@@ -76,7 +76,6 @@
76
76
  "d3-selection": "3.0.0",
77
77
  "d3": "7.3.0",
78
78
  "dayjs": "1.11.18",
79
- "defu": "6.1.4",
80
79
  "diff2html": "3.4.24",
81
80
  "dompurify": "3.2.5",
82
81
  "element-matches": "^0.1.2",
@@ -183,7 +183,6 @@ function getTooltip(key: string, fallback?: string): string | undefined {
183
183
  }
184
184
 
185
185
  &-button {
186
- text-transform: capitalize;
187
186
  min-width: 0;
188
187
  max-width: 100%;
189
188
 
@@ -440,14 +440,14 @@ export default {
440
440
  />
441
441
  </div>
442
442
  <SimpleBox
443
- v-if="uiLoginBackgroundLight || uiLoginBackgroundDark"
443
+ v-if="uiLoginBackgroundLight"
444
444
  class="theme-light mb-10"
445
445
  >
446
446
  <label class="text-muted">{{ t('branding.loginBackground.lightPreview') }}</label>
447
447
  <img
448
448
  class="img-preview"
449
449
  data-testid="branding-login-background-light-preview"
450
- :src="uiLoginBackgroundLight ? uiLoginBackgroundLight : uiLoginBackgroundDark"
450
+ :src="uiLoginBackgroundLight"
451
451
  >
452
452
  </SimpleBox>
453
453
  </div>
@@ -465,14 +465,14 @@ export default {
465
465
  />
466
466
  </div>
467
467
  <SimpleBox
468
- v-if="uiLoginBackgroundDark || uiLoginBackgroundLight"
468
+ v-if="uiLoginBackgroundDark"
469
469
  class="theme-dark mb-10"
470
470
  >
471
471
  <label class="text-muted">{{ t('branding.loginBackground.darkPreview') }}</label>
472
472
  <img
473
473
  class="img-preview"
474
474
  data-testid="branding-login-background-dark-preview"
475
- :src="uiLoginBackgroundDark ? uiLoginBackgroundDark : uiLoginBackgroundLight"
475
+ :src="uiLoginBackgroundDark"
476
476
  >
477
477
  </SimpleBox>
478
478
  </div>
@@ -207,6 +207,68 @@ describe('page: UI plugins/Extensions', () => {
207
207
 
208
208
  expect(items).toHaveLength(0);
209
209
  });
210
+
211
+ it('should display repository name for non-installed plugins with chart info', () => {
212
+ const plugin = {
213
+ installed: false,
214
+ chart: { repoNameDisplay: 'rancher-charts' },
215
+ certified: true
216
+ };
217
+ const items = wrapper.vm.getFooterItems(plugin);
218
+
219
+ expect(items).toHaveLength(1);
220
+ expect(items[0].icon).toBe('repository-alt');
221
+ expect(items[0].labels).toStrictEqual(['rancher-charts']);
222
+ });
223
+
224
+ it('should display repository name for installed plugins when chart info is present (original repo exists)', () => {
225
+ const plugin = {
226
+ installed: true,
227
+ chart: { repoNameDisplay: 'rancher-charts' },
228
+ certified: true
229
+ };
230
+ const items = wrapper.vm.getFooterItems(plugin);
231
+
232
+ expect(items).toHaveLength(1);
233
+ expect(items[0].icon).toBe('repository-alt');
234
+ expect(items[0].labels).toStrictEqual(['rancher-charts']);
235
+ });
236
+
237
+ it('should display original repository name for installed plugins when original repo was removed', () => {
238
+ const plugin = {
239
+ installed: true,
240
+ originalRepoNameDisplay: 'removed-repo',
241
+ certified: true
242
+ };
243
+ const items = wrapper.vm.getFooterItems(plugin);
244
+
245
+ expect(items).toHaveLength(1);
246
+ expect(items[0].icon).toBe('repository-alt');
247
+ expect(items[0].labels).toStrictEqual(['removed-repo']);
248
+ });
249
+
250
+ it('should prefer chart.repoNameDisplay over originalRepoNameDisplay when both are present', () => {
251
+ const plugin = {
252
+ installed: true,
253
+ chart: { repoNameDisplay: 'current-repo' },
254
+ originalRepoNameDisplay: 'original-repo',
255
+ certified: true
256
+ };
257
+ const items = wrapper.vm.getFooterItems(plugin);
258
+
259
+ expect(items).toHaveLength(1);
260
+ expect(items[0].labels).toStrictEqual(['current-repo']);
261
+ });
262
+
263
+ it('should NOT display repository name when neither chart nor originalRepoNameDisplay are present', () => {
264
+ const plugin = {
265
+ installed: true,
266
+ certified: true
267
+ };
268
+ const items = wrapper.vm.getFooterItems(plugin);
269
+
270
+ expect(items).toHaveLength(0);
271
+ });
210
272
  });
211
273
 
212
274
  describe('getStatuses', () => {
@@ -269,6 +331,162 @@ describe('page: UI plugins/Extensions', () => {
269
331
  });
270
332
  });
271
333
 
334
+ describe('showInstallDialog', () => {
335
+ let dispatchMock: jest.Mock;
336
+
337
+ const createWrapper = (availablePlugins: any[] = []) => {
338
+ dispatchMock = jest.fn().mockResolvedValue(true);
339
+
340
+ const store = {
341
+ getters: {
342
+ 'prefs/get': jest.fn(),
343
+ 'catalog/rawCharts': {},
344
+ 'uiplugins/plugins': [],
345
+ 'uiplugins/errors': {},
346
+ 'management/all': () => [],
347
+ },
348
+ dispatch: dispatchMock,
349
+ };
350
+
351
+ const wrapper = shallowMount(UiPluginsPage, {
352
+ global: {
353
+ mocks: {
354
+ $store: store,
355
+ t,
356
+ },
357
+ stubs: { ActionMenu: { template: '<div />' } }
358
+ }
359
+ });
360
+
361
+ // Mock the available computed property
362
+ Object.defineProperty(wrapper.vm, 'available', { get: () => availablePlugins });
363
+
364
+ return wrapper;
365
+ };
366
+
367
+ it('should open UninstallExistingExtensionDialog when installing a plugin that is already installed from a different source', () => {
368
+ const installedPlugin = {
369
+ id: 'other-repo/my-plugin',
370
+ name: 'my-plugin',
371
+ installed: true
372
+ };
373
+ const pluginToInstall = {
374
+ id: 'new-repo/my-plugin',
375
+ name: 'my-plugin'
376
+ };
377
+
378
+ const wrapper = createWrapper([installedPlugin, pluginToInstall]);
379
+
380
+ wrapper.vm.showInstallDialog(pluginToInstall, 'install', {});
381
+
382
+ expect(dispatchMock).toHaveBeenCalledWith(
383
+ 'management/promptModal',
384
+ expect.objectContaining({ component: 'UninstallExistingExtensionDialog' })
385
+ );
386
+ });
387
+
388
+ it('should NOT open InstallExtensionDialog when plugin is already installed from a different source', () => {
389
+ const installedPlugin = {
390
+ id: 'other-repo/my-plugin',
391
+ name: 'my-plugin',
392
+ installed: true
393
+ };
394
+ const pluginToInstall = {
395
+ id: 'new-repo/my-plugin',
396
+ name: 'my-plugin'
397
+ };
398
+
399
+ const wrapper = createWrapper([installedPlugin, pluginToInstall]);
400
+
401
+ wrapper.vm.showInstallDialog(pluginToInstall, 'install', {});
402
+
403
+ expect(dispatchMock).not.toHaveBeenCalledWith(
404
+ 'management/promptModal',
405
+ expect.objectContaining({ component: 'InstallExtensionDialog' })
406
+ );
407
+ });
408
+
409
+ it('should pass the installedPlugin to UninstallExistingExtensionDialog', () => {
410
+ const installedPlugin = {
411
+ id: 'other-repo/my-plugin',
412
+ name: 'my-plugin',
413
+ installed: true
414
+ };
415
+ const pluginToInstall = {
416
+ id: 'new-repo/my-plugin',
417
+ name: 'my-plugin'
418
+ };
419
+
420
+ const wrapper = createWrapper([installedPlugin, pluginToInstall]);
421
+
422
+ wrapper.vm.showInstallDialog(pluginToInstall, 'install', {});
423
+
424
+ expect(dispatchMock).toHaveBeenCalledWith(
425
+ 'management/promptModal',
426
+ expect.objectContaining({
427
+ component: 'UninstallExistingExtensionDialog',
428
+ componentProps: expect.objectContaining({ installedPlugin })
429
+ })
430
+ );
431
+ });
432
+
433
+ it('should open InstallExtensionDialog when installing a plugin that is NOT installed from another source', () => {
434
+ const pluginToInstall = {
435
+ id: 'repo/my-plugin',
436
+ name: 'my-plugin'
437
+ };
438
+
439
+ const wrapper = createWrapper([pluginToInstall]);
440
+
441
+ wrapper.vm.showInstallDialog(pluginToInstall, 'install', {});
442
+
443
+ expect(dispatchMock).toHaveBeenCalledWith(
444
+ 'management/promptModal',
445
+ expect.objectContaining({ component: 'InstallExtensionDialog' })
446
+ );
447
+ });
448
+
449
+ it('should open InstallExtensionDialog when the same plugin id is being re-installed (same source)', () => {
450
+ const installedPlugin = {
451
+ id: 'repo/my-plugin',
452
+ name: 'my-plugin',
453
+ installed: true
454
+ };
455
+
456
+ const wrapper = createWrapper([installedPlugin]);
457
+
458
+ wrapper.vm.showInstallDialog(installedPlugin, 'install', {});
459
+
460
+ expect(dispatchMock).toHaveBeenCalledWith(
461
+ 'management/promptModal',
462
+ expect.objectContaining({ component: 'InstallExtensionDialog' })
463
+ );
464
+ });
465
+
466
+ it('should open InstallExtensionDialog for upgrade action even if same name exists in another repo', () => {
467
+ const installedPlugin = {
468
+ id: 'repo-a/my-plugin',
469
+ name: 'my-plugin',
470
+ installed: true
471
+ };
472
+ const otherPlugin = {
473
+ id: 'repo-b/my-plugin',
474
+ name: 'my-plugin',
475
+ installed: false
476
+ };
477
+
478
+ const wrapper = createWrapper([installedPlugin, otherPlugin]);
479
+
480
+ // Upgrade action should always go to InstallExtensionDialog
481
+ wrapper.vm.showInstallDialog(installedPlugin, 'upgrade', {});
482
+
483
+ expect(dispatchMock).toHaveBeenCalledWith(
484
+ 'management/promptModal',
485
+ expect.objectContaining({ component: 'InstallExtensionDialog' })
486
+ );
487
+ });
488
+ });
489
+
272
490
  describe('watch: helmOps', () => {
273
491
  let wrapper: VueWrapper<any>;
274
492
  let updatePluginInstallStatusMock: jest.Mock;
@@ -296,10 +514,10 @@ describe('page: UI plugins/Extensions', () => {
296
514
  computed: {
297
515
  // Override the computed property for this test suite
298
516
  available: () => [
299
- { name: 'plugin1' },
300
- { name: 'plugin2' },
301
- { name: 'plugin3' },
302
- { name: 'plugin4' },
517
+ { name: 'plugin1', id: 'repo/plugin1' },
518
+ { name: 'plugin2', id: 'repo/plugin2' },
519
+ { name: 'plugin3', id: 'repo/plugin3' },
520
+ { name: 'plugin4', id: 'repo/plugin4' },
303
521
  ],
304
522
  hasMenuActions: () => true,
305
523
  menuActions: () => []
@@ -311,9 +529,9 @@ describe('page: UI plugins/Extensions', () => {
311
529
 
312
530
  // Set the 'installing' status on the component instance
313
531
  wrapper.vm.installing = {
314
- plugin1: 'install',
315
- plugin2: 'downgrade',
316
- plugin3: 'uninstall',
532
+ 'repo/plugin1': 'install',
533
+ 'repo/plugin2': 'downgrade',
534
+ 'repo/plugin3': 'uninstall',
317
535
  };
318
536
 
319
537
  // Reset errors
@@ -330,7 +548,7 @@ describe('page: UI plugins/Extensions', () => {
330
548
  wrapper.vm.helmOps = helmOps;
331
549
  await wrapper.vm.$nextTick();
332
550
 
333
- expect(updatePluginInstallStatusMock).toHaveBeenCalledWith('plugin1', 'install');
551
+ expect(updatePluginInstallStatusMock).toHaveBeenCalledWith('repo/plugin1', 'install');
334
552
  });
335
553
 
336
554
  it('should not update status for an upgrade op when a downgrade was initiated', async() => {
@@ -344,7 +562,7 @@ describe('page: UI plugins/Extensions', () => {
344
562
  await wrapper.vm.$nextTick();
345
563
 
346
564
  // It should not be called with 'upgrade' for plugin2
347
- expect(updatePluginInstallStatusMock).not.toHaveBeenCalledWith('plugin2', 'upgrade');
565
+ expect(updatePluginInstallStatusMock).not.toHaveBeenCalledWith('repo/plugin2', 'upgrade');
348
566
  });
349
567
 
350
568
  it('should clear status for a completed uninstall operation', async() => {
@@ -357,7 +575,7 @@ describe('page: UI plugins/Extensions', () => {
357
575
  wrapper.vm.helmOps = helmOps;
358
576
  await wrapper.vm.$nextTick();
359
577
 
360
- expect(updatePluginInstallStatusMock).toHaveBeenCalledWith('plugin3', false);
578
+ expect(updatePluginInstallStatusMock).toHaveBeenCalledWith('repo/plugin3', false);
361
579
  });
362
580
 
363
581
  it('should set error and clear status for a failed operation', async() => {
@@ -370,8 +588,8 @@ describe('page: UI plugins/Extensions', () => {
370
588
  wrapper.vm.helmOps = helmOps;
371
589
  await wrapper.vm.$nextTick();
372
590
 
373
- expect(wrapper.vm.errors.plugin1).toBe(true);
374
- expect(updatePluginInstallStatusMock).toHaveBeenCalledWith('plugin1', false);
591
+ expect(wrapper.vm.errors['repo/plugin1']).toBe(true);
592
+ expect(updatePluginInstallStatusMock).toHaveBeenCalledWith('repo/plugin1', false);
375
593
  });
376
594
 
377
595
  it('should clear status for plugins with no active operation', async() => {
@@ -385,7 +603,7 @@ describe('page: UI plugins/Extensions', () => {
385
603
  await wrapper.vm.$nextTick();
386
604
 
387
605
  // plugin4 has no op, so its status should be cleared
388
- expect(updatePluginInstallStatusMock).toHaveBeenCalledWith('plugin4', false);
606
+ expect(updatePluginInstallStatusMock).toHaveBeenCalledWith('repo/plugin4', false);
389
607
  });
390
608
  });
391
609
  });