Skip to content

Commit

Permalink
PRfd - Merge PR and change PR status (#8209)
Browse files Browse the repository at this point in the history
* Create PR page

* Integrate List API

* Fix Creation function

* Extract BranchesSelection to a component

* Fix dates

* Add Markdown support

* Merge PR and change PR status

* Extra improvments for the List page

* Fix PR comments

* Change width

* Improve error handling

* Update page width

* Add index to list items

* Fix colors

* Add retries

* Remove the option to Re-Open a PR

* Use exp-backoff

* Fix error handling

* Rename file

* Fix PR comments

* Revert "Rename file"

This reverts commit a5566fa.

* Rename file
  • Loading branch information
itaigilo authored Sep 24, 2024
1 parent 158d547 commit 06cd860
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 70 deletions.
37 changes: 18 additions & 19 deletions webui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion webui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"bootstrap": "^5.3.2",
"csstype": "^3.1.3",
"dayjs": "^1.11.10",
"exponential-backoff": "^3.1.1",
"lodash": "^4.17.21",
"p-map": "^7.0.0",
"prismjs": "^1.29.0",
Expand All @@ -38,7 +39,7 @@
"react-dropzone": "^14.2.3",
"react-icons": "^4.12.0",
"react-ipynb-renderer": "^2.2.1",
"react-markdown": "^8.0.3",
"react-markdown": "^8.0.7",
"react-router-dom": "^6.20.1",
"react-simple-code-editor": "^0.13.1",
"react-syntax-highlighter": "^15.5.0",
Expand Down
19 changes: 19 additions & 0 deletions webui/src/lib/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,25 @@ class Pulls {
}
return await response.text();
}

async update(repoId, pullId, pullDetails) {
const response = await apiRequest(`/repositories/${encodeURIComponent(repoId)}/pulls/${encodeURIComponent(pullId)}`, {
method: 'PATCH',
body: JSON.stringify(pullDetails),
});
if (response.status !== 204) {
const baseMessage = 'Could not update pull request';
switch (response.status) {
case 400:
case 401:
case 403:
case 404:
throw new Error(`${baseMessage}: ${(await response.json()).message}`);
default:
throw new Error(`${baseMessage} (status = ${response.status}).`);
}
}
}
}

// uploadWithProgress uses good ol' XMLHttpRequest because progress indication in fetch() is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,23 +111,26 @@ const MergeButton = ({repo, onDone, source, dest, disabled = false}) => {
<MetadataFields metadataFields={metadataFields} setMetadataFields={setMetadataFields}/>
</Form>
<FormControl sx={{m: 1, minWidth: 120}}>
<InputLabel id="demo-select-small">Strategy</InputLabel>
<InputLabel id="demo-select-small" className="text-secondary">Strategy</InputLabel>
<Select
labelId="demo-select-small"
id="demo-simple-select-helper"
value={mergeState.strategy}
label="Strategy"
className="text-secondary"
onChange={onStrategyChange}
>
<MenuItem value={"none"}>Default</MenuItem>
<MenuItem value={"source-wins"}>source-wins</MenuItem>
<MenuItem value={"dest-wins"}>dest-wins</MenuItem>
</Select>
</FormControl>
<FormHelperText>In case of a merge conflict, this option will force the merge process
<FormHelperText className="text-secondary">
In case of a merge conflict, this option will force the merge process
to automatically favor changes from <b>{dest}</b> (&rdquo;dest-wins&rdquo;) or
from <b>{source}</b> (&rdquo;source-wins&rdquo;). In case no selection is made,
the merge process will fail in case of a conflict.</FormHelperText>
the merge process will fail in case of a conflict.
</FormHelperText>
{(mergeState.err) ? (<AlertError error={mergeState.err}/>) : (<></>)}
</Modal.Body>
<Modal.Footer>
Expand Down
4 changes: 2 additions & 2 deletions webui/src/pages/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import RepositoryCommitsPage from "./repositories/repository/commits";
import RepositoryCommitPage from "./repositories/repository/commits/commit";
import RepositoryBranchesPage from "./repositories/repository/branches";
import RepositoryTagsPage from "./repositories/repository/tags";
import RepositoryPullsPage from "./repositories/repository/pulls/pulls";
import RepositoryPullsListPage from "./repositories/repository/pulls/pullsList";
import RepositoryCreatePullPage from "./repositories/repository/pulls/createPull";
import RepositoryPullDetailsPage from "./repositories/repository/pulls/pullDetails";
import RepositoryComparePage from "./repositories/repository/compare";
Expand Down Expand Up @@ -66,7 +66,7 @@ export const IndexPage = () => {
<Route path="branches" element={<RepositoryBranchesPage/>}/>
<Route path="tags" element={<RepositoryTagsPage/>}/>
<Route path="pulls">
<Route index element={<RepositoryPullsPage/>}/>
<Route index element={<RepositoryPullsListPage/>}/>
<Route path="create" element={<RepositoryCreatePullPage/>}/>
<Route path=":pullId" element={<RepositoryPullDetailsPage/>}/>
</Route>
Expand Down
94 changes: 73 additions & 21 deletions webui/src/pages/repositories/repository/pulls/pullDetails.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import React, {useEffect} from "react";
import React, {useEffect, useState} from "react";
import {useOutletContext} from "react-router-dom";
import Badge from "react-bootstrap/Badge";
import Button from "react-bootstrap/Button";
import Card from "react-bootstrap/Card";
import {GitMergeIcon, GitPullRequestClosedIcon, GitPullRequestIcon} from "@primer/octicons-react";
import dayjs from "dayjs";
import Markdown from 'react-markdown'
import {backOff} from "exponential-backoff";

import {AlertError, Loading} from "../../../../lib/components/controls";
import {useRefs} from "../../../../lib/hooks/repo";
import {useRouter} from "../../../../lib/hooks/router";
import {RepoError} from "../error";
import {pulls as pullsAPI} from "../../../../lib/api";
import {pulls as pullsAPI, refs as refsAPI} from "../../../../lib/api";
import {useAPI} from "../../../../lib/hooks/api";
import {Link} from "../../../../lib/components/nav";
import CompareBranches from "../../../../lib/components/repository/compareBranches";
Expand All @@ -31,20 +33,56 @@ const StatusBadge = ({status}) => {
case PullStatus.open:
return <Badge pill bg={"success"}>{<GitPullRequestIcon/>} {text}</Badge>;
case PullStatus.closed:
return <Badge pill bg={"purple"}>{<GitPullRequestClosedIcon/>} {text}</Badge>;
return <Badge pill bg={"secondary"}>{<GitPullRequestClosedIcon/>} {text}</Badge>;
case PullStatus.merged:
return <Badge pill bg={"danger"}>{<GitMergeIcon/>} {text}</Badge>;
return <Badge pill bg={"primary"}>{<GitMergeIcon/>} {text}</Badge>;
default:
return <Badge pill bg={"secondary"}>{text}</Badge>;
return <Badge pill bg={"light"}>{text}</Badge>;
}
};

const PullDetailsContent = ({repo, pull}) => {
let [loading, setLoading] = useState(false);
let [error, setError] = useState(null);

const mergePullRequest = async () => {
setError(null);
setLoading(true);
try {
await refsAPI.merge(repo.id, pull.source_branch, pull.destination_branch);
} catch (error) {
setError(`Failed to merge pull request: ${error.message}`);
setLoading(false);
return;
}
try {
await backOff(() => pullsAPI.update(repo.id, pull.id, {status: PullStatus.merged}));
} catch (error) {
setError(`Failed to update pull request status: ${error.message}`);
setLoading(false);
}
window.location.reload(); // TODO (gilo): replace with a more elegant solution
}

const changePullStatus = (status) => async () => {
setError(null);
setLoading(true);
try {
await pullsAPI.update(repo.id, pull.id, {status});
window.location.reload(); // TODO (gilo): replace with a more elegant solution
} catch (error) {
setError(`Failed to change pull-request status to ${status}: ${error.message}`);
setLoading(false);
}
}

const createdAt = dayjs(pull.creation_date);

const isPullOpen = () => pull.status === PullStatus.open;

return (
<div className="pull-details mb-5">
<h1>{pull.title} <span className="fs-5 text-secondary">{pull.id}</span></h1>
<div className="pull-details w-75 mb-5">
<h1>{pull.title}</h1>
<div className="pull-info mt-3">
<StatusBadge status={pull.status}/>
<span className="ms-2">
Expand All @@ -58,27 +96,41 @@ const PullDetailsContent = ({repo, pull}) => {
Opened on {createdAt.format("MMM D, YYYY")} ({createdAt.fromNow()}).
</Card.Header>
<Card.Body className="description">
{pull.description}
<Markdown>{pull.description}</Markdown>
</Card.Body>
</Card>
<div className="bottom-buttons-row mt-4 clearfix">
{error && <AlertError error={error} onDismiss={() => setError(null)}/>}
<div className="bottom-buttons-group float-end">
<Button variant="outline-secondary" className="text-secondary-emphasis me-2">
Close pull request
</Button>
<Button variant="success">
<GitMergeIcon/> Merge pull request
</Button>
{isPullOpen() &&
<Button variant="outline-secondary"
className="text-secondary-emphasis me-2"
disabled={loading}
onClick={changePullStatus(PullStatus.closed)}>
{loading ?
<span className="spinner-border spinner-border-sm text-light" role="status"/> :
<>Close pull request</>
}
</Button>
}
{isPullOpen() &&
<Button variant="success"
disabled={loading}
onClick={mergePullRequest}>
{loading ?
<span className="spinner-border spinner-border-sm text-light" role="status"/> :
<><GitMergeIcon/> Merge pull request</>
}
</Button>
}
</div>
</div>
<hr className="mt-5 mb-4"/>
<div className="w-75">
<CompareBranches
repo={repo}
reference={{id: pull.destination_branch, type: RefTypeBranch}}
compareReference={{id: pull.source_branch, type: RefTypeBranch}}
/>
</div>
<CompareBranches
repo={repo}
reference={{id: pull.destination_branch, type: RefTypeBranch}}
compareReference={{id: pull.source_branch, type: RefTypeBranch}}
/>
</div>
);
};
Expand Down
Loading

0 comments on commit 06cd860

Please sign in to comment.