Skip to content

Commit 0c0f042

Browse files
authored
feat: link to a page listing the extensions related to kubernetes providers (#433)
* feat: link to extensions catalog Signed-off-by: Philippe Martin <[email protected]> * feat: new provider card Signed-off-by: Philippe Martin <[email protected]> * chore: update podman-desktop api with navigate to extensions catalog Signed-off-by: Philippe Martin <[email protected]> * test: add unit test Signed-off-by: Philippe Martin <[email protected]> * fix: ui Signed-off-by: Philippe Martin <[email protected]> --------- Signed-off-by: Philippe Martin <[email protected]>
1 parent 81e501b commit 0c0f042

File tree

7 files changed

+248
-13
lines changed

7 files changed

+248
-13
lines changed

packages/channels/src/interface/navigation-api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ export const NavigationApi = Symbol.for('NavigationApi');
2020

2121
export interface NavigationApi {
2222
navigateToProviderNewConnection(id: string): Promise<void>;
23+
navigateToExtensionsCatalog(searchTerm?: string): Promise<void>;
2324
}

packages/extension/src/manager/navigation-api.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,15 @@ export class NavigationApiImpl implements NavigationApi {
3232
}
3333
return podmanDesktopApi.navigation.navigateToCreateProviderConnection(id);
3434
}
35+
36+
async navigateToExtensionsCatalog(searchTerm?: string): Promise<void> {
37+
// This test can be removed when the minimal version is set to 1.23
38+
if (!('navigateToExtensionsCatalog' in podmanDesktopApi.navigation)) {
39+
console.warn(
40+
'navigating to extensions catalog is not supported in this version of Podman Desktop, please upgrade to the latest version',
41+
);
42+
return;
43+
}
44+
return podmanDesktopApi.navigation.navigateToExtensionsCatalog({ searchTerm });
45+
}
3546
}

packages/webview/src/component/dashboard/KubernetesProviderCard.svelte

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,12 @@ async function createNew(provider: KubernetesProvider): Promise<void> {
3434
<Markdown markdown={provider.emptyConnectionMarkdownDescription} />
3535
</p>
3636

37-
<div class="flex justify-center">
38-
<Button
39-
type="primary"
40-
on:click={(): Promise<void> => createNew(provider)}
41-
class="flex items-center"
42-
aria-label={provider.creationButtonTitle ?? 'Create new'}>
43-
<Fa icon={faPlusCircle} size="1.2x" class="mr-1" />
44-
{provider.creationButtonTitle ?? 'Create new'}
45-
</Button>
46-
</div>
37+
<Button
38+
type="primary"
39+
on:click={(): Promise<void> => createNew(provider)}
40+
class="flex items-center"
41+
aria-label={provider.creationButtonTitle ?? 'Create new'}>
42+
<Fa icon={faPlusCircle} size="1.2x" class="mr-1" />
43+
{provider.creationButtonTitle ?? 'Create new'}
44+
</Button>
4745
</div>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<script lang="ts">
2+
import { faPuzzlePiece } from '@fortawesome/free-solid-svg-icons';
3+
import { Button } from '@podman-desktop/ui-svelte';
4+
import Fa from 'svelte-fa';
5+
import NewProvider from '/@/component/icons/NewProvider.svelte';
6+
import { getContext } from 'svelte';
7+
import { Remote } from '/@/remote/remote';
8+
import { API_NAVIGATION } from '@kubernetes-dashboard/channels';
9+
import Markdown from '/@/markdown/Markdown.svelte';
10+
11+
const remote = getContext<Remote>(Remote);
12+
const navigationApi = remote.getProxy(API_NAVIGATION);
13+
14+
const markdownText = `
15+
Install a new Kubernetes provider via extension. Navigate to extensions by pressing the button, install the ones you prefer and they will show up here.
16+
17+
More information: [creating a kube cluster](https://podman-desktop.io/docs/kubernetes/creating-a-kube-cluster)`;
18+
</script>
19+
20+
<div class="rounded-xl p-5 text-left border border-dotted border-(--pd-content-divider)">
21+
<div class="flex justify-left text-(--pd-details-empty-icon) py-2 mb-2">
22+
<NewProvider />
23+
</div>
24+
<h1 class="text-lg font-semibold mb-4">New provider</h1>
25+
26+
<p class="text-sm text-(--pd-content-text) mb-6">
27+
<Markdown markdown={markdownText}></Markdown>
28+
</p>
29+
30+
<Button
31+
type="secondary"
32+
on:click={async (): Promise<void> => {
33+
await navigationApi.navigateToExtensionsCatalog('category:kubernetes keyword:provider not:installed');
34+
}}
35+
class="flex items-center"
36+
aria-label="See available extensions">
37+
<Fa icon={faPuzzlePiece} size="1.2x" class="mr-1" />
38+
See available extensions
39+
</Button>
40+
</div>

packages/webview/src/component/dashboard/NoContextPage.spec.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,30 @@ import { beforeEach, expect, test, vi } from 'vitest';
2323
import NoContextPage from './NoContextPage.svelte';
2424
import { StatesMocks } from '/@/tests/state-mocks';
2525
import { FakeStateObject } from '/@/state/util/fake-state-object.svelte';
26-
import type { KubernetesProvidersInfo } from '@kubernetes-dashboard/channels';
26+
import { API_NAVIGATION, type KubernetesProvidersInfo, type NavigationApi } from '@kubernetes-dashboard/channels';
2727
import KubeIcon from '/@/component/icons/KubeIcon.svelte';
2828
import KubernetesProviderCard from '/@/component/dashboard/KubernetesProviderCard.svelte';
2929
import type { Unsubscriber } from 'svelte/store';
30+
import { RemoteMocks } from '/@/tests/remote-mocks';
3031

3132
vi.mock(import('./KubernetesProviderCard.svelte'));
3233
vi.mock(import('/@/component/icons/KubeIcon.svelte'));
3334

3435
const statesMocks = new StatesMocks();
3536
let kubernetesProvidersMock: FakeStateObject<KubernetesProvidersInfo, void>;
37+
const remoteMocks = new RemoteMocks();
3638

3739
beforeEach(() => {
3840
vi.resetAllMocks();
3941
statesMocks.reset();
42+
remoteMocks.reset();
4043

4144
kubernetesProvidersMock = new FakeStateObject<KubernetesProvidersInfo, void>();
4245
statesMocks.mock<KubernetesProvidersInfo, void>('stateKubernetesProvidersInfoUI', kubernetesProvidersMock);
46+
47+
remoteMocks.mock(API_NAVIGATION, {
48+
navigateToExtensionsCatalog: vi.fn(),
49+
} as unknown as NavigationApi);
4350
});
4451

4552
test('should render the Kubernetes icon', () => {
@@ -50,8 +57,17 @@ test('should render the Kubernetes icon', () => {
5057
test('should render the main heading', () => {
5158
render(NoContextPage);
5259

53-
const heading = screen.getByRole('heading', { level: 1 });
54-
expect(heading).toHaveTextContent('No Kubernetes cluster');
60+
const headings = screen.getAllByRole('heading', { level: 1 });
61+
expect(headings.length).toBeGreaterThan(0);
62+
expect(headings[0]).toHaveTextContent('No Kubernetes cluster');
63+
});
64+
65+
test('should render the New provider card', () => {
66+
render(NoContextPage);
67+
68+
const headings = screen.getAllByRole('heading', { level: 1 });
69+
expect(headings.length).toBeGreaterThan(1);
70+
expect(headings[1]).toHaveTextContent('New provider');
5571
});
5672

5773
test('should render the description text', () => {

packages/webview/src/component/dashboard/NoContextPage.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { getContext, onDestroy, onMount } from 'svelte';
44
import { States } from '/@/state/states';
55
import type { Unsubscriber } from 'svelte/store';
66
import KubernetesProviderCard from '/@/component/dashboard/KubernetesProviderCard.svelte';
7+
import NewProviderCard from '/@/component/dashboard/NewProviderCard.svelte';
78
89
const states = getContext<States>(States);
910
const kubernetesProviders = states.stateKubernetesProvidersInfoUI;
@@ -33,6 +34,7 @@ onDestroy(() => {
3334
{#each kubernetesProviders.data?.providers as provider (provider.id)}
3435
<KubernetesProviderCard provider={provider} />
3536
{/each}
37+
<NewProviderCard />
3638
</div>
3739
</div>
3840
</div>

0 commit comments

Comments
 (0)