Skip to content

Commit a71af46

Browse files
anwesha-palit-redhatvikram-raj
authored andcommitted
feat: added group user validation for manual approval
Pair programming with [email protected] (SRVKP-8180) Assisted by: Claude 4.5
1 parent dc8cbc4 commit a71af46

File tree

16 files changed

+1411
-118
lines changed

16 files changed

+1411
-118
lines changed

locales/en/plugin__pipelines-console-plugin.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
"Cancelling": "Cancelling",
9393
"Cannot be longer than {{characterCount}} characters.": "Cannot be longer than {{characterCount}} characters.",
9494
"Categories": "Categories",
95+
"Checking authorization...": "Checking authorization...",
9596
"Checks: Read & Write": "Checks: Read & Write",
9697
"Click": "Click",
9798
"Click {{submit}} to save changes or {{reset}} to cancel changes.": "Click {{submit}} to save changes or {{reset}} to cancel changes.",
@@ -220,6 +221,7 @@
220221
"GitHub application name": "GitHub application name",
221222
"Go to Admin Approvals tab": "Go to Admin Approvals tab",
222223
"Go to Approvals tab": "Go to Approvals tab",
224+
"Group": "Group",
223225
"Hide credential options": "Hide credential options",
224226
"Hide variables": "Hide variables",
225227
"Hide VolumeClaimTemplate options": "Hide VolumeClaimTemplate options",
@@ -594,6 +596,7 @@
594596
"Use your Git Personal token. Create a token with repo, public_repo & admin:repo_hook scopes and give your token an expiration, i.e 30d.": "Use your Git Personal token. Create a token with repo, public_repo & admin:repo_hook scopes and give your token an expiration, i.e 30d.",
595597
"Use your GitHub Personal token. Use this <2>link</2> to create a <5>classic</5> token with <7>repo</7> & <10>admin:repo_hook</10> scopes and give your token an expiration, i.e 30d.": "Use your GitHub Personal token. Use this <2>link</2> to create a <5>classic</5> token with <7>repo</7> & <10>admin:repo_hook</10> scopes and give your token an expiration, i.e 30d.",
596598
"Use your Gitlab Personal access token. Use this <2>link</2> to create a token with <5>api</5> scope. Select the role as <8>Maintainer/Owner</8>. Give your token an expiration i.e 30d.": "Use your Gitlab Personal access token. Use this <2>link</2> to create a token with <5>api</5> scope. Select the role as <8>Maintainer/Owner</8>. Give your token an expiration i.e 30d.",
599+
"User": "User",
597600
"User not an approver": "User not an approver",
598601
"Username": "Username",
599602
"Value": "Value",

src/components/approval-tasks/ApprovalRow.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const ApprovalRow: React.FC<
3838
const {
3939
metadata: { name, namespace, creationTimestamp },
4040
spec: { description, numberOfApprovalsRequired },
41-
status: { approvers, approversResponse },
41+
status: { approvers, approvalsReceived },
4242
} = obj;
4343

4444
const translatedApproversCount = t('{{assignees}} Assigned', {
@@ -104,7 +104,7 @@ const ApprovalRow: React.FC<
104104
{getApprovalStatusInfo(approvalTaskStatus).message}{' '}
105105
<Tooltip content={translatedApproversCount} position="right">
106106
<span className="pipelines-approval-status-info">{`(${
107-
approversResponse?.length || 0
107+
approvalsReceived || 0
108108
}/${numberOfApprovalsRequired || 0})`}</span>
109109
</Tooltip>
110110
</SplitItem>

src/components/approval-tasks/ApprovalTaskActionDropdown.tsx

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
DropdownList,
88
MenuToggle,
99
MenuToggleElement,
10+
Spinner,
1011
Tooltip,
1112
} from '@patternfly/react-core';
1213
import { KEBAB_BUTTON_ID } from '../../consts';
@@ -18,7 +19,8 @@ import {
1819
import { ApprovalTaskModel } from '../../models';
1920
import { approvalModal } from './modal';
2021
import { ApproverStatusResponse } from '../../types';
21-
import { useGetActiveUser } from '../hooks/hooks';
22+
import { useActiveUserWithUpdate } from '../hooks/hooks';
23+
import { isUserAuthorizedForApproval } from '../utils/approval-group-utils';
2224

2325
type ApprovalTaskActionDropdownProps = {
2426
approvalTask: ApprovalTaskKind;
@@ -29,14 +31,16 @@ const ApprovalTaskActionDropdown: React.FC<ApprovalTaskActionDropdownProps> = ({
2931
approvalTask,
3032
pipelineRun,
3133
}) => {
32-
const currentUser = useGetActiveUser();
34+
const { currentUser, updateUserInfo } = useActiveUserWithUpdate();
3335
const launchModal = useModal();
3436
const { t } = useTranslation('plugin__pipelines-console-plugin');
3537
const {
3638
metadata: { name, namespace },
37-
status: { approvers, state },
39+
status: { state },
40+
spec: { approvers },
3841
} = approvalTask;
3942
const [isOpen, setIsOpen] = React.useState(false);
43+
const [isAuthorized, setIsAuthorized] = React.useState<boolean | null>(null);
4044
const onToggle = () => {
4145
setIsOpen(!isOpen);
4246
};
@@ -47,7 +51,8 @@ const ApprovalTaskActionDropdown: React.FC<ApprovalTaskActionDropdownProps> = ({
4751
launchModal(approvalModal, {
4852
resource: approvalTask,
4953
pipelineRunName: pipelineRun?.metadata?.name,
50-
userName: currentUser,
54+
userName: currentUser?.username,
55+
currentUser: currentUser,
5156
type: 'approve',
5257
});
5358
};
@@ -56,7 +61,8 @@ const ApprovalTaskActionDropdown: React.FC<ApprovalTaskActionDropdownProps> = ({
5661
launchModal(approvalModal, {
5762
resource: approvalTask,
5863
pipelineRunName: pipelineRun?.metadata?.name,
59-
userName: currentUser,
64+
userName: currentUser?.username,
65+
currentUser: currentUser,
6066
type: 'reject',
6167
});
6268
};
@@ -69,10 +75,36 @@ const ApprovalTaskActionDropdown: React.FC<ApprovalTaskActionDropdownProps> = ({
6975
namespace,
7076
});
7177

78+
// Check group-based authorization
79+
React.useEffect(() => {
80+
const checkAuthorization = async () => {
81+
if (currentUser && approvers) {
82+
try {
83+
if (currentUser?.username) {
84+
const authorized = await isUserAuthorizedForApproval(
85+
currentUser.username,
86+
approvers,
87+
currentUser,
88+
updateUserInfo,
89+
);
90+
setIsAuthorized(authorized);
91+
}
92+
} catch (error) {
93+
console.error('Error checking group authorization:', error);
94+
setIsAuthorized(false);
95+
}
96+
} else {
97+
setIsAuthorized(false);
98+
}
99+
};
100+
checkAuthorization();
101+
}, [currentUser, approvers]);
102+
72103
const isDropdownDisabled =
73104
!canApproveAndRejectResource ||
74105
state !== ApproverStatusResponse.Pending ||
75-
!approvers?.find((approver) => approver === currentUser);
106+
isAuthorized === null || // Still loading
107+
!isAuthorized; // Not authorized (includes both direct user and group checks)
76108

77109
const tooltipContent = () => {
78110
if (!canApproveAndRejectResource) {
@@ -84,7 +116,18 @@ const ApprovalTaskActionDropdown: React.FC<ApprovalTaskActionDropdownProps> = ({
84116
if (state !== ApproverStatusResponse.Pending) {
85117
return t(`PipelineRun has been {{state}}`, { state });
86118
}
87-
if (!approvers?.find((approver) => approver === currentUser)) {
119+
if (isAuthorized === null) {
120+
return (
121+
(
122+
<Spinner
123+
className="pf-v5-u-mr-xs"
124+
size="sm"
125+
aria-label={t('Checking authorization...')}
126+
/>
127+
) + t('Checking authorization...')
128+
);
129+
}
130+
if (!isAuthorized) {
88131
return t('User not an approver');
89132
}
90133
return t('Permission denied');

src/components/approval-tasks/ApprovalTasksList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const pipelineApprovalFilterReducer = (obj: ApprovalTaskKind, pipelineRuns) => {
3434
) {
3535
return ApprovalStatus.RequestSent;
3636
}
37-
return status || ApprovalStatus.Unknown;
37+
return status || ApprovalStatus.Unknown;
3838
};
3939

4040
const ApprovalTasksList: React.FC<ApprovalTasksListProps> = ({

0 commit comments

Comments
 (0)