Skip to content

Commit 7ee282c

Browse files
committed
Merge remote-tracking branch 'finos/main' into denis-coric/fix-1392-linked
# Conflicts: # src/ui/services/git-push.ts # src/ui/views/PushDetails/PushDetails.tsx
2 parents 67b85b2 + 5c4329c commit 7ee282c

17 files changed

Lines changed: 579 additions & 105 deletions

File tree

src/db/file/pushes.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export const authorise = async (id: string, attestation: any): Promise<{ message
111111
return { message: `authorised ${id}` };
112112
};
113113

114-
export const reject = async (id: string, attestation: any): Promise<{ message: string }> => {
114+
export const reject = async (id: string, rejection: any): Promise<{ message: string }> => {
115115
const action = await getPush(id);
116116
if (!action) {
117117
throw new Error(`push ${id} not found`);
@@ -120,7 +120,7 @@ export const reject = async (id: string, attestation: any): Promise<{ message: s
120120
action.authorised = false;
121121
action.canceled = false;
122122
action.rejected = true;
123-
action.attestation = attestation;
123+
action.rejection = rejection;
124124
await writeAudit(action);
125125
return { message: `reject ${id}` };
126126
};

src/db/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,8 @@ export const deletePush = (id: string): Promise<void> => start().deletePush(id);
167167
export const authorise = (id: string, attestation: any): Promise<{ message: string }> =>
168168
start().authorise(id, attestation);
169169
export const cancel = (id: string): Promise<{ message: string }> => start().cancel(id);
170-
export const reject = (id: string, attestation: any): Promise<{ message: string }> =>
171-
start().reject(id, attestation);
170+
export const reject = (id: string, rejection: any): Promise<{ message: string }> =>
171+
start().reject(id, rejection);
172172
export const getRepos = (query?: Partial<RepoQuery>): Promise<Repo[]> => start().getRepos(query);
173173
export const getRepo = (name: string): Promise<Repo | null> => start().getRepo(name);
174174
export const getRepoByUrl = (url: string): Promise<Repo | null> => start().getRepoByUrl(url);

src/db/mongo/pushes.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,15 @@ export const authorise = async (id: string, attestation: any): Promise<{ message
7777
return { message: `authorised ${id}` };
7878
};
7979

80-
export const reject = async (id: string, attestation: any): Promise<{ message: string }> => {
80+
export const reject = async (id: string, rejection: any): Promise<{ message: string }> => {
8181
const action = await getPush(id);
8282
if (!action) {
8383
throw new Error(`push ${id} not found`);
8484
}
8585
action.authorised = false;
8686
action.canceled = false;
8787
action.rejected = true;
88-
action.attestation = attestation;
88+
action.rejection = rejection;
8989
await writeAudit(action);
9090
return { message: `reject ${id}` };
9191
};

src/db/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export interface Sink {
9898
deletePush: (id: string) => Promise<void>;
9999
authorise: (id: string, attestation: any) => Promise<{ message: string }>;
100100
cancel: (id: string) => Promise<{ message: string }>;
101-
reject: (id: string, attestation: any) => Promise<{ message: string }>;
101+
reject: (id: string, rejection: any) => Promise<{ message: string }>;
102102
getRepos: (query?: Partial<RepoQuery>) => Promise<Repo[]>;
103103
getRepo: (name: string) => Promise<Repo | null>;
104104
getRepoByUrl: (url: string) => Promise<Repo | null>;

src/proxy/actions/Action.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { processGitURLForNameAndOrg, processUrlPath } from '../routes/helper';
22
import { Step } from './Step';
3-
import { Attestation, CommitData } from '../processors/types';
3+
import { Attestation, CommitData, Rejection } from '../processors/types';
44

55
/**
66
* Class representing a Push.
@@ -34,6 +34,7 @@ class Action {
3434
user?: string;
3535
userEmail?: string;
3636
attestation?: Attestation;
37+
rejection?: Rejection;
3738
lastStep?: Step;
3839
proxyGitPath?: string;
3940
newIdxFiles?: string[];

src/proxy/processors/types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ export type Attestation = {
1919
questions: Question[];
2020
};
2121

22+
export type Rejection = {
23+
reviewer: {
24+
username: string;
25+
reviewerEmail: string;
26+
};
27+
timestamp: string | Date;
28+
reason: string;
29+
};
30+
2231
export type CommitContent = {
2332
item: number;
2433
type: number;

src/service/routes/push.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ router.post('/:id/reject', async (req: Request<{ id: string }>, res: Response) =
4545

4646
const id = req.params.id;
4747
const { username } = req.user as { username: string };
48+
const { reason } = req.body;
49+
50+
if (!reason || !reason.trim()) {
51+
res.status(400).send({
52+
message: 'Rejection reason is required',
53+
});
54+
return;
55+
}
4856

4957
// Get the push request
5058
const push = await getValidPushOrRespond(id, res);
@@ -71,8 +79,29 @@ router.post('/:id/reject', async (req: Request<{ id: string }>, res: Response) =
7179
const isAllowed = await db.canUserApproveRejectPush(id, username);
7280

7381
if (isAllowed) {
74-
const result = await db.reject(id, null);
75-
console.log(`User ${username} rejected push request for ${id}`);
82+
const reviewerList = await db.getUsers({ username });
83+
const reviewerEmail = reviewerList[0].email;
84+
85+
if (!reviewerEmail) {
86+
res.status(404).send({
87+
message: `There was no registered email address for the reviewer: ${username}`,
88+
});
89+
return;
90+
}
91+
92+
const rejection = {
93+
reason,
94+
timestamp: new Date(),
95+
reviewer: {
96+
username,
97+
reviewerEmail,
98+
},
99+
};
100+
101+
const result = await db.reject(id, rejection);
102+
console.log(
103+
`User ${username} rejected push request for ${id}${reason ? ` with reason: ${reason}` : ''}`,
104+
);
76105
res.send(result);
77106
} else {
78107
res.status(403).send({

src/ui/services/git-push.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,12 @@ const authorisePush = async (
6565
}
6666
};
6767

68-
const rejectPush = async (id: string): Promise<ServiceResult> => {
68+
const rejectPush = async (id: string, reason?: string): Promise<ServiceResult> => {
6969
const apiV1Base = await getApiV1BaseUrl();
7070
const url = `${apiV1Base}/push/${id}/reject`;
7171

7272
try {
73-
await axios.post(url, {}, getAxiosConfig());
73+
await axios.post(url, { reason }, getAxiosConfig());
7474
return successResult();
7575
} catch (error: any) {
7676
return errorResult(error, 'Failed to reject push request');

src/ui/views/PushDetails/PushDetails.tsx

Lines changed: 14 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import CardFooter from '../../components/Card/CardFooter';
1212
import Button from '../../components/CustomButtons/Button';
1313
import Diff from './components/Diff';
1414
import Attestation from './components/Attestation';
15-
import AttestationView from './components/AttestationView';
15+
import AttestationInfo from './components/AttestationInfo';
16+
import RejectionInfo from './components/RejectionInfo';
17+
import Reject from './components/Reject';
1618
import Table from '@material-ui/core/Table';
1719
import TableBody from '@material-ui/core/TableBody';
1820
import TableHead from '@material-ui/core/TableHead';
@@ -22,11 +24,9 @@ import { getPush, authorisePush, rejectPush, cancelPush } from '../../services/g
2224
import type { ServiceResult } from '../../services/errors';
2325
import { CheckCircle, Visibility, Cancel, Block } from '@material-ui/icons';
2426
import Snackbar from '@material-ui/core/Snackbar';
25-
import Tooltip from '@material-ui/core/Tooltip';
26-
import { AttestationFormData, PushActionView } from '../../types';
27+
import { PushActionView } from '../../types';
2728
import { trimPrefixRefsHeads, trimTrailingDotGit } from '../../../db/helper';
2829
import { generateEmailLink, getGitProvider } from '../../utils';
29-
import UserLink from '../../components/UserLink/UserLink';
3030

3131
const Dashboard: React.FC = () => {
3232
const { id } = useParams<{ id: string }>();
@@ -75,9 +75,9 @@ const Dashboard: React.FC = () => {
7575
handleActionFailure(result);
7676
};
7777

78-
const reject = async () => {
78+
const reject = async (reason: string) => {
7979
if (!id) return;
80-
const result = await rejectPush(id);
80+
const result = await rejectPush(id, reason);
8181
if (result.success) {
8282
navigate('/dashboard/push/');
8383
return;
@@ -172,91 +172,17 @@ const Dashboard: React.FC = () => {
172172
<Button color='warning' onClick={cancel}>
173173
Cancel
174174
</Button>
175-
<Button color='danger' onClick={reject}>
176-
Reject
177-
</Button>
175+
<Reject rejectFn={reject} />
178176
<Attestation approveFn={authorise} />
179177
</div>
180178
)}
181-
{push.attestation && push.authorised && (
182-
<div
183-
style={{
184-
background: '#eee',
185-
padding: '10px 20px 10px 20px',
186-
borderRadius: '10px',
187-
color: 'black',
188-
marginTop: '15px',
189-
float: 'right',
190-
position: 'relative',
191-
textAlign: 'left',
192-
}}
193-
>
194-
<span style={{ position: 'absolute', top: 0, right: 0 }}>
195-
<CheckCircle
196-
style={{
197-
cursor: push.autoApproved ? 'default' : 'pointer',
198-
transform: 'scale(0.65)',
199-
opacity: push.autoApproved ? 0.5 : 1,
200-
}}
201-
onClick={() => {
202-
if (!push.autoApproved) {
203-
setAttestation(true);
204-
}
205-
}}
206-
htmlColor='green'
207-
/>
208-
</span>
209-
210-
{push.autoApproved ? (
211-
<div style={{ paddingTop: '15px' }}>
212-
<p>
213-
<strong>Auto-approved by system</strong>
214-
</p>
215-
</div>
216-
) : (
217-
<>
218-
{isGitHub && (
219-
<UserLink username={push.attestation.reviewer.username}>
220-
<img
221-
style={{ width: '45px', borderRadius: '20px' }}
222-
src={`https://github.com/${push.attestation.reviewer.gitAccount}.png`}
223-
/>
224-
</UserLink>
225-
)}
226-
<div>
227-
<p>
228-
{isGitHub && (
229-
<UserLink username={push.attestation.reviewer.username}>
230-
{push.attestation.reviewer.gitAccount}
231-
</UserLink>
232-
)}
233-
{!isGitHub && <UserLink username={push.attestation.reviewer.username} />}{' '}
234-
approved this contribution
235-
</p>
236-
</div>
237-
</>
238-
)}
239-
240-
<Tooltip
241-
title={moment(push.attestation.timestamp).format(
242-
'dddd, MMMM Do YYYY, h:mm:ss a',
243-
)}
244-
arrow
245-
>
246-
<kbd style={{ color: 'black', float: 'right' }}>
247-
{moment(push.attestation.timestamp).fromNow()}
248-
</kbd>
249-
</Tooltip>
250-
251-
{!push.autoApproved && (
252-
<AttestationView
253-
data={push.attestation as AttestationFormData}
254-
attestation={attestation}
255-
setAttestation={setAttestation}
256-
/>
257-
)}
258-
</div>
259-
)}
179+
<AttestationInfo
180+
push={push}
181+
isGitHub={isGitHub}
182+
attestation={attestation}
183+
setAttestation={setAttestation}
184+
/>
185+
<RejectionInfo push={push} />
260186
</CardHeader>
261187
<CardBody>
262188
<GridContainer>
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import React from 'react';
2+
import moment from 'moment';
3+
import { CheckCircle } from '@material-ui/icons';
4+
import Tooltip from '@material-ui/core/Tooltip';
5+
import UserLink from '../../../components/UserLink/UserLink';
6+
import AttestationView from './AttestationView';
7+
import { AttestationFormData, PushActionView } from '../../../types';
8+
9+
interface AttestationInfoProps {
10+
push: PushActionView;
11+
isGitHub: boolean;
12+
attestation: boolean;
13+
setAttestation: (value: boolean) => void;
14+
}
15+
16+
const AttestationInfo: React.FC<AttestationInfoProps> = ({
17+
push,
18+
isGitHub,
19+
attestation,
20+
setAttestation,
21+
}) => {
22+
if (!push.attestation || !push.authorised) {
23+
return null;
24+
}
25+
26+
return (
27+
<div
28+
style={{
29+
background: '#eee',
30+
padding: '10px 20px 10px 20px',
31+
borderRadius: '10px',
32+
color: 'black',
33+
marginTop: '15px',
34+
float: 'right',
35+
position: 'relative',
36+
textAlign: 'left',
37+
}}
38+
>
39+
<span style={{ position: 'absolute', top: 0, right: 0 }}>
40+
<CheckCircle
41+
style={{
42+
cursor: push.autoApproved ? 'default' : 'pointer',
43+
transform: 'scale(0.65)',
44+
opacity: push.autoApproved ? 0.5 : 1,
45+
}}
46+
onClick={() => {
47+
if (!push.autoApproved) {
48+
setAttestation(true);
49+
}
50+
}}
51+
htmlColor='green'
52+
/>
53+
</span>
54+
55+
{push.autoApproved ? (
56+
<div style={{ paddingTop: '15px' }}>
57+
<p>
58+
<strong>Auto-approved by system</strong>
59+
</p>
60+
</div>
61+
) : (
62+
<>
63+
{isGitHub && (
64+
<UserLink username={push.attestation.reviewer.username}>
65+
<img
66+
style={{ width: '45px', borderRadius: '20px' }}
67+
src={`https://github.com/${push.attestation.reviewer.gitAccount}.png`}
68+
/>
69+
</UserLink>
70+
)}
71+
<div>
72+
<p>
73+
{isGitHub && (
74+
<UserLink username={push.attestation.reviewer.username}>
75+
{push.attestation.reviewer.gitAccount}
76+
</UserLink>
77+
)}
78+
{!isGitHub && <UserLink username={push.attestation.reviewer.username} />} approved
79+
this contribution
80+
</p>
81+
</div>
82+
</>
83+
)}
84+
85+
<Tooltip
86+
title={moment(push.attestation.timestamp).format('dddd, MMMM Do YYYY, h:mm:ss a')}
87+
arrow
88+
>
89+
<kbd style={{ color: 'black', float: 'right' }}>
90+
{moment(push.attestation.timestamp).fromNow()}
91+
</kbd>
92+
</Tooltip>
93+
94+
{!push.autoApproved && (
95+
<AttestationView
96+
data={push.attestation as AttestationFormData}
97+
attestation={attestation}
98+
setAttestation={setAttestation}
99+
/>
100+
)}
101+
</div>
102+
);
103+
};
104+
105+
export default AttestationInfo;

0 commit comments

Comments
 (0)