mirror of
https://github.com/podman-desktop/podman-desktop
synced 2026-04-21 17:47:22 +00:00
chore(managed-config): add configuration to disable local extensions (#16457)
### What does this PR do? Adds a setting when set in `~/.local/share/containers/podman-desktop/configuration/settings.json` disables showing the "Local Extensions" tab. ### Screenshot / video of UI `extensions.localExtensions.enabled": true`: `extensions.localExtensions.enabled": false`: ### What issues does this PR fix or reference? <!-- Include any related issues from Podman Desktop repository (or from another issue tracker). --> Closes https://github.com/podman-desktop/podman-desktop/issues/16037 ### How to test this PR? <!-- Please explain steps to verify the functionality, do not forget to provide unit/component tests --> - [X] Tests are covering the bug fix or the new feature 1. Edit `~/.local/share/containers/podman-desktop/configuration/settings.json` 2. Set `extensions.localExtensions.enabled": false` 3. `pnpm watch` 4. See there no more "Local Extensions" tab in Extensions section Signed-off-by: Charlie Drage <charlie@charliedrage.com>
This commit is contained in:
parent
a3d29f7b97
commit
8a6b9649a0
5 changed files with 121 additions and 45 deletions
|
|
@ -163,6 +163,7 @@ const proxy: Proxy = {
|
|||
|
||||
const configurationRegistry: ConfigurationRegistry = {
|
||||
getConfiguration: vi.fn(),
|
||||
registerConfigurations: vi.fn(),
|
||||
} as unknown as ConfigurationRegistry;
|
||||
|
||||
const originalConsoleError = console.error;
|
||||
|
|
@ -398,3 +399,23 @@ test('Should use proxy object if proxySettings is undefined', () => {
|
|||
// @ts-expect-error proxy property exists on https object
|
||||
expect(options.agent?.https?.proxy.href).toBe('https://localhost/');
|
||||
});
|
||||
|
||||
test('should register local extensions enabled configuration property', () => {
|
||||
extensionsCatalog.init();
|
||||
|
||||
expect(configurationRegistry.registerConfigurations).toHaveBeenCalledWith([
|
||||
expect.objectContaining({
|
||||
id: 'preferences.extensions',
|
||||
title: 'Extensions',
|
||||
type: 'object',
|
||||
properties: expect.objectContaining({
|
||||
'extensions.localExtensions.enabled': {
|
||||
description: 'Show the local extensions tab.',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
hidden: true,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -69,6 +69,12 @@ export class ExtensionsCatalog {
|
|||
default: ExtensionsCatalog.DEFAULT_EXTENSIONS_URL,
|
||||
hidden: true,
|
||||
},
|
||||
['extensions.localExtensions.enabled']: {
|
||||
description: 'Show the local extensions tab.',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
hidden: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -96,14 +96,14 @@ const combined: CombinedExtensionInfoUI[] = [
|
|||
] as unknown[] as CombinedExtensionInfoUI[];
|
||||
|
||||
test('Expect to see extensions', async () => {
|
||||
vi.mocked(window.getConfigurationValue).mockResolvedValue(true);
|
||||
catalogExtensionInfos.set([aFakeExtension, bFakeExtension]);
|
||||
extensionInfos.set(combined);
|
||||
|
||||
render(ExtensionList);
|
||||
|
||||
await vi.waitFor(() => {
|
||||
const headingExtensions = screen.getByRole('heading', { name: 'extensions' });
|
||||
expect(headingExtensions).toBeInTheDocument();
|
||||
expect(screen.getByRole('heading', { name: 'extensions' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// get first extension
|
||||
|
|
@ -124,77 +124,74 @@ test('Expect to see extensions', async () => {
|
|||
});
|
||||
|
||||
test('Expect to see empty screen on extension page only', async () => {
|
||||
vi.mocked(window.getConfigurationValue).mockResolvedValue(true);
|
||||
catalogExtensionInfos.set([aFakeExtension]);
|
||||
extensionInfos.set([]);
|
||||
|
||||
render(ExtensionList, { searchTerm: 'A' });
|
||||
|
||||
let title: HTMLElement | null;
|
||||
|
||||
await vi.waitFor(() => {
|
||||
title = screen.queryByText(`No extensions matching 'A' found`);
|
||||
expect(title).toBeInTheDocument();
|
||||
expect(screen.queryByText(`No extensions matching 'A' found`)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// click on the catalog
|
||||
const catalogTab = screen.getByRole('button', { name: 'Catalog' });
|
||||
await fireEvent.click(catalogTab);
|
||||
|
||||
title = screen.queryByText(`No extensions matching 'A' found`);
|
||||
const title = screen.queryByText(`No extensions matching 'A' found`);
|
||||
expect(title).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Expect to see empty screen on catalog page only', async () => {
|
||||
vi.mocked(window.getConfigurationValue).mockResolvedValue(true);
|
||||
catalogExtensionInfos.set([]);
|
||||
extensionInfos.set(combined);
|
||||
|
||||
render(ExtensionList, { searchTerm: 'A' });
|
||||
|
||||
let title: HTMLElement | null;
|
||||
|
||||
await vi.waitFor(async () => {
|
||||
title = screen.queryByText(`No extensions matching 'A' found`);
|
||||
expect(title).not.toBeInTheDocument();
|
||||
|
||||
// click on the catalog
|
||||
const catalogTab = screen.getByRole('button', { name: 'Catalog' });
|
||||
await fireEvent.click(catalogTab);
|
||||
await vi.waitFor(() => {
|
||||
expect(screen.getByRole('button', { name: 'Catalog' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
let title = screen.queryByText(`No extensions matching 'A' found`);
|
||||
expect(title).not.toBeInTheDocument();
|
||||
|
||||
// click on the catalog
|
||||
const catalogTab = screen.getByRole('button', { name: 'Catalog' });
|
||||
await fireEvent.click(catalogTab);
|
||||
|
||||
title = screen.queryByText(`No extensions matching 'A' found`);
|
||||
expect(title).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Expect to see empty screens on both pages', async () => {
|
||||
vi.mocked(window.getConfigurationValue).mockResolvedValue(true);
|
||||
catalogExtensionInfos.set([]);
|
||||
extensionInfos.set([]);
|
||||
|
||||
render(ExtensionList, { searchTerm: 'foo' });
|
||||
|
||||
let title: HTMLElement | null;
|
||||
|
||||
await vi.waitFor(() => {
|
||||
title = screen.getByText(`No extensions matching 'foo' found`);
|
||||
expect(title).toBeInTheDocument();
|
||||
expect(screen.getByText(`No extensions matching 'foo' found`)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// click on the catalog
|
||||
const catalogTab = screen.getByRole('button', { name: 'Catalog' });
|
||||
await fireEvent.click(catalogTab);
|
||||
|
||||
title = screen.getByText(`No extensions matching 'foo' found`);
|
||||
const title = screen.getByText(`No extensions matching 'foo' found`);
|
||||
expect(title).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Search extension page searches also description', async () => {
|
||||
vi.mocked(window.getConfigurationValue).mockResolvedValue(true);
|
||||
catalogExtensionInfos.set([aFakeExtension]);
|
||||
extensionInfos.set(combined);
|
||||
|
||||
render(ExtensionList, { searchTerm: 'bar' });
|
||||
|
||||
await vi.waitFor(() => {
|
||||
const myExtension1 = screen.getByRole('region', { name: 'idAInstalled' });
|
||||
expect(myExtension1).toBeInTheDocument();
|
||||
expect(screen.getByRole('region', { name: 'idAInstalled' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// second extension should not be there as only in catalog (not installed) and doesn't have "bar" in the description
|
||||
|
|
@ -204,25 +201,33 @@ test('Search extension page searches also description', async () => {
|
|||
cleanup();
|
||||
|
||||
// Change the search
|
||||
vi.mocked(window.getConfigurationValue).mockResolvedValue(true);
|
||||
render(ExtensionList, { searchTerm: 'foo' });
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(window.getConfigurationValue).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// The extension should not be there as it doesn't have "foo" in the description
|
||||
const myExtension2 = screen.queryByRole('region', { name: 'idAInstalled' });
|
||||
expect(myExtension2).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Search catalog page searches also description', async () => {
|
||||
vi.mocked(window.getConfigurationValue).mockResolvedValue(true);
|
||||
catalogExtensionInfos.set([aFakeExtension, bFakeExtension]);
|
||||
extensionInfos.set([]);
|
||||
|
||||
render(ExtensionList, { searchTerm: 'bar' });
|
||||
|
||||
await vi.waitFor(async () => {
|
||||
// Click on the catalog
|
||||
const catalogTab = screen.getByRole('button', { name: 'Catalog' });
|
||||
await fireEvent.click(catalogTab);
|
||||
await vi.waitFor(() => {
|
||||
expect(screen.getByRole('button', { name: 'Catalog' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Click on the catalog
|
||||
const catalogTab = screen.getByRole('button', { name: 'Catalog' });
|
||||
await fireEvent.click(catalogTab);
|
||||
|
||||
// Verify that the extension containing "bar" in the description is displayed
|
||||
const myExtension1 = screen.getByRole('group', { name: 'A Extension' });
|
||||
expect(myExtension1).toBeInTheDocument();
|
||||
|
|
@ -233,6 +238,10 @@ test('Search catalog page searches also description', async () => {
|
|||
});
|
||||
|
||||
test('Expect to see local extensions tab content', async () => {
|
||||
vi.mocked(window.getConfigurationValue).mockImplementation(async (key: string) => {
|
||||
// Return true for local extensions enabled, false for development mode to show empty screen
|
||||
return key === 'extensions.localExtensions.enabled';
|
||||
});
|
||||
catalogExtensionInfos.set([]);
|
||||
extensionInfos.set([]);
|
||||
|
||||
|
|
@ -240,29 +249,34 @@ test('Expect to see local extensions tab content', async () => {
|
|||
|
||||
render(ExtensionList);
|
||||
|
||||
await vi.waitFor(async () => {
|
||||
// select the local extensions tab
|
||||
const localModeTab = screen.getByRole('button', { name: 'Local Extensions' });
|
||||
await fireEvent.click(localModeTab);
|
||||
await vi.waitFor(() => {
|
||||
expect(screen.getByRole('button', { name: 'Local Extensions' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// select the local extensions tab
|
||||
const localModeTab = screen.getByRole('button', { name: 'Local Extensions' });
|
||||
await fireEvent.click(localModeTab);
|
||||
|
||||
// expect to see empty screen
|
||||
const emptyText = screen.getByText('Enable Preferences > Extensions > Development Mode to test local extensions');
|
||||
expect(emptyText).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Switching tabs keeps only terms in search term', async () => {
|
||||
vi.mocked(window.getConfigurationValue).mockResolvedValue(true);
|
||||
catalogExtensionInfos.set([aFakeExtension, bFakeExtension]);
|
||||
extensionInfos.set([]);
|
||||
|
||||
render(ExtensionList, { searchTerm: 'bar category:bar not:installed' });
|
||||
|
||||
await vi.waitFor(async () => {
|
||||
// Click on the catalog
|
||||
const catalogTab = screen.getByRole('button', { name: 'Catalog' });
|
||||
await fireEvent.click(catalogTab);
|
||||
await vi.waitFor(() => {
|
||||
expect(screen.getByRole('button', { name: 'Catalog' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Click on the catalog
|
||||
const catalogTab = screen.getByRole('button', { name: 'Catalog' });
|
||||
await fireEvent.click(catalogTab);
|
||||
|
||||
// Verify that the extension containing "bar" in the description is displayed (which is not in bar category and is installed)
|
||||
// meaning that `category:bar not:installed` has been removed from search term
|
||||
const myExtension1 = screen.getByRole('group', { name: 'A Extension' });
|
||||
|
|
@ -273,8 +287,7 @@ test('Expect install custom button is visible', async () => {
|
|||
render(ExtensionList);
|
||||
|
||||
await vi.waitFor(() => {
|
||||
const installCustomButton = screen.getByRole('button', { name: 'Install custom' });
|
||||
expect(installCustomButton).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Install custom' })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -284,7 +297,36 @@ test('Expect install custom button to not be visible if extensions.customExtensi
|
|||
render(ExtensionList);
|
||||
|
||||
await vi.waitFor(() => {
|
||||
const installCustomButton = screen.queryByRole('button', { name: 'Install custom' });
|
||||
expect(installCustomButton).not.toBeInTheDocument();
|
||||
expect(window.getConfigurationValue).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const installCustomButton = screen.queryByRole('button', { name: 'Install custom' });
|
||||
expect(installCustomButton).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Expect local extensions tab is visible', async () => {
|
||||
vi.mocked(window.getConfigurationValue).mockResolvedValue(undefined);
|
||||
catalogExtensionInfos.set([]);
|
||||
extensionInfos.set([]);
|
||||
|
||||
render(ExtensionList);
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(screen.getByRole('button', { name: 'Local Extensions' })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test('Expect local extensions tab to not be visible if extensions.localExtensions.enabled is false', async () => {
|
||||
vi.mocked(window.getConfigurationValue).mockResolvedValue(false);
|
||||
catalogExtensionInfos.set([]);
|
||||
extensionInfos.set([]);
|
||||
|
||||
render(ExtensionList);
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(window.getConfigurationValue).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const localExtensionsTab = screen.queryByRole('button', { name: 'Local Extensions' });
|
||||
expect(localExtensionsTab).not.toBeInTheDocument();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -28,6 +28,10 @@ let enableCustomExtensions = $derived(
|
|||
(await window.getConfigurationValue('extensions.customExtensions.enabled')) ?? true,
|
||||
);
|
||||
|
||||
let enableLocalExtensions = $derived(
|
||||
(await window.getConfigurationValue('extensions.localExtensions.enabled')) ?? true,
|
||||
);
|
||||
|
||||
const filteredInstalledExtensions: CombinedExtensionInfoUI[] = $derived(
|
||||
extensionsUtils.filterInstalledExtensions($combinedInstalledExtensions, searchTerm),
|
||||
);
|
||||
|
|
@ -104,12 +108,14 @@ function changeScreen(newScreen: 'installed' | 'catalog' | 'development'): void
|
|||
changeScreen('catalog');
|
||||
}}
|
||||
selected={screen === 'catalog'}>Catalog</Button>
|
||||
{#if enableLocalExtensions}
|
||||
<Button
|
||||
type="tab"
|
||||
on:click={(): void => {
|
||||
changeScreen('development');
|
||||
}}
|
||||
selected={screen === 'development'}>Local Extensions</Button>
|
||||
type="tab"
|
||||
on:click={(): void => {
|
||||
changeScreen('development');
|
||||
}}
|
||||
selected={screen === 'development'}>Local Extensions</Button>
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
{#snippet content()}
|
||||
|
|
@ -132,7 +138,7 @@ function changeScreen(newScreen: 'installed' | 'catalog' | 'development'): void
|
|||
on:resetFilter={(): string => (searchTerm = '')} />
|
||||
{/if}
|
||||
<CatalogExtensionList showEmptyScreen={!searchTerm} catalogExtensions={filteredCatalogExtensions} />
|
||||
{:else if screen === 'development'}
|
||||
{:else if screen === 'development' && enableLocalExtensions}
|
||||
<DevelopmentExtensionList />
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ import TabItem from '@theme/TabItem';
|
|||
| `extensions.customExtensions.enabled` | boolean | `true` | When disabled, the `Install custom...` button on the Extensions page will not appear. |
|
||||
| `extensions.ignoreBannerRecommendations` | boolean | `false` | Disable recommendation banners |
|
||||
| `extensions.ignoreRecommendations` | boolean | `false` | Disable extension recommendations |
|
||||
| `extensions.localExtensions.enabled` | boolean | `true` | Show the local extensions tab |
|
||||
| `feedback.dialog` | boolean | `true` | Show experimental feature feedback dialog |
|
||||
| `kubernetes.Kubeconfig` | string | `"~/.kube/config"` | Path to kubeconfig file |
|
||||
| `kubernetes.statesExperimental` | object | `null` | **EXPERIMENTAL:** New context monitoring. Example: `{}` |
|
||||
|
|
|
|||
Loading…
Reference in a new issue