From 7e577d877c50694828cf845af6b1a93e71d08e21 Mon Sep 17 00:00:00 2001 From: Charlie Drage Date: Fri, 12 Jul 2024 16:12:12 -0400 Subject: [PATCH] feat: add config maps and secrets to k8s (renderer code) (#8042) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add config maps and secrets to k8s (renderer code) ### What does this PR do? * Adds ConfigMaps and Secrets to Kubernetes * Ability to delete / view / edit both configmap and secrets ### Screenshot / video of UI ![Screenshot 2024-06-17 at 4 24 23 PM](https://github.com/containers/podman-desktop/assets/6422176/b348e49c-142e-429e-ad25-08a6fe389cf6) https://github.com/containers/podman-desktop/assets/6422176/41108199-7e2b-4cc2-81f6-2b267b1c887a ### What issues does this PR fix or reference? Closes https://github.com/containers/podman-desktop/issues/7342 Closes https://github.com/containers/podman-desktop/issues/7190 (its the last part of it) ### How to test this PR? - [X] Tests are covering the bug fix or the new feature 1. Create a ConfigMap and Secrets on your k8s cluster. 2. Delete / Edit / View them Signed-off-by: Charlie Drage * update based on review Signed-off-by: Charlie Drage * update namespace column and order in app navigation Signed-off-by: Charlie Drage * change search to lowercase Signed-off-by: Charlie Drage --------- Signed-off-by: Charlie Drage --- packages/renderer/src/App.svelte | 20 ++ packages/renderer/src/AppNavigation.spec.ts | 2 + packages/renderer/src/AppNavigation.svelte | 40 ++++ .../ConfigMapDetails.spec.ts | 57 ++++++ .../ConfigMapDetails.svelte | 96 +++++++++ .../ConfigMapDetailsSummary.spec.ts | 72 +++++++ .../ConfigMapDetailsSummary.svelte | 27 +++ .../ConfigMapSecretActions.spec.ts | 82 ++++++++ .../ConfigMapSecretActions.svelte | 41 ++++ .../ConfigMapSecretColumnActions.spec.ts | 41 ++++ .../ConfigMapSecretColumnActions.svelte | 8 + .../ConfigMapSecretColumnName.spec.ts | 88 ++++++++ .../ConfigMapSecretColumnName.svelte | 30 +++ .../ConfigMapSecretColumnStatus.spec.ts | 42 ++++ .../ConfigMapSecretColumnStatus.svelte | 10 + .../ConfigMapSecretColumnType.spec.ts | 63 ++++++ .../ConfigMapSecretColumnType.svelte | 24 +++ .../ConfigMapSecretEmptyScreen.spec.ts | 32 +++ .../ConfigMapSecretEmptyScreen.svelte | 10 + .../ConfigMapSecretList.spec.ts | 105 ++++++++++ .../ConfigMapSecretList.svelte | 190 ++++++++++++++++++ .../configmaps-secrets/ConfigMapSecretUI.ts | 27 +++ .../configmaps-secrets/SecretDetails.spec.ts | 57 ++++++ .../configmaps-secrets/SecretDetails.svelte | 93 +++++++++ .../SecretDetailsSummary.spec.ts | 71 +++++++ .../SecretDetailsSummary.svelte | 27 +++ .../configmap-secret-utils.spec.ts | 65 ++++++ .../configmap-secret-utils.ts | 55 +++++ .../kube/details/KubeConfigMapArtifact.svelte | 41 ++++ .../kube/details/KubeSecretArtifact.svelte | 35 ++++ .../src/stores/kubernetes-contexts-state.ts | 41 ++++ 31 files changed, 1592 insertions(+) create mode 100644 packages/renderer/src/lib/configmaps-secrets/ConfigMapDetails.spec.ts create mode 100644 packages/renderer/src/lib/configmaps-secrets/ConfigMapDetails.svelte create mode 100644 packages/renderer/src/lib/configmaps-secrets/ConfigMapDetailsSummary.spec.ts create mode 100644 packages/renderer/src/lib/configmaps-secrets/ConfigMapDetailsSummary.svelte create mode 100644 packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretActions.spec.ts create mode 100644 packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretActions.svelte create mode 100644 packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnActions.spec.ts create mode 100644 packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnActions.svelte create mode 100644 packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnName.spec.ts create mode 100644 packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnName.svelte create mode 100644 packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnStatus.spec.ts create mode 100644 packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnStatus.svelte create mode 100644 packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnType.spec.ts create mode 100644 packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnType.svelte create mode 100644 packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretEmptyScreen.spec.ts create mode 100644 packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretEmptyScreen.svelte create mode 100644 packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretList.spec.ts create mode 100644 packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretList.svelte create mode 100644 packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretUI.ts create mode 100644 packages/renderer/src/lib/configmaps-secrets/SecretDetails.spec.ts create mode 100644 packages/renderer/src/lib/configmaps-secrets/SecretDetails.svelte create mode 100644 packages/renderer/src/lib/configmaps-secrets/SecretDetailsSummary.spec.ts create mode 100644 packages/renderer/src/lib/configmaps-secrets/SecretDetailsSummary.svelte create mode 100644 packages/renderer/src/lib/configmaps-secrets/configmap-secret-utils.spec.ts create mode 100644 packages/renderer/src/lib/configmaps-secrets/configmap-secret-utils.ts create mode 100644 packages/renderer/src/lib/kube/details/KubeConfigMapArtifact.svelte create mode 100644 packages/renderer/src/lib/kube/details/KubeSecretArtifact.svelte diff --git a/packages/renderer/src/App.svelte b/packages/renderer/src/App.svelte index 44e789e09f6..d3e964c66f5 100644 --- a/packages/renderer/src/App.svelte +++ b/packages/renderer/src/App.svelte @@ -10,6 +10,9 @@ import type { NavigationRequest } from '/@api/navigation-request'; import AppNavigation from './AppNavigation.svelte'; import Appearance from './lib/appearance/Appearance.svelte'; import ComposeDetails from './lib/compose/ComposeDetails.svelte'; +import ConfigMapDetails from './lib/configmaps-secrets/ConfigMapDetails.svelte'; +import ConfigMapSecretList from './lib/configmaps-secrets/ConfigMapSecretList.svelte'; +import SecretDetails from './lib/configmaps-secrets/SecretDetails.svelte'; import ContainerDetails from './lib/container/ContainerDetails.svelte'; import ContainerExport from './lib/container/ContainerExport.svelte'; import ContainerList from './lib/container/ContainerList.svelte'; @@ -244,6 +247,23 @@ window.events?.receive('navigate', (navigationRequest: unknown) => { navigationHint="details"> + + + + + + + + + { vi.mocked(kubeContextStore).kubernetesCurrentContextIngresses = readable([]); vi.mocked(kubeContextStore).kubernetesCurrentContextRoutes = readable([]); vi.mocked(kubeContextStore).kubernetesCurrentContextNodes = readable([]); + vi.mocked(kubeContextStore).kubernetesCurrentContextConfigMaps = readable([]); + vi.mocked(kubeContextStore).kubernetesCurrentContextSecrets = readable([]); vi.mocked(kubeContextStore).kubernetesCurrentContextPersistentVolumeClaims = readable([]); render(AppNavigation, { diff --git a/packages/renderer/src/AppNavigation.svelte b/packages/renderer/src/AppNavigation.svelte index d4d6fe5f2c0..8bdc1f633ba 100644 --- a/packages/renderer/src/AppNavigation.svelte +++ b/packages/renderer/src/AppNavigation.svelte @@ -13,6 +13,7 @@ import type { ImageInfo } from '/@api/image-info'; import { CommandRegistry } from './lib/CommandRegistry'; import NewContentOnDashboardBadge from './lib/dashboard/NewContentOnDashboardBadge.svelte'; import { ImageUtils } from './lib/image/image-utils'; +import ConfigMapSecretIcon from './lib/images/ConfigMapSecretIcon.svelte'; import DashboardIcon from './lib/images/DashboardIcon.svelte'; import DeploymentIcon from './lib/images/DeploymentIcon.svelte'; import ExtensionIcon from './lib/images/ExtensionIcon.svelte'; @@ -34,11 +35,13 @@ import { contributions } from './stores/contribs'; import { imagesInfos } from './stores/images'; import { kubernetesContexts } from './stores/kubernetes-contexts'; import { + kubernetesCurrentContextConfigMaps, kubernetesCurrentContextDeployments, kubernetesCurrentContextIngresses, kubernetesCurrentContextNodes, kubernetesCurrentContextPersistentVolumeClaims, kubernetesCurrentContextRoutes, + kubernetesCurrentContextSecrets, kubernetesCurrentContextServices, } from './stores/kubernetes-contexts-state'; import { podsInfos } from './stores/pods'; @@ -55,12 +58,17 @@ let persistentVolumeClaimsSubscribe: Unsubscriber; let servicesSubscribe: Unsubscriber; let ingressesSubscribe: Unsubscriber; let routesSubscribe: Unsubscriber; +let configmapsSubscribe: Unsubscriber; +let secretsSubscribe: Unsubscriber; let combinedInstalledExtensionsSubscribe: Unsubscriber; let podCount = ''; let containerCount = ''; let imageCount = ''; let volumeCount = ''; +let configmapsCount = 0; +let secretsCount = 0; +let configmapSecretsCount = ''; let persistentVolumeClaimsCount = ''; let contextCount = 0; let deploymentCount = ''; @@ -145,6 +153,15 @@ onMount(async () => { routesCount = value.length; updateIngressesRoutesCount(ingressesCount + routesCount); }); + + configmapsSubscribe = kubernetesCurrentContextConfigMaps.subscribe(value => { + configmapsCount = value.length; + updateConfigMapSecretsCount(configmapsCount + secretsCount); + }); + secretsSubscribe = kubernetesCurrentContextSecrets.subscribe(value => { + secretsCount = value.length; + updateConfigMapSecretsCount(configmapsCount + secretsCount); + }); contextsSubscribe = kubernetesContexts.subscribe(value => { contextCount = value.length; }); @@ -185,8 +202,16 @@ onDestroy(() => { if (servicesSubscribe) { servicesSubscribe(); } + if (configmapsSubscribe) { + configmapsSubscribe(); + } + if (secretsSubscribe) { + secretsSubscribe(); + } ingressesSubscribe?.(); routesSubscribe?.(); + configmapsSubscribe?.(); + secretsSubscribe?.(); combinedInstalledExtensionsSubscribe?.(); }); @@ -198,6 +223,14 @@ function updateIngressesRoutesCount(count: number) { } } +function updateConfigMapSecretsCount(count: number) { + if (count > 0) { + configmapSecretsCount = ' (' + count + ')'; + } else { + configmapSecretsCount = ''; + } +} + function clickSettings(b: boolean) { if (b) { exitSettingsCallback(); @@ -262,6 +295,13 @@ export let meta: TinroRouteMeta; bind:meta="{meta}"> + + + {/if} diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapDetails.spec.ts b/packages/renderer/src/lib/configmaps-secrets/ConfigMapDetails.spec.ts new file mode 100644 index 00000000000..369a0540b33 --- /dev/null +++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapDetails.spec.ts @@ -0,0 +1,57 @@ +/********************************************************************** + * 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 '@testing-library/jest-dom/vitest'; + +import type { KubernetesObject, V1ConfigMap } from '@kubernetes/client-node'; +import { render, screen } from '@testing-library/svelte'; +import { writable } from 'svelte/store'; +import { beforeAll, expect, test, vi } from 'vitest'; + +import * as kubeContextStore from '/@/stores/kubernetes-contexts-state'; + +import ConfigMapDetails from './ConfigMapDetails.svelte'; + +const configMap: V1ConfigMap = { + metadata: { + name: 'my-configmap', + namespace: 'default', + }, + data: {}, +}; + +vi.mock('/@/stores/kubernetes-contexts-state', async () => { + return { + kubernetesCurrentContextConfigMaps: vi.fn(), + }; +}); + +beforeAll(() => { + (window as any).kubernetesReadNamespacedConfigMap = vi.fn(); +}); + +test('Confirm renders configmap details', async () => { + // mock object store + const configMaps = writable([configMap]); + vi.mocked(kubeContextStore).kubernetesCurrentContextConfigMaps = configMaps; + + render(ConfigMapDetails, { name: 'my-configmap', namespace: 'default' }); + + expect(screen.getByText('my-configmap')).toBeInTheDocument(); + expect(screen.getByText('default')).toBeInTheDocument(); +}); diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapDetails.svelte b/packages/renderer/src/lib/configmaps-secrets/ConfigMapDetails.svelte new file mode 100644 index 00000000000..c2e599ba28c --- /dev/null +++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapDetails.svelte @@ -0,0 +1,96 @@ + + +{#if configMap} + + + + + +
+ +
+ + + + + + + + + + + + + + + + +
+{/if} diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapDetailsSummary.spec.ts b/packages/renderer/src/lib/configmaps-secrets/ConfigMapDetailsSummary.spec.ts new file mode 100644 index 00000000000..db2aa78281a --- /dev/null +++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapDetailsSummary.spec.ts @@ -0,0 +1,72 @@ +/********************************************************************** + * 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 '@testing-library/jest-dom/vitest'; + +import type { V1ConfigMap } from '@kubernetes/client-node'; +import { render, screen } from '@testing-library/svelte'; +import { beforeEach, expect, test, vi } from 'vitest'; + +import ConfigMapDetailsSummary from './ConfigMapDetailsSummary.svelte'; + +const configMap: V1ConfigMap = { + metadata: { + name: 'my-configmap', + namespace: 'default', + }, + data: { + key1: 'value1', + key2: 'value2', + }, + binaryData: { + key3: 'value3', + }, +}; + +const kubeError = 'Error retrieving node details'; + +beforeEach(() => { + vi.resetAllMocks(); + vi.clearAllMocks(); +}); + +test('Confirm renders configmap details summary', async () => { + render(ConfigMapDetailsSummary, { configMap }); + + expect(screen.getByText('my-configmap')).toBeInTheDocument(); + expect(screen.getByText('default')).toBeInTheDocument(); + expect(screen.getByText('key1')).toBeInTheDocument(); + expect(screen.getByText('value1')).toBeInTheDocument(); + expect(screen.getByText('key2')).toBeInTheDocument(); + expect(screen.getByText('value2')).toBeInTheDocument(); + // binary data just shows the key and size, not the data + expect(screen.getByText('key3: 6 bytes')).toBeInTheDocument(); +}); + +test('Expect to show loading if there is no data present', async () => { + render(ConfigMapDetailsSummary, {}); + + const loadingMessage = screen.getByText('Loading ...'); + expect(loadingMessage).toBeInTheDocument(); +}); + +test('Expect to show error message when there is a kube error', async () => { + render(ConfigMapDetailsSummary, { configMap, kubeError: kubeError }); + + const errorMessage = screen.getByText(kubeError); + expect(errorMessage).toBeInTheDocument(); +}); diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapDetailsSummary.svelte b/packages/renderer/src/lib/configmaps-secrets/ConfigMapDetailsSummary.svelte new file mode 100644 index 00000000000..55844bf7b2c --- /dev/null +++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapDetailsSummary.svelte @@ -0,0 +1,27 @@ + + + +{#if kubeError} + +{/if} + + + {#if configMap} + + + {:else} +

Loading ...

+ {/if} +
diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretActions.spec.ts b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretActions.spec.ts new file mode 100644 index 00000000000..d64273da037 --- /dev/null +++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretActions.spec.ts @@ -0,0 +1,82 @@ +/********************************************************************** + * 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 '@testing-library/jest-dom/vitest'; + +import { fireEvent, render, screen, waitFor } from '@testing-library/svelte'; +import { afterEach, beforeEach, expect, test, vi } from 'vitest'; + +import ConfigMapSecretActions from './ConfigMapSecretActions.svelte'; +import type { ConfigMapSecretUI } from './ConfigMapSecretUI'; + +const updateMock = vi.fn(); +const deleteMock = vi.fn(); +const showMessageBoxMock = vi.fn(); + +const fakeConfigMap: ConfigMapSecretUI = { + name: 'my-configmap', + namespace: '', + selected: false, + type: 'ConfigMap', + status: '', + keys: [], +}; + +const fakeSecret: ConfigMapSecretUI = { + name: 'my-secret', + namespace: '', + selected: false, + type: 'Secret', + status: '', + keys: [], +}; + +beforeEach(() => { + (window as any).showMessageBox = showMessageBoxMock; + (window as any).kubernetesDeleteConfigMap = deleteMock; + (window as any).kubernetesDeleteSecret = deleteMock; +}); + +afterEach(() => { + vi.resetAllMocks(); + vi.clearAllMocks(); +}); + +test('Expect no error when deleting configmap', async () => { + showMessageBoxMock.mockResolvedValue({ response: 0 }); + render(ConfigMapSecretActions, { configMapSecret: fakeConfigMap, onUpdate: updateMock }); + + // click on delete button + const deleteButton = screen.getByRole('button', { name: 'Delete ConfigMap' }); + await fireEvent.click(deleteButton); + + // wait for the delete function to be called + await waitFor(() => expect(deleteMock).toHaveBeenCalled()); +}); + +test('Expect no error when deleting secret', async () => { + showMessageBoxMock.mockResolvedValue({ response: 0 }); + render(ConfigMapSecretActions, { configMapSecret: fakeSecret, onUpdate: updateMock }); + + // click on delete button + const deleteButton = screen.getByRole('button', { name: 'Delete Secret' }); + await fireEvent.click(deleteButton); + + // wait for the delete function to be called + await waitFor(() => expect(deleteMock).toHaveBeenCalled()); +}); diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretActions.svelte b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretActions.svelte new file mode 100644 index 00000000000..2e9f6978d7c --- /dev/null +++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretActions.svelte @@ -0,0 +1,41 @@ + + + diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnActions.spec.ts b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnActions.spec.ts new file mode 100644 index 00000000000..67cb05b926e --- /dev/null +++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnActions.spec.ts @@ -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 '@testing-library/jest-dom/vitest'; + +import { render, screen } from '@testing-library/svelte'; +import { expect, test } from 'vitest'; + +import ConfigMapSecretColumnActions from './ConfigMapSecretColumnActions.svelte'; +import type { ConfigMapSecretUI } from './ConfigMapSecretUI'; + +test('Expect action buttons', async () => { + const configMap: ConfigMapSecretUI = { + name: 'my-configmap', + namespace: '', + selected: false, + type: 'ConfigMap', + status: '', + keys: [], + }; + + render(ConfigMapSecretColumnActions, { object: configMap }); + + const buttons = await screen.findAllByRole('button'); + expect(buttons).toHaveLength(1); +}); diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnActions.svelte b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnActions.svelte new file mode 100644 index 00000000000..46894d1f2ed --- /dev/null +++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnActions.svelte @@ -0,0 +1,8 @@ + + + diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnName.spec.ts b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnName.spec.ts new file mode 100644 index 00000000000..5660c770886 --- /dev/null +++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnName.spec.ts @@ -0,0 +1,88 @@ +/********************************************************************** + * 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 '@testing-library/jest-dom/vitest'; + +import { fireEvent, render, screen } from '@testing-library/svelte'; +import { router } from 'tinro'; +import { expect, test, vi } from 'vitest'; + +import ConfigMapSecretColumnName from './ConfigMapSecretColumnName.svelte'; +import type { ConfigMapSecretUI } from './ConfigMapSecretUI'; + +const configMap: ConfigMapSecretUI = { + name: 'my-configmap', + namespace: 'default', + selected: false, + type: 'ConfigMap', + status: '', + keys: [], +}; + +const secret: ConfigMapSecretUI = { + name: 'my-secret', + namespace: 'default', + selected: false, + type: 'Secret', + status: '', + keys: [], +}; + +test('Expect simple column styling', async () => { + render(ConfigMapSecretColumnName, { object: configMap }); + + const text = screen.getByText(configMap.name); + expect(text).toBeInTheDocument(); + expect(text).toHaveClass('text-sm'); + expect(text).toHaveClass('text-[var(--pd-table-body-text-highlight)]'); +}); + +test('Configmap: Expect clicking works', async () => { + render(ConfigMapSecretColumnName, { object: configMap }); + + const text = screen.getByText(configMap.name); + expect(text).toBeInTheDocument(); + + // test click + const routerGotoSpy = vi.spyOn(router, 'goto'); + + fireEvent.click(text); + + expect(routerGotoSpy).toBeCalledWith('/configmapsSecrets/configmap/my-configmap/default/summary'); +}); + +test('Secret: Expect clicking works', async () => { + render(ConfigMapSecretColumnName, { object: secret }); + + const text = screen.getByText(secret.name); + expect(text).toBeInTheDocument(); + + // test click + const routerGotoSpy = vi.spyOn(router, 'goto'); + + fireEvent.click(text); + + expect(routerGotoSpy).toBeCalledWith('/configmapsSecrets/secret/my-secret/default/summary'); +}); + +test('Expect namespace in column', async () => { + render(ConfigMapSecretColumnName, { object: configMap }); + + const text = screen.getByText(configMap.namespace); + expect(text).toBeInTheDocument(); +}); diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnName.svelte b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnName.svelte new file mode 100644 index 00000000000..c5ae9ab308b --- /dev/null +++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnName.svelte @@ -0,0 +1,30 @@ + + + diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnStatus.spec.ts b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnStatus.spec.ts new file mode 100644 index 00000000000..39eeb39fc24 --- /dev/null +++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnStatus.spec.ts @@ -0,0 +1,42 @@ +/********************************************************************** + * 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 '@testing-library/jest-dom/vitest'; + +import { render, screen } from '@testing-library/svelte'; +import { expect, test } from 'vitest'; + +import ConfigMapSecretColumnStatus from './ConfigMapSecretColumnStatus.svelte'; +import type { ConfigMapSecretUI } from './ConfigMapSecretUI'; + +test('Expect status styling for running', async () => { + const configMap: ConfigMapSecretUI = { + name: 'my-configmap', + namespace: '', + selected: false, + type: 'ConfigMap', + status: 'RUNNING', + keys: [], + }; + + render(ConfigMapSecretColumnStatus, { object: configMap }); + + const text = screen.getByRole('status'); + expect(text).toBeInTheDocument(); + expect(text).toHaveClass('bg-[var(--pd-status-running)]'); +}); diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnStatus.svelte b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnStatus.svelte new file mode 100644 index 00000000000..d26e7e23d96 --- /dev/null +++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnStatus.svelte @@ -0,0 +1,10 @@ + + + diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnType.spec.ts b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnType.spec.ts new file mode 100644 index 00000000000..b3c48c3b15c --- /dev/null +++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnType.spec.ts @@ -0,0 +1,63 @@ +/********************************************************************** + * 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 '@testing-library/jest-dom/vitest'; + +import { render, screen } from '@testing-library/svelte'; +import { expect, test } from 'vitest'; + +import ConfigMapSecretColumnType from './ConfigMapSecretColumnType.svelte'; +import type { ConfigMapSecretUI } from './ConfigMapSecretUI'; + +test('Expect type display for ConfigMap', async () => { + const configMap: ConfigMapSecretUI = { + name: 'my-configmap', + namespace: '', + selected: false, + type: 'ConfigMap', + status: '', + keys: [], + }; + + render(ConfigMapSecretColumnType, { object: configMap }); + + const text = screen.getByText('ConfigMap'); + expect(text).toBeInTheDocument(); + const svg = text.parentElement?.querySelector('svg'); + expect(svg).toBeInTheDocument(); + expect(svg).toHaveClass('text-[var(--pd-status-running)]'); +}); + +test('Expect type display for Secret', async () => { + const secret: ConfigMapSecretUI = { + name: 'my-secret', + namespace: '', + selected: false, + type: 'Secret', + status: '', + keys: [], + }; + + render(ConfigMapSecretColumnType, { object: secret }); + + const text = screen.getByText('Secret'); + expect(text).toBeInTheDocument(); + const svg = text.parentElement?.querySelector('svg'); + expect(svg).toBeInTheDocument(); + expect(svg).toHaveClass('text-[var(--pd-status-running)]'); +}); diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnType.svelte b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnType.svelte new file mode 100644 index 00000000000..afaba9b940a --- /dev/null +++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnType.svelte @@ -0,0 +1,24 @@ + + +
+ +
diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretEmptyScreen.spec.ts b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretEmptyScreen.spec.ts new file mode 100644 index 00000000000..b2e8067dc83 --- /dev/null +++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretEmptyScreen.spec.ts @@ -0,0 +1,32 @@ +/********************************************************************** + * 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 + ***********************************************************************/ + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import '@testing-library/jest-dom/vitest'; + +import { render, screen } from '@testing-library/svelte'; +import { expect, test } from 'vitest'; + +import ConfigMapSecretEmptyScreen from './ConfigMapSecretEmptyScreen.svelte'; + +test('Expect configmap empty screen', async () => { + render(ConfigMapSecretEmptyScreen); + const noNodes = screen.getByRole('heading', { name: 'No configmaps or secrets' }); + expect(noNodes).toBeInTheDocument(); +}); diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretEmptyScreen.svelte b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretEmptyScreen.svelte new file mode 100644 index 00000000000..213f20a02b2 --- /dev/null +++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretEmptyScreen.svelte @@ -0,0 +1,10 @@ + + + diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretList.spec.ts b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretList.spec.ts new file mode 100644 index 00000000000..e4ef4315bb9 --- /dev/null +++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretList.spec.ts @@ -0,0 +1,105 @@ +/********************************************************************** + * 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 + ***********************************************************************/ + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import '@testing-library/jest-dom/vitest'; + +import type { V1ConfigMap, V1Secret } from '@kubernetes/client-node'; +import { render, screen } from '@testing-library/svelte'; +/* eslint-disable import/no-duplicates */ +import { tick } from 'svelte'; +import { get } from 'svelte/store'; +/* eslint-enable import/no-duplicates */ +import { beforeAll, beforeEach, expect, test, vi } from 'vitest'; + +import { kubernetesCurrentContextConfigMaps } from '/@/stores/kubernetes-contexts-state'; + +import ConfigMapSecretList from './ConfigMapSecretList.svelte'; + +const kubernetesRegisterGetCurrentContextResourcesMock = vi.fn(); + +beforeAll(() => { + (window as any).kubernetesRegisterGetCurrentContextResources = kubernetesRegisterGetCurrentContextResourcesMock; +}); + +beforeEach(() => { + vi.resetAllMocks(); + vi.clearAllMocks(); + (window as any).kubernetesGetContextsGeneralState = () => Promise.resolve(new Map()); + (window as any).kubernetesGetCurrentContextGeneralState = () => Promise.resolve({}); + (window as any).window.kubernetesUnregisterGetCurrentContextResources = () => Promise.resolve(undefined); +}); + +async function waitRender(customProperties: object): Promise { + render(ConfigMapSecretList, { ...customProperties }); + await tick(); +} + +test('Expect configmap empty screen', async () => { + kubernetesRegisterGetCurrentContextResourcesMock.mockResolvedValue([]); + render(ConfigMapSecretList); + const noNodes = screen.getByRole('heading', { name: 'No configmaps or secrets' }); + expect(noNodes).toBeInTheDocument(); +}); + +test('Expect configmap and secrets list', async () => { + const configMap: V1ConfigMap = { + metadata: { + name: 'my-configmap', + namespace: 'my-namespace', + }, + data: { + key1: 'value1', + key2: 'value2', + }, + }; + + const secret: V1Secret = { + metadata: { + name: 'my-secret', + namespace: 'my-namespace', + }, + data: { + secretkey1: 'value1', + secretkey2: 'value2', + }, + type: 'Opaque', + }; + + kubernetesRegisterGetCurrentContextResourcesMock.mockResolvedValue([configMap, secret]); + + // wait while store is populated + while (get(kubernetesCurrentContextConfigMaps).length === 0) { + await new Promise(resolve => setTimeout(resolve, 500)); + } + + await waitRender({}); + + const configMapName = screen.getByRole('cell', { name: 'my-configmap my-namespace' }); + expect(configMapName).toBeInTheDocument(); + // Expect ConfigMap type + const configMapType = screen.getByRole('cell', { name: 'ConfigMap' }); + expect(configMapType).toBeInTheDocument(); + + const secretName = screen.getByRole('cell', { name: 'my-secret my-namespace' }); + expect(secretName).toBeInTheDocument(); + // Expect Opaque type + const secretType = screen.getByRole('cell', { name: 'Opaque' }); + expect(secretType).toBeInTheDocument(); +}); diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretList.svelte b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretList.svelte new file mode 100644 index 00000000000..4079ccbfb10 --- /dev/null +++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretList.svelte @@ -0,0 +1,190 @@ + + + + + + + + + {#if selectedItemsNumber > 0} +