From 7c80251b06e5d02d01c0d5b73123a2306a251f20 Mon Sep 17 00:00:00 2001 From: Robert Luby Date: Thu, 3 Sep 2026 10:19:36 +0200 Subject: [PATCH] feat: change function delete action to undeploy Rename the per-row delete icon button to an undeploy (power off) button with a custom confirmation message that clarifies the GitHub repository and code are preserved. Add an undeploy step to the setup guide and fix the guide title wording. Refs: SRVOCF-1071 Co-Authored-By: Claude Opus 4.8 --- e2e/use-cases/delete/function-delete.test.ts | 12 ++++---- e2e/use-cases/list/functions-list.test.ts | 2 +- .../en/plugin__console-functions-plugin.json | 6 ++-- .../components/FunctionTable.test.tsx | 12 ++++---- .../components/FunctionTable.tsx | 28 ++++++++++++------- .../components/SetupGuide.test.tsx | 2 ++ .../function-list/components/SetupGuide.tsx | 8 +++++- 7 files changed, 44 insertions(+), 26 deletions(-) diff --git a/e2e/use-cases/delete/function-delete.test.ts b/e2e/use-cases/delete/function-delete.test.ts index b0601cf0..eeeea24a 100644 --- a/e2e/use-cases/delete/function-delete.test.ts +++ b/e2e/use-cases/delete/function-delete.test.ts @@ -18,7 +18,7 @@ test.describe('Delete function', () => { await deleteFunction(page, PRESEEDED_FUNC_NAME, PRESEEDED_FUNC_NAMESPACE); }); - test('delete button is disabled for not deployed functions', async ({ page }) => { + test('undeploy button is disabled for not deployed functions', async ({ page }) => { await navigateToFunctionsList(page); const grid = page.getByRole('grid', { name: 'Functions' }); @@ -26,10 +26,10 @@ test.describe('Delete function', () => { const row = grid.locator('tbody tr').filter({ hasText: PRESEEDED_FUNC_NAME }); await expect(row.getByText('NotDeployed')).toBeVisible(); - await expect(row.getByRole('button', { name: 'Delete' })).toBeDisabled(); + await expect(row.getByRole('button', { name: 'Undeploy' })).toBeDisabled(); }); - test('delete button removes function from cluster', async ({ page }) => { + test('undeploy button removes function from cluster', async ({ page }) => { test.setTimeout(600_000); await test.step('make sure deletion target function is deployed in cluster', async () => { @@ -52,9 +52,9 @@ test.describe('Delete function', () => { await test.step('undeploy function', async () => { const grid = page.getByRole('grid', { name: 'Functions' }); const row = grid.locator(`tbody tr:has(td:text-is("${PRESEEDED_FUNC_NAME}"))`); - const deleteBtn = row.getByRole('button', { name: 'Delete' }); - await expect(deleteBtn).toBeEnabled({ timeout: 30_000 }); - await deleteBtn.click(); + const undeployBtn = row.getByRole('button', { name: 'Undeploy' }); + await expect(undeployBtn).toBeEnabled({ timeout: 30_000 }); + await undeployBtn.click(); const modal = page.getByRole('dialog'); await expect(modal).toBeVisible({ timeout: 5_000 }); diff --git a/e2e/use-cases/list/functions-list.test.ts b/e2e/use-cases/list/functions-list.test.ts index 3eff7999..e6d46d6b 100644 --- a/e2e/use-cases/list/functions-list.test.ts +++ b/e2e/use-cases/list/functions-list.test.ts @@ -39,7 +39,7 @@ test.describe('Functions list', () => { await expect(row).toBeVisible(); await expect(row.getByText('NotDeployed')).toBeVisible(); await expect(row.getByRole('button', { name: 'Edit' })).toBeEnabled(); - await expect(row.getByRole('button', { name: 'Delete' })).toBeDisabled(); + await expect(row.getByRole('button', { name: 'Undeploy' })).toBeDisabled(); }); await test.step('refresh re-fetches the list', async () => { diff --git a/locales/en/plugin__console-functions-plugin.json b/locales/en/plugin__console-functions-plugin.json index b608da89..c6289f50 100644 --- a/locales/en/plugin__console-functions-plugin.json +++ b/locales/en/plugin__console-functions-plugin.json @@ -8,6 +8,7 @@ "Branch": "Branch", "Cancel": "Cancel", "Click \"Create new function\", choose a runtime, and add any environment variables (plain values or from a secret). Submitting creates a GitHub repository, pushes the function scaffold, and starts a GitHub Actions workflow that deploys the function to your cluster. It appears here as \"NotDeployed\" until the workflow finishes, then the status changes to \"Running\".": "Click \"Create new function\", choose a runtime, and add any environment variables (plain values or from a secret). Submitting creates a GitHub repository, pushes the function scaffold, and starts a GitHub Actions workflow that deploys the function to your cluster. It appears here as \"NotDeployed\" until the workflow finishes, then the status changes to \"Running\".", + "Click the undeploy button in the Actions column to remove the running function and its Knative Service from the cluster. The GitHub repository and its code remain, so you can redeploy it later.": "Click the undeploy (power off) button in the Actions column to remove the running function and its Knative Service from the cluster. The GitHub repository and its code remain, so you can redeploy it later.", "Close": "Close", "Coming soon": "Coming soon", "ConfigMap": "ConfigMap", @@ -23,7 +24,6 @@ "Create function": "Create function", "Create new function": "Create new function", "Create or select a project for your function. If it needs credentials such as an API key, create a secret in that namespace so you can reference it as an environment variable.": "Create or select a project for your function. If it needs credentials such as an API key, create a secret in that namespace so you can reference it as an environment variable.", - "Delete": "Delete", "Edit": "Edit", "Edit and redeploy": "Edit and redeploy", "Edit function": "Edit function", @@ -62,12 +62,14 @@ "Select a namespace first": "Select a namespace first", "Select...": "Select...", "Serverless functions in your repository and deployed to your cluster. Manage lifecycle, monitor status, and scale on demand.": "Serverless functions in your repository and deployed to your cluster. Manage lifecycle, monitor status, and scale on demand.", - "Set up guide": "Set up guide", + "Setup guide": "Setup guide", "Sign in with GitHub": "Sign in with GitHub", "Start editing": "Start editing", "Status": "Status", "Stay": "Stay", "Undeploy": "Undeploy", + "Undeploy a function": "Undeploy a function", + "Undeploying removes the running function and its Knative Service from the cluster. The GitHub repository and its code remain, so you can redeploy it later.": "Undeploying removes the running function and its Knative Service from the cluster. The GitHub repository and its code remain, so you can redeploy it later.", "Unsaved changes": "Unsaved changes", "URL": "URL", "Value": "Value", diff --git a/src/pages/function-list/components/FunctionTable.test.tsx b/src/pages/function-list/components/FunctionTable.test.tsx index ee7b7a64..b9468ecd 100644 --- a/src/pages/function-list/components/FunctionTable.test.tsx +++ b/src/pages/function-list/components/FunctionTable.test.tsx @@ -22,7 +22,7 @@ vi.mock('@openshift-console/dynamic-plugin-sdk', () => ({ vi.mock('@patternfly/react-icons', () => ({ ExclamationTriangleIcon: () => 'WarningIcon', PencilAltIcon: () => 'EditIcon', - TrashIcon: () => 'DeleteIcon', + PowerOffIcon: () => 'UndeployIcon', })); const mockKnativeService = { @@ -203,7 +203,7 @@ describe('FunctionTable', () => { expect(onEdit).toHaveBeenCalledWith('my-repo'); }); - it('launches delete modal when delete button is clicked', async () => { + it('launches undeploy modal when undeploy button is clicked', async () => { const mockLauncher = vi.fn(); mockUseDeleteModal.mockReturnValue(mockLauncher); const user = userEvent.setup(); @@ -214,24 +214,24 @@ describe('FunctionTable', () => { , ); - await user.click(screen.getByRole('button', { name: 'Delete' })); + await user.click(screen.getByRole('button', { name: 'Undeploy' })); expect(mockLauncher).toHaveBeenCalled(); expect(mockUseDeleteModal).toHaveBeenCalledWith( mockKnativeService, undefined, - undefined, + expect.anything(), 'Undeploy', ); }); - it('disables delete button for NotDeployed functions', () => { + it('disables undeploy button for NotDeployed functions', () => { render( , ); - expect(screen.getByRole('button', { name: 'Delete' })).toBeDisabled(); + expect(screen.getByRole('button', { name: 'Undeploy' })).toBeDisabled(); }); it('disables edit button for cluster-only functions', () => { diff --git a/src/pages/function-list/components/FunctionTable.tsx b/src/pages/function-list/components/FunctionTable.tsx index 5edda32f..f8f4940c 100644 --- a/src/pages/function-list/components/FunctionTable.tsx +++ b/src/pages/function-list/components/FunctionTable.tsx @@ -7,8 +7,8 @@ import { SuccessStatus, useDeleteModal, } from '@openshift-console/dynamic-plugin-sdk'; -import { ActionList, ActionListItem, Button, Tooltip } from '@patternfly/react-core'; -import { ExclamationTriangleIcon, PencilAltIcon, TrashIcon } from '@patternfly/react-icons'; +import { ActionList, ActionListItem, Button, Content, Tooltip } from '@patternfly/react-core'; +import { ExclamationTriangleIcon, PencilAltIcon, PowerOffIcon } from '@patternfly/react-icons'; import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; import { useTranslation } from 'react-i18next'; import { FunctionSource, FunctionStatus } from '../../../common/types'; @@ -80,7 +80,7 @@ export function FunctionTable({ - + @@ -154,22 +154,30 @@ function EditActionButton({ return {button}; } -function DeleteActionButton({ mainResource }: { mainResource?: K8sResourceCommon }) { +function UndeployActionButton({ mainResource }: { mainResource?: K8sResourceCommon }) { const { t } = useTranslation('plugin__console-functions-plugin'); - const launchDelete = useDeleteModal( + const launchUndeploy = useDeleteModal( mainResource as K8sResourceCommon, undefined, - undefined, + + {t( + 'Undeploying removes the running function and its Knative Service from the cluster. The GitHub repository and its code remain, so you can redeploy it later.', + )} + , t('Undeploy'), ); - return ( + const button = (