Skip to content

Commit

Permalink
feat: Add protected check and refactor actions (#606)
Browse files Browse the repository at this point in the history
  • Loading branch information
callmevladik authored and MykolaMarusenko committed Feb 6, 2025
1 parent 7534720 commit bae2f65
Show file tree
Hide file tree
Showing 12 changed files with 255 additions and 114 deletions.
9 changes: 5 additions & 4 deletions src/constants/resourceActions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export enum RESOURCE_ACTIONS {
EDIT = 'edit',
DELETE = 'delete',
}
export const RESOURCE_ACTIONS = {
CREATE: 'create',
EDIT: 'edit',
DELETE: 'delete',
} as const;
1 change: 1 addition & 0 deletions src/k8s/common/labels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const RESOURCE_LABEL_SELECTOR_PROTECTED = 'app.edp.epam.com/deletion-protection';
39 changes: 0 additions & 39 deletions src/utils/actions/createKubeAction/index.ts

This file was deleted.

85 changes: 85 additions & 0 deletions src/utils/actions/createResourceAction/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { KubeObjectInterface } from '@kinvolk/headlamp-plugin/lib/lib/k8s/cluster';
import { RESOURCE_ACTIONS } from '../../../constants/resourceActions';
import { RESOURCE_LABEL_SELECTOR_PROTECTED } from '../../../k8s/common/labels';
import { createResourceAction } from './index';

describe('createResourceAction', () => {
const mockItem: KubeObjectInterface = {
metadata: {
labels: {},
},
} as KubeObjectInterface;

it('should create an action with the given parameters', () => {
const action = createResourceAction({
item: mockItem,
type: RESOURCE_ACTIONS.EDIT,
label: 'Edit',
callback: jest.fn(),
icon: 'edit-icon',
isTextButton: true,
});

expect(action.name).toBe(RESOURCE_ACTIONS.EDIT);
expect(action.label).toBe('Edit');
expect(action.icon).toBe('edit-icon');
expect(action.disabled.status).toBe(false);
expect(action.isTextButton).toBe(true);
});

it('should disable the action if the resource is protected and the action is delete', () => {
const protectedItem = {
...mockItem,
metadata: {
labels: {
[RESOURCE_LABEL_SELECTOR_PROTECTED]: 'true',
},
},
} as unknown as KubeObjectInterface;

const action = createResourceAction({
item: protectedItem,
type: RESOURCE_ACTIONS.DELETE,
label: 'Delete',
callback: jest.fn(),
});

expect(action.disabled.status).toBe(true);
expect(action.disabled.reason).toBe('This resource is protected from deletion.');
});

it('should use the provided disabled status and reason if the resource is not protected', () => {
const action = createResourceAction({
item: mockItem,
type: RESOURCE_ACTIONS.DELETE,
label: 'Delete',
callback: jest.fn(),
disabled: {
status: true,
reason: 'Custom reason',
},
});

expect(action.disabled.status).toBe(true);
expect(action.disabled.reason).toBe('Custom reason');
});

it('should call the callback function when the action is executed', () => {
const mockCallback = jest.fn();
const action = createResourceAction({
item: mockItem,
type: RESOURCE_ACTIONS.EDIT,
label: 'Edit',
callback: mockCallback,
});

const mockEvent = {
stopPropagation: jest.fn(),
};

action.action(mockEvent as any);

expect(mockEvent.stopPropagation).toHaveBeenCalled();
expect(mockCallback).toHaveBeenCalledWith(mockItem);
});
});
53 changes: 53 additions & 0 deletions src/utils/actions/createResourceAction/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { KubeObjectInterface } from '@kinvolk/headlamp-plugin/lib/lib/k8s/cluster';
import { RESOURCE_ACTIONS } from '../../../constants/resourceActions';
import { RESOURCE_LABEL_SELECTOR_PROTECTED } from '../../../k8s/common/labels';
import { KubeObjectAction } from '../../../types/actions';
import { ValueOf } from '../../../types/global';

export const createResourceAction = <Item extends KubeObjectInterface>({
item,
type,
label,
callback,
disabled = {
status: false,
reason: 'You cannot perform this action.',
},
icon,
isTextButton,
}: {
item: Item;
type: ValueOf<typeof RESOURCE_ACTIONS>;
label: string;
callback?: (item: Item) => void;
disabled?: {
status: boolean;
reason?: string;
};
icon?: string;
isTextButton?: boolean;
}): KubeObjectAction => {
const isProtected = item?.metadata?.labels?.[RESOURCE_LABEL_SELECTOR_PROTECTED] === 'true';
const isDeleteAction = type === RESOURCE_ACTIONS.DELETE;

return {
name: type,
label,
icon,
disabled:
isProtected && isDeleteAction
? {
status: true,
reason: 'This resource is protected from deletion.',
}
: {
status: disabled.status,
reason: disabled.status && disabled.reason,
},
action: (e) => {
e.stopPropagation();
callback(item);
},
isTextButton,
};
};
19 changes: 12 additions & 7 deletions src/widgets/CDPipelineActionsMenu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { RESOURCE_ACTIONS } from '../../constants/resourceActions';
import { ICONS } from '../../icons/iconify-icons-mapping';
import { CDPipelineKubeObject } from '../../k8s/groups/EDP/CDPipeline';
import { useDialogContext } from '../../providers/Dialog/hooks';
import { createKubeAction } from '../../utils/actions/createKubeAction';
import { createResourceAction } from '../../utils/actions/createResourceAction';
import { capitalizeFirstLetter } from '../../utils/format/capitalizeFirstLetter';
import { DeleteKubeObjectDialog } from '../dialogs/DeleteKubeObject';
import { ManageCDPipelineDialog } from '../dialogs/ManageCDPipeline';
import { CDPipelineActionsMenuProps } from './types';
Expand All @@ -27,28 +28,32 @@ export const CDPipelineActionsMenu = ({
}

return [
createKubeAction({
name: RESOURCE_ACTIONS.EDIT,
createResourceAction({
type: RESOURCE_ACTIONS.EDIT,
label: capitalizeFirstLetter(RESOURCE_ACTIONS.EDIT),
item: CDPipelineData,
icon: ICONS.PENCIL,
disabled: {
status: !permissions?.update?.CDPipeline.allowed,
reason: permissions?.update?.CDPipeline.reason,
},
action: () => {
callback: (CDPipelineData) => {
if (variant === ACTION_MENU_TYPES.MENU && handleCloseResourceActionListMenu) {
handleCloseResourceActionListMenu();
}
setDialog(ManageCDPipelineDialog, { CDPipelineData });
},
}),
createKubeAction({
name: RESOURCE_ACTIONS.DELETE,
createResourceAction({
type: RESOURCE_ACTIONS.DELETE,
label: capitalizeFirstLetter(RESOURCE_ACTIONS.DELETE),
item: CDPipelineData,
icon: ICONS.BUCKET,
disabled: {
status: !permissions?.delete?.CDPipeline.allowed,
reason: permissions?.delete?.CDPipeline.reason,
},
action: () => {
callback: (CDPipelineData) => {
if (variant === ACTION_MENU_TYPES.MENU && handleCloseResourceActionListMenu) {
handleCloseResourceActionListMenu();
}
Expand Down
18 changes: 11 additions & 7 deletions src/widgets/CodebaseActionsMenu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { ICONS } from '../../icons/iconify-icons-mapping';
import { CodebaseKubeObject } from '../../k8s/groups/EDP/Codebase';
import { routeCDPipelineDetails } from '../../pages/cdpipeline-details/route';
import { useDialogContext } from '../../providers/Dialog/hooks';
import { createKubeAction } from '../../utils/actions/createKubeAction';
import { createResourceAction } from '../../utils/actions/createResourceAction';
import { capitalizeFirstLetter } from '../../utils/format/capitalizeFirstLetter';
import { DeleteKubeObjectDialog } from '../dialogs/DeleteKubeObject';
import { ManageCodebaseDialog } from '../dialogs/ManageCodebase';
Expand Down Expand Up @@ -77,29 +77,33 @@ export const CodebaseActionsMenu = ({
}

return [
createKubeAction({
name: RESOURCE_ACTIONS.EDIT,
createResourceAction({
type: RESOURCE_ACTIONS.EDIT,
label: capitalizeFirstLetter(RESOURCE_ACTIONS.EDIT),
item: codebaseData,
icon: ICONS.PENCIL,
disabled: {
status: !permissions?.update?.Codebase.allowed,
reason: permissions?.update?.Codebase.reason,
},
action: () => {
callback: (codebaseData) => {
if (variant === ACTION_MENU_TYPES.MENU && handleCloseResourceActionListMenu) {
handleCloseResourceActionListMenu();
}

setDialog(ManageCodebaseDialog, { codebaseData });
},
}),
createKubeAction({
name: RESOURCE_ACTIONS.DELETE,
createResourceAction({
type: RESOURCE_ACTIONS.DELETE,
label: capitalizeFirstLetter(RESOURCE_ACTIONS.DELETE),
item: codebaseData,
icon: ICONS.BUCKET,
disabled: {
status: !permissions?.delete?.Codebase.allowed,
reason: permissions?.delete?.Codebase.reason,
},
action: () => {
callback: (codebaseData) => {
if (variant === ACTION_MENU_TYPES.MENU && handleCloseResourceActionListMenu) {
handleCloseResourceActionListMenu();
}
Expand Down
21 changes: 13 additions & 8 deletions src/widgets/CodebaseBranchActionsMenu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { RESOURCE_ACTIONS } from '../../constants/resourceActions';
import { ICONS } from '../../icons/iconify-icons-mapping';
import { CodebaseBranchKubeObject } from '../../k8s/groups/EDP/CodebaseBranch';
import { useDialogContext as useNewDialogContext } from '../../providers/Dialog/hooks';
import { createKubeAction } from '../../utils/actions/createKubeAction';
import { createResourceAction } from '../../utils/actions/createResourceAction';
import { capitalizeFirstLetter } from '../../utils/format/capitalizeFirstLetter';
import { DeleteKubeObjectDialog } from '../dialogs/DeleteKubeObject';
import { ManageCodebaseBranchDialog } from '../dialogs/ManageCodebaseBranch';
import { CodebaseBranchCDPipelineConflictError } from './components/CodebaseBranchCDPipelineConflictError';
Expand Down Expand Up @@ -53,14 +54,16 @@ export const CodebaseBranchActionsMenu = ({
}

return [
createKubeAction({
name: RESOURCE_ACTIONS.EDIT,
createResourceAction({
type: RESOURCE_ACTIONS.EDIT,
label: capitalizeFirstLetter(RESOURCE_ACTIONS.EDIT),
item: branch,
icon: ICONS.PENCIL,
disabled: {
status: !permissions?.update?.CodebaseBranch.allowed,
reason: permissions?.update?.CodebaseBranch.reason,
},
icon: ICONS.PENCIL,
action: () => {
callback: (branch) => {
if (variant === ACTION_MENU_TYPES.MENU && handleCloseResourceActionListMenu) {
handleCloseResourceActionListMenu();
}
Expand All @@ -76,16 +79,18 @@ export const CodebaseBranchActionsMenu = ({
});
},
}),
createKubeAction({
name: RESOURCE_ACTIONS.DELETE,
createResourceAction({
type: RESOURCE_ACTIONS.DELETE,
label: capitalizeFirstLetter(RESOURCE_ACTIONS.DELETE),
item: branch,
icon: ICONS.BUCKET,
disabled: {
status: isDefaultBranch ? true : !permissions?.delete?.CodebaseBranch.allowed,
reason: isDefaultBranch
? 'You cannot delete the default branch'
: permissions?.delete?.CodebaseBranch.reason,
},
action: () => {
callback: (branch) => {
if (variant === ACTION_MENU_TYPES.MENU && handleCloseResourceActionListMenu) {
handleCloseResourceActionListMenu();
}
Expand Down
Loading

0 comments on commit bae2f65

Please sign in to comment.