Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/db/file/pushes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export const authorise = async (id: string, attestation: any): Promise<{ message
return { message: `authorised ${id}` };
};

export const reject = async (id: string, attestation: any): Promise<{ message: string }> => {
export const reject = async (id: string, rejection: any): Promise<{ message: string }> => {
const action = await getPush(id);
if (!action) {
throw new Error(`push ${id} not found`);
Expand All @@ -120,7 +120,7 @@ export const reject = async (id: string, attestation: any): Promise<{ message: s
action.authorised = false;
action.canceled = false;
action.rejected = true;
action.attestation = attestation;
action.rejection = rejection;
await writeAudit(action);
return { message: `reject ${id}` };
};
Expand Down
4 changes: 2 additions & 2 deletions src/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,8 @@ export const deletePush = (id: string): Promise<void> => start().deletePush(id);
export const authorise = (id: string, attestation: any): Promise<{ message: string }> =>
start().authorise(id, attestation);
export const cancel = (id: string): Promise<{ message: string }> => start().cancel(id);
export const reject = (id: string, attestation: any): Promise<{ message: string }> =>
start().reject(id, attestation);
export const reject = (id: string, rejection: any): Promise<{ message: string }> =>
start().reject(id, rejection);
export const getRepos = (query?: Partial<RepoQuery>): Promise<Repo[]> => start().getRepos(query);
export const getRepo = (name: string): Promise<Repo | null> => start().getRepo(name);
export const getRepoByUrl = (url: string): Promise<Repo | null> => start().getRepoByUrl(url);
Expand Down
4 changes: 2 additions & 2 deletions src/db/mongo/pushes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,15 @@ export const authorise = async (id: string, attestation: any): Promise<{ message
return { message: `authorised ${id}` };
};

export const reject = async (id: string, attestation: any): Promise<{ message: string }> => {
export const reject = async (id: string, rejection: any): Promise<{ message: string }> => {
const action = await getPush(id);
if (!action) {
throw new Error(`push ${id} not found`);
}
action.authorised = false;
action.canceled = false;
action.rejected = true;
action.attestation = attestation;
action.rejection = rejection;
await writeAudit(action);
return { message: `reject ${id}` };
};
Expand Down
2 changes: 1 addition & 1 deletion src/db/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export interface Sink {
deletePush: (id: string) => Promise<void>;
authorise: (id: string, attestation: any) => Promise<{ message: string }>;
cancel: (id: string) => Promise<{ message: string }>;
reject: (id: string, attestation: any) => Promise<{ message: string }>;
reject: (id: string, rejection: any) => Promise<{ message: string }>;
getRepos: (query?: Partial<RepoQuery>) => Promise<Repo[]>;
getRepo: (name: string) => Promise<Repo | null>;
getRepoByUrl: (url: string) => Promise<Repo | null>;
Expand Down
3 changes: 2 additions & 1 deletion src/proxy/actions/Action.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { processGitURLForNameAndOrg, processUrlPath } from '../routes/helper';
import { Step } from './Step';
import { Attestation, CommitData } from '../processors/types';
import { Attestation, CommitData, Rejection } from '../processors/types';

/**
* Class representing a Push.
Expand Down Expand Up @@ -34,6 +34,7 @@ class Action {
user?: string;
userEmail?: string;
attestation?: Attestation;
rejection?: Rejection;
lastStep?: Step;
proxyGitPath?: string;
newIdxFiles?: string[];
Expand Down
9 changes: 9 additions & 0 deletions src/proxy/processors/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ export type Attestation = {
questions: Question[];
};

export type Rejection = {
reviewer: {
username: string;
reviewerEmail: string;
};
timestamp: string | Date;
reason: string;
};

export type CommitContent = {
item: number;
type: number;
Expand Down
33 changes: 31 additions & 2 deletions src/service/routes/push.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ router.post('/:id/reject', async (req: Request, res: Response) => {

const id = req.params.id;
const { username } = req.user as { username: string };
const { reason } = req.body;

if (!reason || !reason.trim()) {
res.status(400).send({
message: 'Rejection reason is required',
});
return;
}

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

if (isAllowed) {
const result = await db.reject(id, null);
console.log(`User ${username} rejected push request for ${id}`);
const reviewerList = await db.getUsers({ username });
const reviewerEmail = reviewerList[0].email;

if (!reviewerEmail) {
res.status(404).send({
message: `There was no registered email address for the reviewer: ${username}`,
});
return;
}

const rejection = {
reason,
timestamp: new Date(),
reviewer: {
username,
reviewerEmail,
},
};

const result = await db.reject(id, rejection);
console.log(
`User ${username} rejected push request for ${id}${reason ? ` with reason: ${reason}` : ''}`,
);
res.send(result);
} else {
res.status(403).send({
Expand Down
3 changes: 2 additions & 1 deletion src/ui/services/git-push.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,13 @@ const rejectPush = async (
id: string,
setMessage: (message: string) => void,
setUserAllowedToReject: (userAllowedToReject: boolean) => void,
reason?: string,
): Promise<void> => {
const apiV1Base = await getApiV1BaseUrl();
const url = `${apiV1Base}/push/${id}/reject`;
let errorMsg = '';
let isUserAllowedToReject = true;
await axios.post(url, {}, getAxiosConfig()).catch((error: any) => {
await axios.post(url, { reason }, getAxiosConfig()).catch((error: any) => {
if (error.response && error.response.status === 401) {
errorMsg = 'You are not authorised to reject...';
isUserAllowedToReject = false;
Expand Down
102 changes: 14 additions & 88 deletions src/ui/views/PushDetails/PushDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import CardFooter from '../../components/Card/CardFooter';
import Button from '../../components/CustomButtons/Button';
import Diff from './components/Diff';
import Attestation from './components/Attestation';
import AttestationView from './components/AttestationView';
import AttestationInfo from './components/AttestationInfo';
import RejectionInfo from './components/RejectionInfo';
import Reject from './components/Reject';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableHead from '@material-ui/core/TableHead';
Expand All @@ -21,11 +23,9 @@ import TableCell from '@material-ui/core/TableCell';
import { getPush, authorisePush, rejectPush, cancelPush } from '../../services/git-push';
import { CheckCircle, Visibility, Cancel, Block } from '@material-ui/icons';
import Snackbar from '@material-ui/core/Snackbar';
import Tooltip from '@material-ui/core/Tooltip';
import { AttestationFormData, PushActionView } from '../../types';
import { PushActionView } from '../../types';
import { trimPrefixRefsHeads, trimTrailingDotGit } from '../../../db/helper';
import { generateEmailLink, getGitProvider } from '../../utils';
import UserLink from '../../components/UserLink/UserLink';

const Dashboard: React.FC = () => {
const { id } = useParams<{ id: string }>();
Expand Down Expand Up @@ -62,9 +62,9 @@ const Dashboard: React.FC = () => {
}
};

const reject = async () => {
const reject = async (reason: string) => {
if (!id) return;
await rejectPush(id, setMessage, setUserAllowedToReject);
await rejectPush(id, setMessage, setUserAllowedToReject, reason);
if (isUserAllowedToReject) {
navigate('/dashboard/push/');
}
Expand Down Expand Up @@ -153,91 +153,17 @@ const Dashboard: React.FC = () => {
<Button color='warning' onClick={cancel}>
Cancel
</Button>
<Button color='danger' onClick={reject}>
Reject
</Button>
<Reject rejectFn={reject} />
<Attestation approveFn={authorise} />
</div>
)}
{push.attestation && push.authorised && (
<div
style={{
background: '#eee',
padding: '10px 20px 10px 20px',
borderRadius: '10px',
color: 'black',
marginTop: '15px',
float: 'right',
position: 'relative',
textAlign: 'left',
}}
>
<span style={{ position: 'absolute', top: 0, right: 0 }}>
<CheckCircle
style={{
cursor: push.autoApproved ? 'default' : 'pointer',
transform: 'scale(0.65)',
opacity: push.autoApproved ? 0.5 : 1,
}}
onClick={() => {
if (!push.autoApproved) {
setAttestation(true);
}
}}
htmlColor='green'
/>
</span>

{push.autoApproved ? (
<div style={{ paddingTop: '15px' }}>
<p>
<strong>Auto-approved by system</strong>
</p>
</div>
) : (
<>
{isGitHub && (
<UserLink username={push.attestation.reviewer.username}>
<img
style={{ width: '45px', borderRadius: '20px' }}
src={`https://github.com/${push.attestation.reviewer.gitAccount}.png`}
/>
</UserLink>
)}
<div>
<p>
{isGitHub && (
<UserLink username={push.attestation.reviewer.username}>
{push.attestation.reviewer.gitAccount}
</UserLink>
)}
{!isGitHub && <UserLink username={push.attestation.reviewer.username} />}{' '}
approved this contribution
</p>
</div>
</>
)}

<Tooltip
title={moment(push.attestation.timestamp).format(
'dddd, MMMM Do YYYY, h:mm:ss a',
)}
arrow
>
<kbd style={{ color: 'black', float: 'right' }}>
{moment(push.attestation.timestamp).fromNow()}
</kbd>
</Tooltip>

{!push.autoApproved && (
<AttestationView
data={push.attestation as AttestationFormData}
attestation={attestation}
setAttestation={setAttestation}
/>
)}
</div>
)}
<AttestationInfo
push={push}
isGitHub={isGitHub}
attestation={attestation}
setAttestation={setAttestation}
/>
<RejectionInfo push={push} />
</CardHeader>
<CardBody>
<GridContainer>
Expand Down
105 changes: 105 additions & 0 deletions src/ui/views/PushDetails/components/AttestationInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React from 'react';
import moment from 'moment';
import { CheckCircle } from '@material-ui/icons';
import Tooltip from '@material-ui/core/Tooltip';
import UserLink from '../../../components/UserLink/UserLink';
import AttestationView from './AttestationView';
import { AttestationFormData, PushActionView } from '../../../types';

interface AttestationInfoProps {
push: PushActionView;
isGitHub: boolean;
attestation: boolean;
setAttestation: (value: boolean) => void;
}

const AttestationInfo: React.FC<AttestationInfoProps> = ({
push,
isGitHub,
attestation,
setAttestation,
}) => {
if (!push.attestation || !push.authorised) {
return null;
}

return (
<div
style={{
background: '#eee',
padding: '10px 20px 10px 20px',
borderRadius: '10px',
color: 'black',
marginTop: '15px',
float: 'right',
position: 'relative',
textAlign: 'left',
}}
>
<span style={{ position: 'absolute', top: 0, right: 0 }}>
<CheckCircle
style={{
cursor: push.autoApproved ? 'default' : 'pointer',
transform: 'scale(0.65)',
opacity: push.autoApproved ? 0.5 : 1,
}}
onClick={() => {
if (!push.autoApproved) {
setAttestation(true);
}
}}
htmlColor='green'
/>
</span>

{push.autoApproved ? (
<div style={{ paddingTop: '15px' }}>
<p>
<strong>Auto-approved by system</strong>
</p>
</div>
) : (
<>
{isGitHub && (
<UserLink username={push.attestation.reviewer.username}>
<img
style={{ width: '45px', borderRadius: '20px' }}
src={`https://github.com/${push.attestation.reviewer.gitAccount}.png`}
/>
</UserLink>
)}
<div>
<p>
{isGitHub && (
<UserLink username={push.attestation.reviewer.username}>
{push.attestation.reviewer.gitAccount}
</UserLink>
)}
{!isGitHub && <UserLink username={push.attestation.reviewer.username} />} approved
this contribution
</p>
</div>
</>
)}

<Tooltip
title={moment(push.attestation.timestamp).format('dddd, MMMM Do YYYY, h:mm:ss a')}
arrow
>
<kbd style={{ color: 'black', float: 'right' }}>
{moment(push.attestation.timestamp).fromNow()}
</kbd>
</Tooltip>

{!push.autoApproved && (
<AttestationView
data={push.attestation as AttestationFormData}
attestation={attestation}
setAttestation={setAttestation}
/>
)}
</div>
);
};

export default AttestationInfo;
Loading
Loading