Skip to content

Commit

Permalink
feat: make Kubernetes support experimental
Browse files Browse the repository at this point in the history
Adds a new experimental flag (off by default) that hides the Kubernetes
options on the left navbar. When you enable it, the options automatically
appear.

Tests added to make sure that the Kubernetes options don't appear if the
preference is not set, and do appear if it is (and have at least one
context).

Fixes podman-desktop#5525.

Signed-off-by: Tim deBoer <git@tdeboer.ca>
  • Loading branch information
deboer-tim committed Jan 22, 2024
1 parent 8df529b commit bc635dc
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 9 deletions.
6 changes: 6 additions & 0 deletions packages/main/src/plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ import { NavigationManager } from '/@/plugin/navigation/navigation-manager.js';
import { WebviewRegistry } from './webview/webview-registry.js';
import type { IDisposable } from './types/disposable.js';

import { KubernetesUtils } from './kubernetes-util.js';

type LogType = 'log' | 'warn' | 'trace' | 'debug' | 'error';

export const UPDATER_UPDATE_AVAILABLE_ICON = 'fa fa-exclamation-triangle';
Expand Down Expand Up @@ -746,6 +748,10 @@ export class PluginSystem {
const webviewRegistry = new WebviewRegistry(apiSender);
await webviewRegistry.start();

// init kubernetes configuration
const kubernetesUtils = new KubernetesUtils(configurationRegistry);
kubernetesUtils.init();

this.extensionLoader = new ExtensionLoader(
commandRegistry,
menuRegistry,
Expand Down
41 changes: 41 additions & 0 deletions packages/main/src/plugin/kubernetes-util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**********************************************************************
* Copyright (C) 2024 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 { IConfigurationNode, IConfigurationRegistry } from './configuration-registry.js';

export class KubernetesUtils {
constructor(private configurationRegistry: IConfigurationRegistry) {}

init() {
const kubernetesConfiguration: IConfigurationNode = {
id: 'preferences.kubernetes',
title: 'Kubernetes',
type: 'object',
properties: {
['kubernetes.experimental']: {
description: 'Experimental extended Kubernetes support.',
type: 'boolean',
default: false,
hidden: false,
},
},
};

this.configurationRegistry.registerConfigurations([kubernetesConfiguration]);
}
}
91 changes: 83 additions & 8 deletions packages/renderer/src/AppNavigation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,19 @@
***********************************************************************/

import '@testing-library/jest-dom/vitest';
import { beforeAll, test, expect } from 'vitest';
import { beforeAll, test, expect, vi } from 'vitest';
import { render, screen } from '@testing-library/svelte';
import AppNavigation from './AppNavigation.svelte';
import type { TinroRouteMeta } from 'tinro';
import { kubernetesContexts } from './stores/kubernetes-contexts';

// fake the window.events object
const eventsMock = vi.fn();
const getConfigurationValueMock = vi.fn();

// fake the window object
beforeAll(() => {
(window.events as unknown) = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
receive: (_channel: string, func: any) => {
func();
},
};
(window as any).events = eventsMock;
(window as any).getConfigurationValue = getConfigurationValueMock;
});

test('Test rendering of the navigation bar with empty items', () => {
Expand Down Expand Up @@ -57,4 +57,79 @@ test('Test rendering of the navigation bar with empty items', () => {
expect(volumes).toBeInTheDocument();
const settings = screen.getByRole('link', { name: 'Settings' });
expect(settings).toBeInTheDocument();

const deployments = screen.queryByRole('link', { name: 'Deployments' });
expect(deployments).not.toBeInTheDocument();
const services = screen.queryByRole('link', { name: 'Services' });
expect(services).not.toBeInTheDocument();
});

test('Test Kubernetes experimental hidden with valid context', async () => {
kubernetesContexts.set([
{
name: 'context-name',
cluster: 'cluster-name',
user: 'user-name',
clusterInfo: {
name: 'cluster-name',
server: 'https://server-name',
},
},
]);
getConfigurationValueMock.mockResolvedValue(false);

const meta = {
url: '/',
} as unknown as TinroRouteMeta;

render(AppNavigation, {
meta,
exitSettingsCallback: () => {},
});

const navigationBar = screen.getByRole('navigation', { name: 'AppNavigation' });
expect(navigationBar).toBeInTheDocument();

// wait 100ms for stores to initialize
await new Promise(resolve => setTimeout(resolve, 100));

const deployments = screen.queryByRole('link', { name: 'Deployments' });
expect(deployments).not.toBeInTheDocument();
const services = screen.queryByRole('link', { name: 'Services' });
expect(services).not.toBeInTheDocument();
});

test('Test Kubernetes experimental enablement', async () => {
kubernetesContexts.set([
{
name: 'context-name',
cluster: 'cluster-name',
user: 'user-name',
clusterInfo: {
name: 'cluster-name',
server: 'https://server-name',
},
},
]);
getConfigurationValueMock.mockResolvedValue(true);

const meta = {
url: '/',
} as unknown as TinroRouteMeta;

render(AppNavigation, {
meta,
exitSettingsCallback: () => {},
});

const navigationBar = screen.getByRole('navigation', { name: 'AppNavigation' });
expect(navigationBar).toBeInTheDocument();

// wait 100ms for stores to initialize
await new Promise(resolve => setTimeout(resolve, 100));

const deployments = screen.getByRole('link', { name: 'Deployments' });
expect(deployments).toBeInTheDocument();
const services = screen.getByRole('link', { name: 'Services' });
expect(services).toBeInTheDocument();
});
19 changes: 18 additions & 1 deletion packages/renderer/src/AppNavigation.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { ingresses } from './stores/ingresses';
import { routes } from './stores/routes';
import Webviews from '/@/lib/webview/Webviews.svelte';
import PuzzleIcon from './lib/images/PuzzleIcon.svelte';
import { onDidChangeConfiguration } from './stores/configurationProperties';
let podInfoSubscribe: Unsubscriber;
let containerInfoSubscribe: Unsubscriber;
Expand All @@ -54,6 +55,17 @@ let ingressesRoutesCount = '';
const imageUtils = new ImageUtils();
export let exitSettingsCallback: () => void;
const KUBERNETES_EXPERIMENTAL_CONFIGURATION_KEY = 'kubernetes.experimental';
let showKubernetesNav = false;
async function updateKubernetesNav(): Promise<void> {
showKubernetesNav = (await window.getConfigurationValue<boolean>(KUBERNETES_EXPERIMENTAL_CONFIGURATION_KEY)) || false;
}
let configurationChangeCallback: EventListenerOrEventListenerObject = () => {
updateKubernetesNav();
};
onMount(async () => {
const commandRegistry = new CommandRegistry();
commandRegistry.init();
Expand Down Expand Up @@ -112,6 +124,10 @@ onMount(async () => {
contextsSubscribe = kubernetesContexts.subscribe(value => {
contextCount = value.length;
});
// set initial Kubernetes experimental state, and listen for changes
await updateKubernetesNav();
onDidChangeConfiguration.addEventListener(KUBERNETES_EXPERIMENTAL_CONFIGURATION_KEY, configurationChangeCallback);
});
onDestroy(() => {
Expand All @@ -138,6 +154,7 @@ onDestroy(() => {
}
ingressesSubscribe?.();
routesSubscribe?.();
onDidChangeConfiguration.removeEventListener(KUBERNETES_EXPERIMENTAL_CONFIGURATION_KEY, configurationChangeCallback);
});
function updateIngressesRoutesCount(count: number) {
Expand Down Expand Up @@ -181,7 +198,7 @@ export let meta: TinroRouteMeta;
<NavItem href="/volumes" tooltip="Volumes{volumeCount}" ariaLabel="Volumes" bind:meta="{meta}">
<VolumeIcon size="24" />
</NavItem>
{#if contextCount > 0}
{#if contextCount > 0 && showKubernetesNav}
<NavItem href="/deployments" tooltip="Deployments{deploymentCount}" ariaLabel="Deployments" bind:meta="{meta}">
<DeploymentIcon size="24" />
</NavItem>
Expand Down

0 comments on commit bc635dc

Please sign in to comment.