Skip to content

Commit 03c17eb

Browse files
authored
refactor: introduce DashboardApiManager (#216)
Signed-off-by: Philippe Martin <phmartin@redhat.com>
1 parent 267f991 commit 03c17eb

File tree

4 files changed

+81
-38
lines changed

4 files changed

+81
-38
lines changed

packages/extension/src/manager/_manager-module.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**********************************************************************
2-
* Copyright (C) 2025 Red Hat, Inc.
2+
* Copyright (C) 2025 - 2026 Red Hat, Inc.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,13 +23,15 @@ import { ChannelSubscriber } from '/@/manager/channel-subscriber';
2323
import { Dispatcher } from '/@/manager/dispatcher';
2424
import { DashboardStatesManager } from './dashboard-states-manager';
2525
import { OpenDialogApiImpl } from '/@/manager/open-dialog-api';
26+
import { DashboardApiManager } from '/@/manager/dashboard-api-manager';
2627

2728
const managersModule = new ContainerModule(options => {
2829
options.bind<ContextsManager>(ContextsManager).toSelf().inSingletonScope();
2930
options.bind<ContextsManager>(ChannelSubscriber).toSelf().inSingletonScope();
3031
options.bind<Dispatcher>(Dispatcher).toSelf().inSingletonScope();
3132
options.bind<DashboardStatesManager>(DashboardStatesManager).toSelf().inSingletonScope();
3233
options.bind<OpenDialogApiImpl>(OpenDialogApiImpl).toSelf().inSingletonScope();
34+
options.bind<DashboardApiManager>(DashboardApiManager).toSelf().inSingletonScope();
3335

3436
// Bind IDisposable to services which need to clear data/stop connection/etc when the panel is left
3537
// (the onDestroy are not called from components when the panel is left, which may introduce memory leaks if not disposed here)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**********************************************************************
2+
* Copyright (C) 2026 Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
***********************************************************************/
18+
19+
import type { KubernetesDashboardExtensionApi } from '@podman-desktop/kubernetes-dashboard-extension-api';
20+
import { extensions } from '@podman-desktop/api';
21+
import { injectable } from 'inversify';
22+
23+
@injectable()
24+
export class DashboardApiManager {
25+
/**
26+
* @returns The Kubernetes Dashboard extension API or undefined if the extension is not installed
27+
*/
28+
getApi(): KubernetesDashboardExtensionApi | undefined {
29+
return extensions.getExtension<KubernetesDashboardExtensionApi>('podman-desktop.kubernetes-dashboard')?.exports;
30+
}
31+
}

packages/extension/src/manager/dashboard-states-manager.spec.ts

Lines changed: 41 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,32 @@
1818

1919
import { afterEach, assert, beforeEach, describe, expect, test, vi } from 'vitest';
2020
import { DashboardStatesManager } from './dashboard-states-manager';
21-
import { type Disposable, type Extension, extensions } from '@podman-desktop/api';
21+
import type { Disposable, ExtensionContext, TelemetryLogger } from '@podman-desktop/api';
22+
import { extensions } from '@podman-desktop/api';
2223
import {
2324
type ResourcesCountInfo,
2425
type ContextsHealthsInfo,
2526
type KubernetesDashboardExtensionApi,
2627
type KubernetesDashboardSubscriber,
2728
type ContextsPermissionsInfo,
2829
} from '@podman-desktop/kubernetes-dashboard-extension-api';
30+
import { InversifyBinding } from '/@/inject/inversify-binding';
31+
import type { RpcExtension } from '@kubernetes-contexts/rpc';
32+
import type { Container } from 'inversify';
33+
import { DashboardApiManager } from '/@/manager/dashboard-api-manager';
2934

30-
beforeEach(() => {
35+
let container: Container;
36+
37+
const dashboardApiManagerMock: DashboardApiManager = {
38+
getApi: vi.fn(),
39+
} as unknown as DashboardApiManager;
40+
41+
beforeEach(async () => {
3142
vi.resetAllMocks();
43+
44+
const inversifyBinding = new InversifyBinding({} as RpcExtension, {} as ExtensionContext, {} as TelemetryLogger);
45+
container = await inversifyBinding.initBindings();
46+
(await container.rebind(DashboardApiManager)).toConstantValue(dashboardApiManagerMock);
3247
});
3348

3449
describe('dashboard extension is not installed', () => {
@@ -39,20 +54,21 @@ describe('dashboard extension is not installed', () => {
3954
vi.mocked(extensions.onDidChange).mockReturnValue({
4055
dispose: onDidChangeDisposable,
4156
} as unknown as Disposable);
57+
vi.mocked(dashboardApiManagerMock.getApi).mockReturnValue(undefined);
4258
});
4359

4460
afterEach(() => {
4561
manager?.dispose();
4662
});
4763

4864
test('subscriber is undefined', () => {
49-
manager = new DashboardStatesManager();
65+
manager = container.get(DashboardStatesManager);
5066
manager.init();
5167
expect(manager.getSubscriber()).toBeUndefined();
5268
});
5369

5470
test('onDidChangeDisposable is called', () => {
55-
manager = new DashboardStatesManager();
71+
manager = container.get(DashboardStatesManager);
5672
manager.init();
5773
manager.dispose();
5874
expect(onDidChangeDisposable).toHaveBeenCalled();
@@ -77,62 +93,55 @@ describe('dashboard extension is installed', () => {
7793
dispose: onDidChangeDisposable,
7894
} as unknown as Disposable;
7995
});
80-
vi.mocked(extensions.getExtension).mockImplementation(id => {
81-
vi.mocked(subscriber).mockReturnValue({
82-
onContextsHealth: onContextsHealth,
83-
onResourcesCount: onResourcesCount,
84-
onContextsPermissions: onContextsPermissions,
85-
dispose: disposeSubscriber,
86-
} as unknown as KubernetesDashboardSubscriber);
87-
if (id === 'podman-desktop.kubernetes-dashboard') {
88-
return {
89-
exports: {
90-
getSubscriber: subscriber,
91-
},
92-
} as unknown as Extension<KubernetesDashboardExtensionApi>;
93-
}
94-
return undefined;
95-
});
96+
vi.mocked(subscriber).mockReturnValue({
97+
onContextsHealth: onContextsHealth,
98+
onResourcesCount: onResourcesCount,
99+
onContextsPermissions: onContextsPermissions,
100+
dispose: disposeSubscriber,
101+
} as unknown as KubernetesDashboardSubscriber);
102+
vi.mocked(dashboardApiManagerMock.getApi).mockReturnValue({
103+
getSubscriber: subscriber,
104+
} as unknown as KubernetesDashboardExtensionApi);
96105
});
97106

98107
afterEach(() => {
99108
manager?.dispose();
100109
});
101110

102111
test('subscriber is eventually defined', async () => {
103-
manager = new DashboardStatesManager();
112+
manager = container.get(DashboardStatesManager);
104113
manager.init();
105114
await vi.waitFor(() => {
106115
expect(manager.getSubscriber()).toBeDefined();
107116
});
108117
});
109118

110119
test('onContextsHealth is eventually called on subscriber', async () => {
111-
manager = new DashboardStatesManager();
120+
manager = container.get(DashboardStatesManager);
112121
manager.init();
113122
await vi.waitFor(() => {
114123
expect(onContextsHealth).toHaveBeenCalled();
115124
});
116125
});
117126

118127
test('onResourcesCount is eventually called on subscriber', async () => {
119-
manager = new DashboardStatesManager();
128+
manager = container.get(DashboardStatesManager);
120129
manager.init();
121130
await vi.waitFor(() => {
122131
expect(onResourcesCount).toHaveBeenCalled();
123132
});
124133
});
125134

126135
test('onContextsPermissions is eventually called on subscriber', async () => {
127-
manager = new DashboardStatesManager();
136+
manager = container.get(DashboardStatesManager);
128137
manager.init();
129138
await vi.waitFor(() => {
130139
expect(onContextsPermissions).toHaveBeenCalled();
131140
});
132141
});
133142

134143
test('when contextsHealth callback is called, onContextsHealthChange is fired', async () => {
135-
manager = new DashboardStatesManager();
144+
manager = container.get(DashboardStatesManager);
136145
const onContextsHealthChangeCallback: () => void = vi.fn();
137146
manager.onContextsHealthChange(onContextsHealthChangeCallback);
138147

@@ -158,7 +167,7 @@ describe('dashboard extension is installed', () => {
158167
});
159168

160169
test('when resourcesCount callback is called, onResourcesCountChange is fired', async () => {
161-
manager = new DashboardStatesManager();
170+
manager = container.get(DashboardStatesManager);
162171
const onResourcesCountChangeCallback: () => void = vi.fn();
163172
manager.onResourcesCountChange(onResourcesCountChangeCallback);
164173

@@ -183,7 +192,7 @@ describe('dashboard extension is installed', () => {
183192
});
184193

185194
test('when contextsPermissions callback is called, onContextsPermissionsChange is fired', async () => {
186-
manager = new DashboardStatesManager();
195+
manager = container.get(DashboardStatesManager);
187196
const onContextsPermissionsChangeCallback: () => void = vi.fn();
188197
manager.onContextsPermissionsChange(onContextsPermissionsChangeCallback);
189198

@@ -208,7 +217,7 @@ describe('dashboard extension is installed', () => {
208217
});
209218

210219
test('when contextsHealth callback is called, getContextsHealths returns the correct value', async () => {
211-
manager = new DashboardStatesManager();
220+
manager = container.get(DashboardStatesManager);
212221
const onContextsHealthChangeCallback: () => void = vi.fn();
213222
manager.onContextsHealthChange(onContextsHealthChangeCallback);
214223

@@ -235,7 +244,7 @@ describe('dashboard extension is installed', () => {
235244
});
236245

237246
test('when resourcesCount callback is called, getResourcesCount returns the correct value', async () => {
238-
manager = new DashboardStatesManager();
247+
manager = container.get(DashboardStatesManager);
239248
const onResourcesCountChangeCallback: () => void = vi.fn();
240249
manager.onResourcesCountChange(onResourcesCountChangeCallback);
241250

@@ -261,7 +270,7 @@ describe('dashboard extension is installed', () => {
261270
});
262271

263272
test('when contextsPermissions callback is called, getContextsPermissions returns the correct value', async () => {
264-
manager = new DashboardStatesManager();
273+
manager = container.get(DashboardStatesManager);
265274
const onContextsPermissionsChangeCallback: () => void = vi.fn();
266275
manager.onContextsPermissionsChange(onContextsPermissionsChangeCallback);
267276

@@ -287,14 +296,14 @@ describe('dashboard extension is installed', () => {
287296
});
288297

289298
test('onDidChangeDisposable is called', () => {
290-
manager = new DashboardStatesManager();
299+
manager = container.get(DashboardStatesManager);
291300
manager.init();
292301
manager.dispose();
293302
expect(onDidChangeDisposable).toHaveBeenCalled();
294303
});
295304

296305
test('subscriber is disposed on dispose', async () => {
297-
manager = new DashboardStatesManager();
306+
manager = container.get(DashboardStatesManager);
298307
manager.init();
299308
await vi.waitFor(() => {
300309
expect(manager.getSubscriber()).toBeDefined();

packages/extension/src/manager/dashboard-states-manager.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ import { Disposable, extensions } from '@podman-desktop/api';
2020
import {
2121
ContextsHealthsInfo,
2222
ResourcesCountInfo,
23-
KubernetesDashboardExtensionApi,
2423
KubernetesDashboardSubscriber,
2524
ContextsPermissionsInfo,
2625
} from '@podman-desktop/kubernetes-dashboard-extension-api';
27-
import { injectable } from 'inversify';
26+
import { inject, injectable } from 'inversify';
2827
import { Emitter, Event } from '/@/types/emitter';
28+
import { DashboardApiManager } from '/@/manager/dashboard-api-manager';
2929

3030
@injectable()
3131
export class DashboardStatesManager implements Disposable {
@@ -45,11 +45,12 @@ export class DashboardStatesManager implements Disposable {
4545
#resourcesCount: ResourcesCountInfo = { counts: [] };
4646
#contextsPermissions: ContextsPermissionsInfo = { permissions: [] };
4747

48+
@inject(DashboardApiManager)
49+
protected dashboardApiManager: DashboardApiManager;
50+
4851
init(): void {
4952
const didChangeSubscription = extensions.onDidChange(() => {
50-
const api = extensions.getExtension<KubernetesDashboardExtensionApi>(
51-
'podman-desktop.kubernetes-dashboard',
52-
)?.exports;
53+
const api = this.dashboardApiManager.getApi();
5354
if (api) {
5455
this.#subscriber = api.getSubscriber();
5556
// dispose the subscriber when the extension is deactivated

0 commit comments

Comments
 (0)