diff --git a/packages/common/src/channels.ts b/packages/common/src/channels.ts index 9ad2dd8e..05e47598 100644 --- a/packages/common/src/channels.ts +++ b/packages/common/src/channels.ts @@ -1,5 +1,32 @@ -import { type DashboardApi } from './interface/dashboard-api'; +/********************************************************************** + * Copyright (C) 2025 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + +import type { ActiveResourcesCountInfo } from './model/active-resources-count-info'; +import type { ContextsHealthsInfo } from './model/contexts-healths-info'; +import type { ContextsPermissionsInfo } from './model/contexts-permissions-info'; +import type { ResourcesCountInfo } from './model/resources-count-info'; +import type { UpdateResourceInfo } from './model/update-resource-info'; + import { createRpcChannel } from './rpc'; -// RPC channels (used by the webview to send requests to the extension) -export const API_DASHBOARD = createRpcChannel('DashboardApi'); +// Broadcast events (sent by extension and received by the webview) +export const RESOURCES_COUNT = createRpcChannel('ResourcesCount'); +export const ACTIVE_RESOURCES_COUNT = createRpcChannel('ActiveResourcesCount'); +export const CONTEXTS_HEALTHS = createRpcChannel('ContextsHealths'); +export const CONTEXTS_PERMISSIONS = createRpcChannel('ContextsPermissions'); +export const UPDATE_RESOURCE = createRpcChannel('UpdateResource'); diff --git a/packages/common/src/model/active-resources-count-info.ts b/packages/common/src/model/active-resources-count-info.ts new file mode 100644 index 00000000..f5903de7 --- /dev/null +++ b/packages/common/src/model/active-resources-count-info.ts @@ -0,0 +1,23 @@ +/********************************************************************** + * Copyright (C) 2025 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + +import type { ResourceCount } from './kubernetes-resource-count'; + +export interface ActiveResourcesCountInfo { + counts: ResourceCount[]; +} diff --git a/packages/common/src/model/api-sender.ts b/packages/common/src/model/contexts-healths-info.ts similarity index 81% rename from packages/common/src/model/api-sender.ts rename to packages/common/src/model/contexts-healths-info.ts index e6cf3ccc..0415fcee 100644 --- a/packages/common/src/model/api-sender.ts +++ b/packages/common/src/model/contexts-healths-info.ts @@ -1,5 +1,5 @@ /********************************************************************** - * Copyright (C) 2022-2024 Red Hat, Inc. + * Copyright (C) 2025 Red Hat, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ * SPDX-License-Identifier: Apache-2.0 ***********************************************************************/ -export type ApiSenderType = { - send: (channel: string, data?: unknown) => void; -}; +import type { ContextHealth } from './kubernetes-contexts-healths'; + +export interface ContextsHealthsInfo { + healths: ContextHealth[]; +} diff --git a/packages/common/src/model/contexts-permissions-info.ts b/packages/common/src/model/contexts-permissions-info.ts new file mode 100644 index 00000000..8ed51cbb --- /dev/null +++ b/packages/common/src/model/contexts-permissions-info.ts @@ -0,0 +1,23 @@ +/********************************************************************** + * Copyright (C) 2025 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + +import type { ContextPermission } from './kubernetes-contexts-permissions'; + +export interface ContextsPermissionsInfo { + permissions: ContextPermission[]; +} diff --git a/packages/common/src/interface/dashboard-api.ts b/packages/common/src/model/resources-count-info.ts similarity index 85% rename from packages/common/src/interface/dashboard-api.ts rename to packages/common/src/model/resources-count-info.ts index 24cc5730..7c1a134d 100644 --- a/packages/common/src/interface/dashboard-api.ts +++ b/packages/common/src/model/resources-count-info.ts @@ -16,7 +16,8 @@ * SPDX-License-Identifier: Apache-2.0 ***********************************************************************/ -export const DashboardApi = Symbol.for('DashboardApi'); -export interface DashboardApi { - ping(): Promise; +import type { ResourceCount } from './kubernetes-resource-count'; + +export interface ResourcesCountInfo { + counts: ResourceCount[]; } diff --git a/packages/common/src/model/update-resource-info.ts b/packages/common/src/model/update-resource-info.ts new file mode 100644 index 00000000..6a60da36 --- /dev/null +++ b/packages/common/src/model/update-resource-info.ts @@ -0,0 +1,25 @@ +/********************************************************************** + * Copyright (C) 2025 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + +import type { KubernetesContextResources } from './kubernetes-resources'; + +export interface UpdateResourceInfo { + contextName: string; + resourceName: string; + resources: KubernetesContextResources[]; +} diff --git a/packages/extension/src/dashboard-extension.spec.ts b/packages/extension/src/dashboard-extension.spec.ts index 5bf544e5..bef3c890 100644 --- a/packages/extension/src/dashboard-extension.spec.ts +++ b/packages/extension/src/dashboard-extension.spec.ts @@ -22,17 +22,19 @@ import { assert, beforeEach, describe, expect, test, vi } from 'vitest'; import { DashboardExtension } from './dashboard-extension'; import { vol } from 'memfs'; -import type { ContextsManager } from './manager/contexts-manager'; -import type { ContextsStatesDispatcher } from './manager/contexts-states-dispatcher'; +import { ContextsManager } from './manager/contexts-manager'; +import { ContextsStatesDispatcher } from './manager/contexts-states-dispatcher'; let extensionContextMock: ExtensionContext; let dashboardExtension: DashboardExtension; let contextsManagerMock: ContextsManager; -let contextsStatesDispatcher: ContextsStatesDispatcher; +let contextsStatesDispatcherMock: ContextsStatesDispatcher; vi.mock(import('node:fs')); vi.mock(import('node:fs/promises')); vi.mock(import('@kubernetes/client-node')); +vi.mock(import('./manager/contexts-manager')); +vi.mock(import('./manager/contexts-states-dispatcher')); beforeEach(() => { vi.restoreAllMocks(); @@ -50,14 +52,19 @@ beforeEach(() => { extensionContextMock = { subscriptions: [], } as unknown as ExtensionContext; + // Create a mock for the contextsManager contextsManagerMock = { update: vi.fn(), } as unknown as ContextsManager; - contextsStatesDispatcher = { + vi.mocked(ContextsManager).mockReturnValue(contextsManagerMock); + + contextsStatesDispatcherMock = { init: vi.fn(), } as unknown as ContextsStatesDispatcher; - dashboardExtension = new DashboardExtension(extensionContextMock, contextsManagerMock, contextsStatesDispatcher); + vi.mocked(ContextsStatesDispatcher).mockReturnValue(contextsStatesDispatcherMock); + + dashboardExtension = new DashboardExtension(extensionContextMock); vi.mocked(kubernetes.getKubeconfig).mockReturnValue({ path: '/path/to/kube/config', } as Uri); @@ -80,7 +87,7 @@ describe('a kubeconfig file is not present', () => { callback({ type: 'UPDATE', location: { path: '/path/to/kube/config' } as Uri }); expect(contextsManagerMock.update).toHaveBeenCalledOnce(); - expect(contextsStatesDispatcher.init).toHaveBeenCalledOnce(); + expect(contextsStatesDispatcherMock.init).toHaveBeenCalledOnce(); }); test('should deactivate correctly', async () => { @@ -107,7 +114,7 @@ describe('a kubeconfig file is present', () => { callback({ type: 'UPDATE', location: { path: '/path/to/kube/config' } as Uri }); expect(contextsManagerMock.update).toHaveBeenCalledOnce(); - expect(contextsStatesDispatcher.init).toHaveBeenCalledOnce(); + expect(contextsStatesDispatcherMock.init).toHaveBeenCalledOnce(); }); test('should deactivate correctly', async () => { diff --git a/packages/extension/src/dashboard-extension.ts b/packages/extension/src/dashboard-extension.ts index b04cc8e4..7ae8c35e 100644 --- a/packages/extension/src/dashboard-extension.ts +++ b/packages/extension/src/dashboard-extension.ts @@ -22,24 +22,18 @@ import { kubernetes, Uri, window } from '@podman-desktop/api'; import { RpcExtension } from '/@common/rpc/rpc'; import { readFile } from 'node:fs/promises'; -import type { ContextsManager } from './manager/contexts-manager'; +import { ContextsManager } from './manager/contexts-manager'; import { existsSync } from 'node:fs'; import { KubeConfig } from '@kubernetes/client-node'; -import type { ContextsStatesDispatcher } from './manager/contexts-states-dispatcher'; +import { ContextsStatesDispatcher } from './manager/contexts-states-dispatcher'; export class DashboardExtension { #extensionContext: ExtensionContext; #contextsManager: ContextsManager; #contextsStatesDispatcher: ContextsStatesDispatcher; - constructor( - readonly extensionContext: ExtensionContext, - readonly contextManager: ContextsManager, - readonly contextsStatesDispatcher: ContextsStatesDispatcher, - ) { + constructor(readonly extensionContext: ExtensionContext) { this.#extensionContext = extensionContext; - this.#contextsManager = contextManager; - this.#contextsStatesDispatcher = contextsStatesDispatcher; } async activate(): Promise { @@ -50,6 +44,9 @@ export class DashboardExtension { rpcExtension.init(); this.#extensionContext.subscriptions.push(rpcExtension); + this.#contextsManager = new ContextsManager(); + this.#contextsStatesDispatcher = new ContextsStatesDispatcher(this.#contextsManager, rpcExtension); + const now = performance.now(); const afterFirst = performance.now(); diff --git a/packages/extension/src/main.ts b/packages/extension/src/main.ts index b264fff5..42860bc9 100644 --- a/packages/extension/src/main.ts +++ b/packages/extension/src/main.ts @@ -19,21 +19,12 @@ import type { ExtensionContext } from '@podman-desktop/api'; import { DashboardExtension } from './dashboard-extension'; -import { ContextsManager } from './manager/contexts-manager'; -import { ContextsStatesDispatcher } from './manager/contexts-states-dispatcher'; let dashboardExtension: DashboardExtension | undefined; // Initialize the activation of the extension. export async function activate(extensionContext: ExtensionContext): Promise { - const contextsManager = new ContextsManager(); - const apiSender = { - send: (channel: string, data?: unknown): void => { - console.log(`==> recv data "${data}" on channel ${channel}`); - }, - }; - const contextsStatesDispatcher = new ContextsStatesDispatcher(contextsManager, apiSender); - dashboardExtension ??= new DashboardExtension(extensionContext, contextsManager, contextsStatesDispatcher); + dashboardExtension ??= new DashboardExtension(extensionContext); await dashboardExtension.activate(); } diff --git a/packages/extension/src/manager/contexts-states-dispatcher.spec.ts b/packages/extension/src/manager/contexts-states-dispatcher.spec.ts index 430b57b8..25405753 100644 --- a/packages/extension/src/manager/contexts-states-dispatcher.spec.ts +++ b/packages/extension/src/manager/contexts-states-dispatcher.spec.ts @@ -21,13 +21,14 @@ import { expect, test, vi } from 'vitest'; import type { IDisposable } from '/@/types/disposable.js'; import type { ContextPermission } from '/@common/model/kubernetes-contexts-permissions.js'; -import type { ApiSenderType } from '/@common/model/api-sender.js'; import type { ContextHealthState } from './context-health-checker.js'; import type { ContextPermissionResult } from './context-permissions-checker.js'; import type { DispatcherEvent } from './contexts-dispatcher.js'; import type { ContextsManager } from './contexts-manager.js'; import { ContextsStatesDispatcher } from './contexts-states-dispatcher.js'; import type { KubeConfigSingleContext } from '/@/types/kubeconfig-single-context.js'; +import type { RpcExtension } from '/@common/rpc/rpc.js'; +import { CONTEXTS_HEALTHS, CONTEXTS_PERMISSIONS, RESOURCES_COUNT, UPDATE_RESOURCE } from '/@common/channels.js'; test('ContextsStatesDispatcher should call updateHealthStates when onContextHealthStateChange event is fired', () => { const manager: ContextsManager = { @@ -41,12 +42,12 @@ test('ContextsStatesDispatcher should call updateHealthStates when onContextHeal onResourceUpdated: vi.fn(), isContextOffline: vi.fn(), } as unknown as ContextsManager; - const apiSender: ApiSenderType = { - send: vi.fn(), - } as unknown as ApiSenderType; - const dispatcher = new ContextsStatesDispatcher(manager, apiSender); - const updateHealthStatesSpy = vi.spyOn(dispatcher, 'updateHealthStates'); - const updatePermissionsSpy = vi.spyOn(dispatcher, 'updatePermissions'); + const rpcExtension: RpcExtension = { + fire: vi.fn(), + } as unknown as RpcExtension; + const dispatcher = new ContextsStatesDispatcher(manager, rpcExtension); + const updateHealthStatesSpy = vi.spyOn(dispatcher, 'updateHealthStates').mockResolvedValue(); + const updatePermissionsSpy = vi.spyOn(dispatcher, 'updatePermissions').mockResolvedValue(); dispatcher.init(); expect(updateHealthStatesSpy).not.toHaveBeenCalled(); expect(updatePermissionsSpy).not.toHaveBeenCalled(); @@ -58,7 +59,7 @@ test('ContextsStatesDispatcher should call updateHealthStates when onContextHeal expect(updatePermissionsSpy).not.toHaveBeenCalled(); }); -test('ContextsStatesDispatcher should call updateHealthStates, updateResourcesCount and updateActiveResourcesCount when onOfflineChange event is fired', () => { +test('ContextsStatesDispatcher should call updateHealthStates, updateResourcesCount and updateActiveResourcesCount when onOfflineChange event is fired', async () => { const manager: ContextsManager = { onContextHealthStateChange: vi.fn(), onOfflineChange: vi.fn(), @@ -70,13 +71,13 @@ test('ContextsStatesDispatcher should call updateHealthStates, updateResourcesCo onResourceUpdated: vi.fn(), isContextOffline: vi.fn(), } as unknown as ContextsManager; - const apiSender: ApiSenderType = { - send: vi.fn(), - } as unknown as ApiSenderType; - const dispatcher = new ContextsStatesDispatcher(manager, apiSender); - const updateHealthStatesSpy = vi.spyOn(dispatcher, 'updateHealthStates'); - const updateResourcesCountSpy = vi.spyOn(dispatcher, 'updateResourcesCount'); - const updateActiveResourcesCountSpy = vi.spyOn(dispatcher, 'updateActiveResourcesCount'); + const rpcExtension: RpcExtension = { + fire: vi.fn(), + } as unknown as RpcExtension; + const dispatcher = new ContextsStatesDispatcher(manager, rpcExtension); + const updateHealthStatesSpy = vi.spyOn(dispatcher, 'updateHealthStates').mockResolvedValue(); + const updateResourcesCountSpy = vi.spyOn(dispatcher, 'updateResourcesCount').mockResolvedValue(); + const updateActiveResourcesCountSpy = vi.spyOn(dispatcher, 'updateActiveResourcesCount').mockResolvedValue(); dispatcher.init(); expect(updateHealthStatesSpy).not.toHaveBeenCalled(); expect(updateResourcesCountSpy).not.toHaveBeenCalled(); @@ -85,9 +86,11 @@ test('ContextsStatesDispatcher should call updateHealthStates, updateResourcesCo vi.mocked(manager.onOfflineChange).mockImplementation(f => f() as IDisposable); vi.mocked(manager.getHealthCheckersStates).mockReturnValue(new Map()); dispatcher.init(); - expect(updateHealthStatesSpy).toHaveBeenCalled(); - expect(updateResourcesCountSpy).toHaveBeenCalled(); - expect(updateActiveResourcesCountSpy).toHaveBeenCalled(); + await vi.waitFor(() => { + expect(updateHealthStatesSpy).toHaveBeenCalled(); + expect(updateResourcesCountSpy).toHaveBeenCalled(); + expect(updateActiveResourcesCountSpy).toHaveBeenCalled(); + }); }); test('ContextsStatesDispatcher should call updatePermissions when onContextPermissionResult event is fired', () => { @@ -102,13 +105,13 @@ test('ContextsStatesDispatcher should call updatePermissions when onContextPermi onResourceUpdated: vi.fn(), isContextOffline: vi.fn(), } as unknown as ContextsManager; - const apiSender: ApiSenderType = { - send: vi.fn(), - } as unknown as ApiSenderType; + const rpcExtension: RpcExtension = { + fire: vi.fn(), + } as unknown as RpcExtension; + const dispatcher = new ContextsStatesDispatcher(manager, rpcExtension); vi.mocked(manager.getPermissions).mockReturnValue([]); - const dispatcher = new ContextsStatesDispatcher(manager, apiSender); - const updateHealthStatesSpy = vi.spyOn(dispatcher, 'updateHealthStates'); - const updatePermissionsSpy = vi.spyOn(dispatcher, 'updatePermissions'); + const updateHealthStatesSpy = vi.spyOn(dispatcher, 'updateHealthStates').mockResolvedValue(); + const updatePermissionsSpy = vi.spyOn(dispatcher, 'updatePermissions').mockResolvedValue(); dispatcher.init(); expect(updateHealthStatesSpy).not.toHaveBeenCalled(); expect(updatePermissionsSpy).not.toHaveBeenCalled(); @@ -119,7 +122,7 @@ test('ContextsStatesDispatcher should call updatePermissions when onContextPermi expect(updatePermissionsSpy).toHaveBeenCalled(); }); -test('ContextsStatesDispatcher should call updateHealthStates and updatePermissions when onContextDelete event is fired', () => { +test('ContextsStatesDispatcher should call updateHealthStates and updatePermissions when onContextDelete event is fired', async () => { const manager: ContextsManager = { onContextHealthStateChange: vi.fn(), onOfflineChange: vi.fn(), @@ -131,13 +134,13 @@ test('ContextsStatesDispatcher should call updateHealthStates and updatePermissi onResourceUpdated: vi.fn(), isContextOffline: vi.fn(), } as unknown as ContextsManager; - const apiSender: ApiSenderType = { - send: vi.fn(), - } as unknown as ApiSenderType; + const rpcExtension: RpcExtension = { + fire: vi.fn(), + } as unknown as RpcExtension; + const dispatcher = new ContextsStatesDispatcher(manager, rpcExtension); vi.mocked(manager.getPermissions).mockReturnValue([]); - const dispatcher = new ContextsStatesDispatcher(manager, apiSender); - const updateHealthStatesSpy = vi.spyOn(dispatcher, 'updateHealthStates'); - const updatePermissionsSpy = vi.spyOn(dispatcher, 'updatePermissions'); + const updateHealthStatesSpy = vi.spyOn(dispatcher, 'updateHealthStates').mockResolvedValue(); + const updatePermissionsSpy = vi.spyOn(dispatcher, 'updatePermissions').mockResolvedValue(); vi.mocked(manager.getHealthCheckersStates).mockReturnValue(new Map()); dispatcher.init(); expect(updateHealthStatesSpy).not.toHaveBeenCalled(); @@ -145,11 +148,13 @@ test('ContextsStatesDispatcher should call updateHealthStates and updatePermissi vi.mocked(manager.onContextDelete).mockImplementation(f => f({} as DispatcherEvent) as IDisposable); dispatcher.init(); - expect(updateHealthStatesSpy).toHaveBeenCalled(); - expect(updatePermissionsSpy).toHaveBeenCalled(); + await vi.waitFor(() => { + expect(updateHealthStatesSpy).toHaveBeenCalled(); + expect(updatePermissionsSpy).toHaveBeenCalled(); + }); }); -test('ContextsStatesDispatcher should call updateResource and updateActiveResourcesCount when onResourceUpdated event is fired', () => { +test('ContextsStatesDispatcher should call updateResource and updateActiveResourcesCount when onResourceUpdated event is fired', async () => { const manager: ContextsManager = { onContextHealthStateChange: vi.fn(), onOfflineChange: vi.fn(), @@ -161,13 +166,13 @@ test('ContextsStatesDispatcher should call updateResource and updateActiveResour onResourceUpdated: vi.fn(), isContextOffline: vi.fn(), } as unknown as ContextsManager; - const apiSender: ApiSenderType = { - send: vi.fn(), - } as unknown as ApiSenderType; + const rpcExtension: RpcExtension = { + fire: vi.fn(), + } as unknown as RpcExtension; + const dispatcher = new ContextsStatesDispatcher(manager, rpcExtension); vi.mocked(manager.getPermissions).mockReturnValue([]); - const dispatcher = new ContextsStatesDispatcher(manager, apiSender); - const updateResourceSpy = vi.spyOn(dispatcher, 'updateResource'); - const updateActiveResourcesCountSpy = vi.spyOn(dispatcher, 'updateActiveResourcesCount'); + const updateResourceSpy = vi.spyOn(dispatcher, 'updateResource').mockResolvedValue(); + const updateActiveResourcesCountSpy = vi.spyOn(dispatcher, 'updateActiveResourcesCount').mockResolvedValue(); vi.mocked(manager.getHealthCheckersStates).mockReturnValue(new Map()); dispatcher.init(); expect(updateResourceSpy).not.toHaveBeenCalled(); @@ -177,8 +182,10 @@ test('ContextsStatesDispatcher should call updateResource and updateActiveResour f => f({} as { contextName: string; resourceName: string }) as IDisposable, ); dispatcher.init(); - expect(updateResourceSpy).toHaveBeenCalled(); - expect(updateActiveResourcesCountSpy).toHaveBeenCalled(); + await vi.waitFor(() => { + expect(updateResourceSpy).toHaveBeenCalled(); + expect(updateActiveResourcesCountSpy).toHaveBeenCalled(); + }); }); test('getContextsHealths should return the values of the map returned by manager.getHealthCheckersStates without kubeConfig', () => { @@ -191,10 +198,10 @@ test('getContextsHealths should return the values of the map returned by manager getPermissions: vi.fn(), isContextOffline: vi.fn(), } as unknown as ContextsManager; - const apiSender: ApiSenderType = { - send: vi.fn(), - } as unknown as ApiSenderType; - const dispatcher = new ContextsStatesDispatcher(manager, apiSender); + const rpcExtension: RpcExtension = { + fire: vi.fn(), + } as unknown as RpcExtension; + const dispatcher = new ContextsStatesDispatcher(manager, rpcExtension); const context1State = { contextName: 'context1', checking: true, @@ -221,7 +228,7 @@ test('getContextsHealths should return the values of the map returned by manager expect(result).toEqual([context1State, context2State, context3State]); }); -test('updateHealthStates should call apiSender.send with kubernetes-contexts-healths', () => { +test('updateHealthStates should call rpcExtension.fire with CONTEXTS_HEALTHS and data', async () => { const manager: ContextsManager = { onContextHealthStateChange: vi.fn(), onContextPermissionResult: vi.fn(), @@ -229,23 +236,23 @@ test('updateHealthStates should call apiSender.send with kubernetes-contexts-hea getHealthCheckersStates: vi.fn(), getPermissions: vi.fn(), } as unknown as ContextsManager; - const apiSender: ApiSenderType = { - send: vi.fn(), - } as unknown as ApiSenderType; - const dispatcher = new ContextsStatesDispatcher(manager, apiSender); + const rpcExtension: RpcExtension = { + fire: vi.fn(), + } as unknown as RpcExtension; + const dispatcher = new ContextsStatesDispatcher(manager, rpcExtension); vi.spyOn(dispatcher, 'getContextsHealths').mockReturnValue([]); - dispatcher.updateHealthStates(); - expect(apiSender.send).toHaveBeenCalledWith('kubernetes-contexts-healths'); + await dispatcher.updateHealthStates(); + expect(rpcExtension.fire).toHaveBeenCalledWith(CONTEXTS_HEALTHS, { healths: [] }); }); test('getContextsPermissions should return the values as an array', () => { const manager: ContextsManager = { getPermissions: vi.fn(), } as unknown as ContextsManager; - const apiSender: ApiSenderType = { - send: vi.fn(), - } as unknown as ApiSenderType; - const dispatcher = new ContextsStatesDispatcher(manager, apiSender); + const rpcExtension: RpcExtension = { + fire: vi.fn(), + } as unknown as RpcExtension; + const dispatcher = new ContextsStatesDispatcher(manager, rpcExtension); const value: ContextPermission[] = [ { contextName: 'context1', @@ -277,32 +284,45 @@ test('getContextsPermissions should return the values as an array', () => { expect(result).toEqual(value); }); -test('updatePermissions should call apiSender.send with kubernetes-contexts-permissions', () => { - const manager: ContextsManager = {} as ContextsManager; - const apiSender: ApiSenderType = { - send: vi.fn(), - } as unknown as ApiSenderType; - const dispatcher = new ContextsStatesDispatcher(manager, apiSender); - dispatcher.updatePermissions(); - expect(vi.mocked(apiSender.send)).toHaveBeenCalledWith('kubernetes-contexts-permissions'); +test('updatePermissions should call rpcExtension.fire with CONTEXTS_PERMISSIONS and data', async () => { + const manager: ContextsManager = { + getPermissions: vi.fn(), + } as unknown as ContextsManager; + const rpcExtension: RpcExtension = { + fire: vi.fn(), + } as unknown as RpcExtension; + const dispatcher = new ContextsStatesDispatcher(manager, rpcExtension); + vi.spyOn(dispatcher, 'getContextsPermissions').mockReturnValue([]); + await dispatcher.updatePermissions(); + expect(rpcExtension.fire).toHaveBeenCalledWith(CONTEXTS_PERMISSIONS, { permissions: [] }); }); -test('updateResourcesCount should call apiSender.send with kubernetes-resources-count', () => { - const manager: ContextsManager = {} as ContextsManager; - const apiSender: ApiSenderType = { - send: vi.fn(), - } as unknown as ApiSenderType; - const dispatcher = new ContextsStatesDispatcher(manager, apiSender); - dispatcher.updateResourcesCount(); - expect(vi.mocked(apiSender.send)).toHaveBeenCalledWith('kubernetes-resources-count'); +test('updateResourcesCount should call rpcExtension.fire with RESOURCES_COUNT and data', async () => { + const manager: ContextsManager = { + getResourcesCount: vi.fn(), + } as unknown as ContextsManager; + const rpcExtension: RpcExtension = { + fire: vi.fn(), + } as unknown as RpcExtension; + const dispatcher = new ContextsStatesDispatcher(manager, rpcExtension); + vi.spyOn(dispatcher, 'getResourcesCount').mockReturnValue([]); + await dispatcher.updateResourcesCount(); + expect(rpcExtension.fire).toHaveBeenCalledWith(RESOURCES_COUNT, { counts: [] }); }); -test('updateResource should call apiSender.send with kubernetes-`resource-name`', () => { - const manager: ContextsManager = {} as ContextsManager; - const apiSender: ApiSenderType = { - send: vi.fn(), - } as unknown as ApiSenderType; - const dispatcher = new ContextsStatesDispatcher(manager, apiSender); - dispatcher.updateResource('resource1'); - expect(vi.mocked(apiSender.send)).toHaveBeenCalledWith('kubernetes-update-resource1'); +test('updateResource should call rpcExtension.fire with UPDATE_RESOURCE and data', async () => { + const manager: ContextsManager = { + getResources: vi.fn(), + } as unknown as ContextsManager; + const rpcExtension: RpcExtension = { + fire: vi.fn(), + } as unknown as RpcExtension; + const dispatcher = new ContextsStatesDispatcher(manager, rpcExtension); + vi.spyOn(dispatcher, 'getResources').mockReturnValue([]); + await dispatcher.updateResource('resource1', 'context1'); + expect(rpcExtension.fire).toHaveBeenCalledWith(UPDATE_RESOURCE, { + contextName: 'context1', + resourceName: 'resource1', + resources: [], + }); }); diff --git a/packages/extension/src/manager/contexts-states-dispatcher.ts b/packages/extension/src/manager/contexts-states-dispatcher.ts index 21753b8e..1c15fc34 100644 --- a/packages/extension/src/manager/contexts-states-dispatcher.ts +++ b/packages/extension/src/manager/contexts-states-dispatcher.ts @@ -22,39 +22,48 @@ import type { ResourceCount } from '/@common/model/kubernetes-resource-count.js' import type { KubernetesContextResources } from '/@common/model/kubernetes-resources.js'; import type { KubernetesTroubleshootingInformation } from '/@common/model/kubernetes-troubleshooting.js'; -import type { ApiSenderType } from '/@common/model/api-sender.js'; import type { ContextHealthState } from './context-health-checker.js'; import type { ContextPermissionResult } from './context-permissions-checker.js'; import type { DispatcherEvent } from './contexts-dispatcher.js'; import type { ContextsManager } from './contexts-manager.js'; +import type { RpcExtension } from '/@common/rpc/rpc.js'; +import { + ACTIVE_RESOURCES_COUNT, + CONTEXTS_HEALTHS, + CONTEXTS_PERMISSIONS, + RESOURCES_COUNT, + UPDATE_RESOURCE, +} from '/@common/channels.js'; export class ContextsStatesDispatcher { constructor( private manager: ContextsManager, - private apiSender: ApiSenderType, + private rpcExtension: RpcExtension, ) {} init(): void { this.manager.onContextHealthStateChange((_state: ContextHealthState) => this.updateHealthStates()); - this.manager.onOfflineChange(() => { - this.updateHealthStates(); - this.updateResourcesCount(); - this.updateActiveResourcesCount(); + this.manager.onOfflineChange(async () => { + await this.updateHealthStates(); + await this.updateResourcesCount(); + await this.updateActiveResourcesCount(); }); this.manager.onContextPermissionResult((_permissions: ContextPermissionResult) => this.updatePermissions()); - this.manager.onContextDelete((_state: DispatcherEvent) => { - this.updateHealthStates(); - this.updatePermissions(); + this.manager.onContextDelete(async (_state: DispatcherEvent) => { + await this.updateHealthStates(); + await this.updatePermissions(); }); this.manager.onResourceCountUpdated(() => this.updateResourcesCount()); - this.manager.onResourceUpdated(event => { - this.updateResource(event.resourceName); - this.updateActiveResourcesCount(); + this.manager.onResourceUpdated(async event => { + await this.updateResource(event.resourceName, event.contextName); + await this.updateActiveResourcesCount(); }); } - updateHealthStates(): void { - this.apiSender.send('kubernetes-contexts-healths'); + async updateHealthStates(): Promise { + await this.rpcExtension.fire(CONTEXTS_HEALTHS, { + healths: this.getContextsHealths(), + }); } getContextsHealths(): ContextHealth[] { @@ -71,20 +80,26 @@ export class ContextsStatesDispatcher { return value; } - updatePermissions(): void { - this.apiSender.send('kubernetes-contexts-permissions'); + async updatePermissions(): Promise { + await this.rpcExtension.fire(CONTEXTS_PERMISSIONS, { + permissions: this.getContextsPermissions(), + }); } getContextsPermissions(): ContextPermission[] { return this.manager.getPermissions(); } - updateResourcesCount(): void { - this.apiSender.send(`kubernetes-resources-count`); + async updateResourcesCount(): Promise { + await this.rpcExtension.fire(RESOURCES_COUNT, { + counts: this.getResourcesCount(), + }); } - updateActiveResourcesCount(): void { - this.apiSender.send(`kubernetes-active-resources-count`); + async updateActiveResourcesCount(): Promise { + await this.rpcExtension.fire(ACTIVE_RESOURCES_COUNT, { + counts: this.getActiveResourcesCount(), + }); } getResourcesCount(): ResourceCount[] { @@ -95,8 +110,12 @@ export class ContextsStatesDispatcher { return this.manager.getActiveResourcesCount(); } - updateResource(resourceName: string): void { - this.apiSender.send(`kubernetes-update-${resourceName}`); + async updateResource(resourceName: string, contextName: string): Promise { + await this.rpcExtension.fire(UPDATE_RESOURCE, { + contextName, + resourceName, + resources: this.getResources([contextName], resourceName), + }); } getResources(contextNames: string[], resourceName: string): KubernetesContextResources[] {