Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow for extensions to navigate to an onboarding screen #9759

Merged
merged 1 commit into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/api/src/navigation-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export enum NavigationPage {
IMAGES = 'images',
IMAGE_BUILD = 'image-build',
IMAGE = 'image',
ONBOARDING = 'preferences-onboarding',
PODS = 'pods',
POD = 'pod',
VOLUMES = 'volumes',
Expand Down
1 change: 1 addition & 0 deletions packages/api/src/navigation-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface NavigationParameters {
[NavigationPage.IMAGES]: never;
[NavigationPage.IMAGE_BUILD]: never;
[NavigationPage.IMAGE]: { id: string; engineId: string; tag: string };
[NavigationPage.ONBOARDING]: { extensionId: string };
[NavigationPage.PODS]: never;
[NavigationPage.POD]: { kind: string; name: string; engineId: string };
[NavigationPage.VOLUMES]: never;
Expand Down
6 changes: 6 additions & 0 deletions packages/extension-api/src/extension-api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4797,6 +4797,12 @@ declare module '@podman-desktop/api' {
*/
export function navigateToEditProviderContainerConnection(connection: ProviderContainerConnection): Promise<void>;

/**
* Navigate to a specific onboarding page referenced by its extensionId
* @param extensionId The id of the extension to navigate to or if missing, default to the extension calling the method
*/
export function navigateToOnboarding(extensionId?: string): Promise<void>;

/**
* Allow to define custom route for an extension.
*
Expand Down
95 changes: 94 additions & 1 deletion packages/main/src/plugin/extension-loader.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { NavigationManager } from '/@/plugin/navigation/navigation-manager.js';
import type { WebviewRegistry } from '/@/plugin/webview/webview-registry.js';
import type { ContributionInfo } from '/@api/contribution-info.js';
import { NavigationPage } from '/@api/navigation-page.js';
import type { OnboardingInfo } from '/@api/onboarding.js';
import type { WebviewInfo } from '/@api/webview-info.js';

import { getBase64Image } from '../util.js';
Expand Down Expand Up @@ -179,7 +180,9 @@ const authenticationProviderRegistry: AuthenticationImpl = {

const iconRegistry: IconRegistry = {} as unknown as IconRegistry;

const onboardingRegistry: OnboardingRegistry = {} as unknown as OnboardingRegistry;
const onboardingRegistry: OnboardingRegistry = {
getOnboarding: vi.fn(),
} as unknown as OnboardingRegistry;

const telemetryTrackMock = vi.fn();
const telemetry: Telemetry = { track: telemetryTrackMock } as unknown as Telemetry;
Expand Down Expand Up @@ -233,6 +236,7 @@ const navigationManager: NavigationManager = new NavigationManager(
providerRegistry,
webviewRegistry,
commandRegistry,
onboardingRegistry,
);

const colorRegistry = {
Expand Down Expand Up @@ -1784,6 +1788,95 @@ describe('Navigation', async () => {

expect(vi.mocked(webviewRegistry.listWebviews)).toHaveBeenCalled();
});

test('navigateToOnboarding without parameter', async () => {
const api = extensionLoader.createApi(
'path',
{
name: 'name',
publisher: 'publisher',
version: '1',
displayName: 'dname',
},
[],
);

vi.mocked(onboardingRegistry.getOnboarding).mockReturnValue({
extension: 'foo',
} as OnboardingInfo);

await api.navigation.navigateToOnboarding();
expect(vi.mocked(apiSender.send)).toBeCalledWith('navigate', {
page: NavigationPage.ONBOARDING,
parameters: {
extensionId: 'publisher.name',
},
});

// checked on onboarding registry
expect(vi.mocked(onboardingRegistry.getOnboarding)).toHaveBeenCalledWith('publisher.name');
});

test('navigateToOnboarding with parameter', async () => {
vi.mocked(onboardingRegistry.getOnboarding).mockReturnValue({
extension: 'foo',
} as OnboardingInfo);

const api = extensionLoader.createApi(
'path',
{
name: 'name',
publisher: 'publisher',
version: '1',
displayName: 'dname',
},
[],
);

// Call the method provided
await api.navigation.navigateToOnboarding('my.extension');

// Ensure the send method is called properly
expect(vi.mocked(apiSender.send)).toBeCalledWith('navigate', {
page: NavigationPage.ONBOARDING,
parameters: {
extensionId: 'my.extension',
},
});

// checked on onboarding registry
expect(vi.mocked(onboardingRegistry.getOnboarding)).toHaveBeenCalledWith('my.extension');
});

test('navigateToOnboarding but no onboarding available', async () => {
vi.mocked(onboardingRegistry.getOnboarding).mockReturnValue(undefined);

const api = extensionLoader.createApi(
'path',
{
name: 'name',
publisher: 'publisher',
version: '1',
displayName: 'dname',
},
[],
);

// Call the method provided
let error = undefined;
try {
await api.navigation.navigateToOnboarding('do.not-exists');
} catch (e) {
error = e;
}
expect(error).toBeDefined();

// Ensure the send method is never called
expect(vi.mocked(apiSender.send)).not.toHaveBeenCalled();

// checked on onboarding registry
expect(vi.mocked(onboardingRegistry.getOnboarding)).toHaveBeenCalledWith('do.not-exists');
});
});

test('check listWebviews', async () => {
Expand Down
7 changes: 7 additions & 0 deletions packages/main/src/plugin/extension-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1435,6 +1435,13 @@ export class ExtensionLoader {
): Promise<void> => {
await this.navigationManager.navigateToEditProviderContainerConnection(connection);
},
navigateToOnboarding: async (extensionId?: string): Promise<void> => {
let onboardingExtensionId = extensionId;
if (!onboardingExtensionId) {
onboardingExtensionId = extensionInfo.id;
}
await this.navigationManager.navigateToOnboarding(onboardingExtensionId);
},
navigate: async (routeId: string, ...args: unknown[]): Promise<void> => {
return this.navigationManager.navigateToRoute(`${extensionInfo.id}.${routeId}`, args);
},
Expand Down
1 change: 1 addition & 0 deletions packages/main/src/plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,7 @@ export class PluginSystem {
providerRegistry,
webviewRegistry,
commandRegistry,
onboardingRegistry,
);

this.extensionLoader = new ExtensionLoader(
Expand Down
20 changes: 20 additions & 0 deletions packages/main/src/plugin/navigation/navigation-manager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ import type { ProviderContainerConnection } from '@podman-desktop/api';
import { beforeEach, describe, expect, test, vi } from 'vitest';

import type { CommandRegistry } from '/@/plugin/command-registry.js';
import type { OnboardingRegistry } from '/@/plugin/onboarding-registry.js';
import { NavigationPage } from '/@api/navigation-page.js';
import type { OnboardingInfo } from '/@api/onboarding.js';
import type { WebviewInfo } from '/@api/webview-info.js';

import type { ApiSenderType } from '../api.js';
Expand Down Expand Up @@ -65,6 +67,10 @@ const commandRegistry: CommandRegistry = {
executeCommand: vi.fn(),
} as unknown as CommandRegistry;

const onboardingRegistry: OnboardingRegistry = {
getOnboarding: vi.fn(),
} as unknown as OnboardingRegistry;

beforeEach(() => {
vi.resetAllMocks();
navigationManager = new TestNavigationManager(
Expand All @@ -74,6 +80,7 @@ beforeEach(() => {
providerRegistry,
webviewRegistry,
commandRegistry,
onboardingRegistry,
);
});

Expand Down Expand Up @@ -178,6 +185,19 @@ test('check navigateToEditProviderContainerConnection', async () => {
});
});

test('check navigateToOnboarding', async () => {
vi.mocked(onboardingRegistry.getOnboarding).mockReturnValue({ extension: 'foo' } as OnboardingInfo);

await navigationManager.navigateToOnboarding('my.extension');

expect(apiSender.send).toHaveBeenCalledWith('navigate', {
page: NavigationPage.ONBOARDING,
parameters: {
extensionId: 'my.extension',
},
});
});

describe('register route', () => {
test('registering route should provide a disposable', () => {
const routeId = 'dummy-route-id';
Expand Down
19 changes: 19 additions & 0 deletions packages/main/src/plugin/navigation/navigation-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import type { ApiSenderType } from '/@/plugin/api.js';
import type { CommandRegistry } from '/@/plugin/command-registry.js';
import type { ContainerProviderRegistry } from '/@/plugin/container-registry.js';
import type { ContributionManager } from '/@/plugin/contribution-manager.js';
import type { OnboardingRegistry } from '/@/plugin/onboarding-registry.js';
import { NavigationPage } from '/@api/navigation-page.js';
import type { NavigationRequest } from '/@api/navigation-request.js';

Expand All @@ -44,6 +45,7 @@ export class NavigationManager {
private providerRegistry: ProviderRegistry,
private webviewRegistry: WebviewRegistry,
private commandRegistry: CommandRegistry,
private onboardingRegistry: OnboardingRegistry,
) {
this.#registry = new Map();
}
Expand Down Expand Up @@ -124,6 +126,12 @@ export class NavigationManager {
if (!(await this.containerRegistry.containerExist(id))) throw new Error(`Container with id ${id} cannot be found.`);
}

private assertOnboardingExist(extensionId: string): void {
if (!this.onboardingRegistry.getOnboarding(extensionId)) {
throw new Error(`Onboarding with extension id ${extensionId} cannot be found.`);
}
}

async navigateToContainerLogs(id: string): Promise<void> {
await this.assertContainerExist(id);

Expand Down Expand Up @@ -301,4 +309,15 @@ export class NavigationManager {
},
});
}

async navigateToOnboarding(extensionId: string): Promise<void> {
this.assertOnboardingExist(extensionId);

this.navigateTo({
page: NavigationPage.ONBOARDING,
parameters: {
extensionId: extensionId,
},
});
}
}
11 changes: 11 additions & 0 deletions packages/renderer/src/navigation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,17 @@ test(`Test navigationHandle for ${NavigationPage.IMAGE}`, () => {
expect(vi.mocked(router.goto)).toHaveBeenCalledWith('/images/123/dummyEngineId/ZHVtbXlUYWc=');
});

test(`Test navigationHandle for ${NavigationPage.ONBOARDING}`, () => {
handleNavigation({
page: NavigationPage.ONBOARDING,
parameters: {
extensionId: 'my.extension',
},
});

expect(vi.mocked(router.goto)).toHaveBeenCalledWith('/preferences/onboarding/my.extension');
});

test(`Test navigationHandle for ${NavigationPage.PODS}`, () => {
handleNavigation({ page: NavigationPage.PODS });

Expand Down
3 changes: 3 additions & 0 deletions packages/renderer/src/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ export const handleNavigation = (request: InferredNavigationRequest<NavigationPa
`/images/${request.parameters.id}/${request.parameters.engineId}/${Buffer.from(request.parameters.tag).toString('base64')}`,
);
break;
case NavigationPage.ONBOARDING:
router.goto(`/preferences/onboarding/${request.parameters.extensionId}`);
break;
case NavigationPage.PODS:
router.goto(`/pods`);
break;
Expand Down