Skip to content

Commit a74ecfb

Browse files
PuneetPunamiyambpavan
authored andcommitted
Updates List CLI command to add support for group of users
Signed-off-by: PuneetPunamiya <[email protected]>
1 parent 64a9107 commit a74ecfb

File tree

2 files changed

+210
-7
lines changed

2 files changed

+210
-7
lines changed

pkg/cli/cmd/list/list.go

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,48 @@ NAME NumberOfApprovalsRequired PendingApprovals Rejected STATUS
4141
`
4242

4343
func pendingApprovals(at *v1alpha1.ApprovalTask) int {
44-
return at.Spec.NumberOfApprovalsRequired - len(at.Status.ApproversResponse)
44+
// Count unique users who have responded (approved or rejected)
45+
respondedUsers := make(map[string]bool)
46+
47+
for _, approver := range at.Status.ApproversResponse {
48+
if approver.Type == "User" {
49+
respondedUsers[approver.Name] = true
50+
} else if approver.Type == "Group" {
51+
// Count individual group members who have responded
52+
for _, member := range approver.GroupMembers {
53+
if member.Response == "approved" || member.Response == "rejected" {
54+
respondedUsers[member.Name] = true
55+
}
56+
}
57+
}
58+
}
59+
60+
return at.Spec.NumberOfApprovalsRequired - len(respondedUsers)
4561
}
4662

4763
func rejected(at *v1alpha1.ApprovalTask) int {
4864
count := 0
65+
rejectedUsers := make(map[string]bool)
66+
4967
for _, approver := range at.Status.ApproversResponse {
50-
if approver.Response == "rejected" {
51-
count = count + 1
68+
if approver.Type == "User" && approver.Response == "rejected" {
69+
if !rejectedUsers[approver.Name] {
70+
rejectedUsers[approver.Name] = true
71+
count++
72+
}
73+
} else if approver.Type == "Group" {
74+
// Count individual group members who have rejected
75+
for _, member := range approver.GroupMembers {
76+
if member.Response == "rejected" {
77+
if !rejectedUsers[member.Name] {
78+
rejectedUsers[member.Name] = true
79+
count++
80+
}
81+
}
82+
}
5283
}
5384
}
85+
5486
return count
5587
}
5688

pkg/cli/cmd/list/list_test.go

Lines changed: 175 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@ func TestListApprovalTasks(t *testing.T) {
2828
{
2929
Name: "tekton",
3030
Input: "reject",
31+
Type: "User",
3132
},
3233
{
3334
Name: "cli",
3435
Input: "pending",
36+
Type: "User",
3537
},
3638
},
3739
NumberOfApprovalsRequired: 2,
@@ -44,6 +46,7 @@ func TestListApprovalTasks(t *testing.T) {
4446
ApproversResponse: []v1alpha1.ApproverState{
4547
{
4648
Name: "tekton",
49+
Type: "User",
4750
Response: "rejected",
4851
},
4952
},
@@ -60,10 +63,12 @@ func TestListApprovalTasks(t *testing.T) {
6063
{
6164
Name: "tekton",
6265
Input: "approve",
66+
Type: "User",
6367
},
6468
{
6569
Name: "cli",
6670
Input: "approve",
71+
Type: "User",
6772
},
6873
},
6974
NumberOfApprovalsRequired: 2,
@@ -76,11 +81,13 @@ func TestListApprovalTasks(t *testing.T) {
7681
ApproversResponse: []v1alpha1.ApproverState{
7782
{
7883
Name: "tekton",
79-
Response: "approve",
84+
Type: "User",
85+
Response: "approved",
8086
},
8187
{
8288
Name: "cli",
83-
Response: "approve",
89+
Type: "User",
90+
Response: "approved",
8491
},
8592
},
8693
State: "approved",
@@ -96,10 +103,12 @@ func TestListApprovalTasks(t *testing.T) {
96103
{
97104
Name: "tekton",
98105
Input: "pending",
106+
Type: "User",
99107
},
100108
{
101109
Name: "cli",
102110
Input: "pending",
111+
Type: "User",
103112
},
104113
},
105114
NumberOfApprovalsRequired: 2,
@@ -125,10 +134,12 @@ func TestListApprovalTasks(t *testing.T) {
125134
{
126135
Name: "tekton",
127136
Input: "reject",
137+
Type: "User",
128138
},
129139
{
130140
Name: "cli",
131141
Input: "pending",
142+
Type: "User",
132143
},
133144
},
134145
NumberOfApprovalsRequired: 2,
@@ -141,6 +152,7 @@ func TestListApprovalTasks(t *testing.T) {
141152
ApproversResponse: []v1alpha1.ApproverState{
142153
{
143154
Name: "tekton",
155+
Type: "User",
144156
Response: "rejected",
145157
},
146158
},
@@ -157,10 +169,12 @@ func TestListApprovalTasks(t *testing.T) {
157169
{
158170
Name: "tekton",
159171
Input: "approve",
172+
Type: "User",
160173
},
161174
{
162175
Name: "cli",
163176
Input: "approve",
177+
Type: "User",
164178
},
165179
},
166180
NumberOfApprovalsRequired: 2,
@@ -173,11 +187,13 @@ func TestListApprovalTasks(t *testing.T) {
173187
ApproversResponse: []v1alpha1.ApproverState{
174188
{
175189
Name: "tekton",
176-
Response: "approve",
190+
Type: "User",
191+
Response: "approved",
177192
},
178193
{
179194
Name: "cli",
180-
Response: "approve",
195+
Type: "User",
196+
Response: "approved",
181197
},
182198
},
183199
State: "approved",
@@ -193,10 +209,12 @@ func TestListApprovalTasks(t *testing.T) {
193209
{
194210
Name: "tekton",
195211
Input: "pending",
212+
Type: "User",
196213
},
197214
{
198215
Name: "cli",
199216
Input: "pending",
217+
Type: "User",
200218
},
201219
},
202220
NumberOfApprovalsRequired: 2,
@@ -280,6 +298,159 @@ func TestListApprovalTasks(t *testing.T) {
280298

281299
}
282300

301+
// Test individual functions for group functionality
302+
func TestPendingApprovalsWithGroups(t *testing.T) {
303+
tests := []struct {
304+
name string
305+
at *v1alpha1.ApprovalTask
306+
expected int
307+
}{
308+
{
309+
name: "group with multiple members responded",
310+
at: &v1alpha1.ApprovalTask{
311+
Spec: v1alpha1.ApprovalTaskSpec{
312+
NumberOfApprovalsRequired: 3,
313+
},
314+
Status: v1alpha1.ApprovalTaskStatus{
315+
ApproversResponse: []v1alpha1.ApproverState{
316+
{
317+
Name: "admin-group",
318+
Type: "Group",
319+
GroupMembers: []v1alpha1.GroupMemberState{
320+
{Name: "alice", Response: "approved"},
321+
{Name: "bob", Response: "rejected"},
322+
},
323+
},
324+
},
325+
},
326+
},
327+
expected: 1, // 3 required - 2 responded = 1 pending
328+
},
329+
{
330+
name: "mixed user and group responses",
331+
at: &v1alpha1.ApprovalTask{
332+
Spec: v1alpha1.ApprovalTaskSpec{
333+
NumberOfApprovalsRequired: 4,
334+
},
335+
Status: v1alpha1.ApprovalTaskStatus{
336+
ApproversResponse: []v1alpha1.ApproverState{
337+
{
338+
Name: "direct-user",
339+
Type: "User",
340+
Response: "approved",
341+
},
342+
{
343+
Name: "dev-team",
344+
Type: "Group",
345+
GroupMembers: []v1alpha1.GroupMemberState{
346+
{Name: "charlie", Response: "approved"},
347+
{Name: "david", Response: "approved"},
348+
},
349+
},
350+
},
351+
},
352+
},
353+
expected: 1, // 4 required - 3 responded = 1 pending
354+
},
355+
{
356+
name: "no responses",
357+
at: &v1alpha1.ApprovalTask{
358+
Spec: v1alpha1.ApprovalTaskSpec{
359+
NumberOfApprovalsRequired: 2,
360+
},
361+
Status: v1alpha1.ApprovalTaskStatus{
362+
ApproversResponse: []v1alpha1.ApproverState{},
363+
},
364+
},
365+
expected: 2, // 2 required - 0 responded = 2 pending
366+
},
367+
}
368+
369+
for _, tt := range tests {
370+
t.Run(tt.name, func(t *testing.T) {
371+
result := pendingApprovals(tt.at)
372+
if result != tt.expected {
373+
t.Errorf("pendingApprovals() = %d, expected %d", result, tt.expected)
374+
}
375+
})
376+
}
377+
}
378+
379+
func TestRejectedWithGroups(t *testing.T) {
380+
tests := []struct {
381+
name string
382+
at *v1alpha1.ApprovalTask
383+
expected int
384+
}{
385+
{
386+
name: "group with multiple rejections",
387+
at: &v1alpha1.ApprovalTask{
388+
Status: v1alpha1.ApprovalTaskStatus{
389+
ApproversResponse: []v1alpha1.ApproverState{
390+
{
391+
Name: "qa-team",
392+
Type: "Group",
393+
GroupMembers: []v1alpha1.GroupMemberState{
394+
{Name: "tester1", Response: "rejected"},
395+
{Name: "tester2", Response: "rejected"},
396+
{Name: "tester3", Response: "approved"},
397+
},
398+
},
399+
},
400+
},
401+
},
402+
expected: 2, // 2 rejections from group members
403+
},
404+
{
405+
name: "mixed user and group rejections",
406+
at: &v1alpha1.ApprovalTask{
407+
Status: v1alpha1.ApprovalTaskStatus{
408+
ApproversResponse: []v1alpha1.ApproverState{
409+
{
410+
Name: "direct-user",
411+
Type: "User",
412+
Response: "rejected",
413+
},
414+
{
415+
Name: "admin-group",
416+
Type: "Group",
417+
GroupMembers: []v1alpha1.GroupMemberState{
418+
{Name: "admin1", Response: "rejected"},
419+
{Name: "admin2", Response: "approved"},
420+
},
421+
},
422+
},
423+
},
424+
},
425+
expected: 2, // 1 direct user + 1 group member rejected = 2
426+
},
427+
{
428+
name: "no rejections",
429+
at: &v1alpha1.ApprovalTask{
430+
Status: v1alpha1.ApprovalTaskStatus{
431+
ApproversResponse: []v1alpha1.ApproverState{
432+
{
433+
Name: "user1",
434+
Type: "User",
435+
Response: "approved",
436+
},
437+
},
438+
},
439+
},
440+
expected: 0, // no rejections
441+
},
442+
}
443+
444+
for _, tt := range tests {
445+
t.Run(tt.name, func(t *testing.T) {
446+
result := rejected(tt.at)
447+
if result != tt.expected {
448+
t.Errorf("rejected() = %d, expected %d", result, tt.expected)
449+
}
450+
})
451+
}
452+
}
453+
283454
func command(t *testing.T, approvaltasks []*v1alpha1.ApprovalTask, ns []*corev1.Namespace, dc dynamic.Interface) *cobra.Command {
284455
cs, _ := test.SeedTestData(t, test.Data{Approvaltasks: approvaltasks, Namespaces: ns})
285456
p := &test.Params{ApprovalTask: cs.ApprovalTask, Kube: cs.Kube, Dynamic: dc}

0 commit comments

Comments
 (0)