Skip to content
This repository was archived by the owner on Mar 4, 2025. It is now read-only.

Commit 80d81d6

Browse files
committed
webui: Migrate create MR page to React
This also fixes some issues on this page like non-working links in the commit preview.
1 parent 0fabd05 commit 80d81d6

File tree

7 files changed

+291
-436
lines changed

7 files changed

+291
-436
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,15 @@ webui/js/dbhub.js*
5353
webui/js/app.js
5454
webui/js/auth.js
5555
webui/js/branches.js
56+
webui/js/commit-list.js
5657
webui/js/database-commits.js
5758
webui/js/database-settings.js
5859
webui/js/database-tags.js
5960
webui/js/database-view.js
6061
webui/js/database-watchers.js
6162
webui/js/db-header.js
6263
webui/js/discussion-comments.js
64+
webui/js/discussion-create-mr.js
6365
webui/js/discussion-list.js
6466
webui/js/markdown-editor.js
6567

webui/jsx/app.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import DatabaseView from "./database-view";
1212
import DatabaseWatchers from "./database-watchers";
1313
import DbHeader from "./db-header";
1414
import DiscussionComments from "./discussion-comments";
15+
import DiscussionCreateMr from "./discussion-create-mr";
1516
import DiscussionList from "./discussion-list";
1617
import MarkdownEditor from "./markdown-editor";
1718

@@ -91,6 +92,14 @@ import MarkdownEditor from "./markdown-editor";
9192
}
9293
}
9394

95+
{
96+
const rootNode = document.getElementById("discussion-create-mr");
97+
if (rootNode) {
98+
const root = ReactDOM.createRoot(rootNode);
99+
root.render(<DiscussionCreateMr />);
100+
}
101+
}
102+
94103
{
95104
const rootNode = document.getElementById("discussion-list");
96105
if (rootNode) {

webui/jsx/commit-list.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
const React = require("react");
2+
const ReactDOM = require("react-dom");
3+
4+
export default function CommitList({commits, owner, database}) {
5+
// Prepare rendered rows for commit table
6+
const commitRows = (commits === null ? null : commits.map(row => (
7+
<tr>
8+
<td>
9+
<a href={"/" + row.author_username} className="blackLink">
10+
{row.author_avatar !== "" ? <img src={row.author_avatar} height="18" width="18" style={{border: "1px solid #8c8c8c"}} /> : null}&nbsp;
11+
{row.author_name}
12+
</a>
13+
</td>
14+
<td>
15+
<a className="blackLink" href={"/diffs/" + owner + "/" + database + "?commit_a=" + row.parent + "&commit_b=" + row.id}>
16+
{row.id.substring(0, 8)}
17+
</a>
18+
</td>
19+
<td>
20+
{row.message === "" ? <span className="text-muted">This commit has no commit message</span> : row.message}
21+
{row.licence_change !== "" ? <span className="text-danger">{row.licence_change}</span> : null}
22+
</td>
23+
<td>
24+
<span title={new Date(row.timestamp).toLocaleString()}>{getTimePeriod(row.timestamp, true)}</span>
25+
</td>
26+
</tr>
27+
)));
28+
29+
return (
30+
<table className="table">
31+
<thead>
32+
<tr><th>Author</th><th>Commit ID</th><th>Commit message</th><th>Date</th></tr>
33+
</thead>
34+
<tbody>
35+
{commitRows}
36+
</tbody>
37+
</table>
38+
);
39+
}

webui/jsx/discussion-comments.js

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const React = require("react");
22
const ReactDOM = require("react-dom");
33

44
import MarkdownEditor from "./markdown-editor";
5+
import CommitList from "./commit-list";
56
import { confirmAlert } from "react-confirm-alert";
67
import "react-confirm-alert/src/react-confirm-alert.css";
78

@@ -113,32 +114,6 @@ function DiscussionTopComment({setStatusMessage, setStatusMessageColour}) {
113114
});
114115
}
115116

116-
// Prepare rendered rows for commit table for merge requests
117-
const commitRows = (mrData === null ? [] : mrData.commitList.map(function(row) {
118-
return (
119-
<tr>
120-
<td>
121-
<a href={"/" + row.author_username} className="blackLink">
122-
{row.author_avatar !== "" ? <img src={row.author_avatar} height="18" width="18" style={{border: "1px solid #8c8c8c"}} /> : null}&nbsp;
123-
{row.author_name}
124-
</a>
125-
</td>
126-
<td>
127-
<a className="blackLink" href={discussionData.open ? ("/diffs/" + discussionData.mr_details.source_owner + "/" + discussionData.mr_details.source_database_name + "?commit_a=" + row.parent + "&commit_b=" + row.id) : null}>
128-
{row.id.substring(0, 8)}
129-
</a>
130-
</td>
131-
<td>
132-
{row.message === "" ? <span className="text-muted">This commit has no commit message</span> : row.message}
133-
{row.licence_change !== "" ? <span className="text-danger">{row.licence_change}</span> : null}
134-
</td>
135-
<td>
136-
<span title={new Date(row.timestamp).toLocaleString()}>{getTimePeriod(row.timestamp, true)}</span>
137-
</td>
138-
</tr>
139-
);
140-
}));
141-
142117
return (
143118
<div className="panel panel-default">
144119
<div className="panel-heading">
@@ -187,14 +162,7 @@ function DiscussionTopComment({setStatusMessage, setStatusMessageColour}) {
187162
<h4>Commit list</h4>
188163
{discussionData.open ? <a href={"/diffs/" + discussionData.mr_details.source_owner + "/" + discussionData.mr_details.source_database_name + "?commit_a=" + mrData.commitList[mrData.commitList.length - 1].parent + "&commit_b=" + mrData.commitList[0].id}>View changes</a> : null}
189164
</div>
190-
<table className="table">
191-
<thead>
192-
<tr><th>Author</th><th>Commit ID</th><th>Commit message</th><th>Date</th></tr>
193-
</thead>
194-
<tbody>
195-
{commitRows}
196-
</tbody>
197-
</table>
165+
<CommitList commits={mrData === null ? null : mrData.commitList} owner={discussionData.mr_details.source_owner} database={discussionData.mr_details.source_database_name} />
198166
{discussionData.mr_details.state !== 1 && (discussionData.creator === authInfo.loggedInUser || meta.owner === authInfo.loggedInUser) ?
199167
<div className="panel-body">
200168
{discussionData.open === true && meta.owner === authInfo.loggedInUser && mrData.destBranchNameOk === true && mrData.destBranchUsable === true ? <><input className="btn btn-success" value="Merge the request" onClick={() => mergeRequest()} />&nbsp;</> : null}

webui/jsx/discussion-create-mr.js

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
const React = require("react");
2+
const ReactDOM = require("react-dom");
3+
4+
import MarkdownEditor from "./markdown-editor";
5+
import Select from "react-dropdown-select";
6+
import CommitList from "./commit-list";
7+
8+
export default function DiscussionCreateMr() {
9+
const [statusMessage, setStatusMessage] = React.useState("");
10+
const [statusMessageColour, setStatusMessageColour] = React.useState("");
11+
12+
const [title, setTitle] = React.useState("");
13+
const [sourceDbName, setSourceDbName] = React.useState(createMrData.sourceDbName);
14+
const [sourceDbOwner, setSourceDbOwner] = React.useState(createMrData.sourceDbOwner);
15+
const [sourceBranch, setSourceBranch] = React.useState(createMrData.sourceDbDefaultBranch);
16+
const [sourceBranches, setSourceBranches] = React.useState(createMrData.sourceBranches);
17+
const [destDbName, setDestDbName] = React.useState(createMrData.destDbName);
18+
const [destDbOwner, setDestDbOwner] = React.useState(createMrData.destDbOwner);
19+
const [destBranch, setDestBranch] = React.useState(createMrData.destDbDefaultBranch);
20+
const [destBranches, setDestBranches] = React.useState(createMrData.destBranches);
21+
const [commitList, setCommitList] = React.useState(createMrData.commitList);
22+
23+
// Updates the commit list showing the difference between source and destination databases
24+
function updateCommitList() {
25+
fetch("/x/diffcommitlist/", {
26+
method: "post",
27+
headers: {
28+
"Content-Type": "application/x-www-form-urlencoded"
29+
},
30+
body: new URLSearchParams({
31+
"destbranch": encodeURIComponent(destBranch),
32+
"destdbname": encodeURIComponent(destDbName),
33+
"destowner": encodeURIComponent(destDbOwner),
34+
"sourcebranch": encodeURIComponent(sourceBranch),
35+
"sourcedbname": encodeURIComponent(sourceDbName),
36+
"sourceowner": encodeURIComponent(sourceDbOwner),
37+
}),
38+
}).then((response) => {
39+
if (!response.ok) {
40+
return Promise.reject(response);
41+
}
42+
43+
response.json().then(data => {
44+
// Retrieving the commit list succeeded, so update the displayed commit list
45+
setCommitList(data.commit_list);
46+
47+
setStatusMessageColour("green");
48+
setStatusMessage("");
49+
});
50+
})
51+
.catch((error) => {
52+
// Retrieving the commit list failed, so clear out the existing displayed list and display a message
53+
// about it
54+
setCommitList([]);
55+
setStatusMessageColour("orange");
56+
setStatusMessage("The selected source and destination can't be merged. Please choose a different source and destination.");
57+
});
58+
}
59+
60+
// Update name of the source or destination branch in the drop down selector
61+
function changeBranch(sourceDest, newBranch) {
62+
if (sourceDest === "source") {
63+
setSourceBranch(newBranch);
64+
} else if(sourceDest === "dest") {
65+
setDestBranch(newBranch);
66+
}
67+
}
68+
69+
// Updates the source or destination database name and branch list in the drop down selectors
70+
function changeDb(sourceDest, newRow) {
71+
// Retrieve the branch list for the newly selected database
72+
fetch("/x/branchnames/", {
73+
method: "post",
74+
headers: {
75+
"Content-Type": "application/x-www-form-urlencoded"
76+
},
77+
body: new URLSearchParams({
78+
"dbname": newRow.database_name,
79+
"username": newRow.database_owner,
80+
}),
81+
}).then((response) => {
82+
if (!response.ok) {
83+
return Promise.reject(response);
84+
}
85+
86+
response.json().then(data => {
87+
// Clear any previous error message
88+
setStatusMessageColour("green");
89+
setStatusMessage("");
90+
91+
// Update the values used when sending the creation request and update the branch list
92+
if (sourceDest === "source") {
93+
setSourceDbName(newRow.database_name);
94+
setSourceDbOwner(newRow.database_owner);
95+
96+
setSourceBranches(data.branches);
97+
setSourceBranch(data.default_branch);
98+
} else if(sourceDest === "dest") {
99+
setDestDbName(newRow.database_name);
100+
setDestDbOwner(newRow.database_owner);
101+
102+
setDestBranches(data.branches);
103+
setDestBranch(data.default_branch);
104+
}
105+
});
106+
})
107+
.catch((error) => {
108+
// Retrieving the branch names failed, so display an error message
109+
setStatusMessageColour("red");
110+
setStatusMessage("Retrieving branch names for the database failed.");
111+
});
112+
}
113+
114+
// Handler for the cancel button. Just bounces back to the database page
115+
function cancelCreate() {
116+
window.location = "/" + meta.owner + "/" + meta.database;
117+
}
118+
119+
// Sends the merge request creation details, and if successful then bounces to the newly created MR for it
120+
function createMR() {
121+
if (authInfo.loggedInUser === "") {
122+
// User needs to be logged in
123+
lock.show();
124+
return;
125+
}
126+
127+
// Send the MR creation request
128+
fetch("/x/createmerge/", {
129+
method: "post",
130+
headers: {
131+
"Content-Type": "application/x-www-form-urlencoded"
132+
},
133+
body: new URLSearchParams({
134+
"desc": encodeURIComponent(document.getElementById("desc").value),
135+
"destbranch": encodeURIComponent(destBranch),
136+
"destdbname": encodeURIComponent(destDbName),
137+
"destowner": encodeURIComponent(destDbOwner),
138+
"sourcebranch": encodeURIComponent(sourceBranch),
139+
"sourcedbname": encodeURIComponent(sourceDbName),
140+
"sourceowner": encodeURIComponent(sourceDbOwner),
141+
"title": encodeURIComponent(title),
142+
}),
143+
}).then((response) => {
144+
if (!response.ok) {
145+
return Promise.reject(response);
146+
}
147+
148+
response.json().then(data => {
149+
// MR creation succeeded. The response should include the MR # we'll bounce to
150+
window.location = "/merge/" + destDbOwner + "/" + destDbName + "?id=" + data.mr_id;
151+
});
152+
})
153+
.catch((error) => {
154+
// Creating the MR failed, so display an error message
155+
error.text().then(text => {
156+
setStatusMessageColour("red");
157+
setStatusMessage("Merge Request creation failed: " + text);
158+
});
159+
});
160+
};
161+
162+
// Update commit list when branches change
163+
React.useEffect(() => {
164+
updateCommitList();
165+
}, [sourceBranch, destBranch]);
166+
167+
const dbListData = createMrData.forkList.map(f => new Object({name: f.database_owner + "/" + f.database_name, database_owner: f.database_owner, database_name: f.database_name}));
168+
const sourceBranchListData = sourceBranches.map(b => new Object({name: b}));
169+
const destBranchListData = destBranches.map(b => new Object({name: b}));
170+
171+
return (<>
172+
<h3 className="text-center">Create a Merge Request</h3>
173+
{statusMessage !== "" ? (
174+
<div className="row">
175+
<div className="col-md-12 text-center">
176+
<div style={{paddingBottom: "1em"}}>
177+
<h4 style={{color: statusMessageColour}}>{statusMessage}</h4>
178+
</div>
179+
</div>
180+
</div>
181+
) : null}
182+
<form>
183+
<div className="form-group">
184+
<label for="title">Title</label>
185+
<input type="text" className="form-control" id="title" placeholder="Please fill in a title for the new merge request" maxlength={80} value={title} onChange={e => setTitle(e.target.value)} required />
186+
</div>
187+
<div className="form-group">
188+
<label for="sourcedb">Source database</label>
189+
<Select name="sourcedb" required={true} labelField="name" valueField="name" onChange={(values) => changeDb("source", values[0])} options={dbListData} values={[{name: sourceDbOwner + "/" + sourceDbName}]} />
190+
<p className="help-block">Where the new data is coming from</p>
191+
</div>
192+
<div className="form-group">
193+
<label for="sourcebranch">Source branch</label>
194+
<Select name="sourcebranch" required={true} labelField="name" valueField="name" onChange={(values) => setSourceBranch(values[0].name)} options={sourceBranchListData} values={[{name: sourceBranch}]} />
195+
<p className="help-block">The branch in the source database to use</p>
196+
</div>
197+
<div className="form-group">
198+
<label for="destdb">Destination database</label>
199+
<Select name="destdb" required={true} labelField="name" valueField="name" onChange={(values) => changeDb("dest", values[0])} options={dbListData} values={[{name: destDbOwner + "/" + destDbName}]} />
200+
<p className="help-block">Where you'd like the data merged into</p>
201+
</div>
202+
<div className="form-group">
203+
<label for="destbranch">Destination branch</label>
204+
<Select name="destbranch" required={true} labelField="name" valueField="name" onChange={(values) => setDestBranch(values[0].name)} options={destBranchListData} values={[{name: destBranch}]} />
205+
<p className="help-block">The target branch in the destination database</p>
206+
</div>
207+
<div className="form-group">
208+
<label for="desc">Description</label>
209+
<MarkdownEditor editorId="desc" rows={10} placeholder="Please add a summary for this merge request, describing what the new or changed data is for" />
210+
<p className="help-block">The purpose of this merge request</p>
211+
</div>
212+
<button type="button" className="btn btn-success" onClick={() => createMR()}>Create</button>&nbsp;
213+
<button type="button" className="btn btn-default" onClick={() => cancelCreate()}>Cancel</button>
214+
</form>
215+
<div className="panel panel-default" style={{marginTop: "1em"}}>
216+
<div className="panel-heading">
217+
Changes between the source and destination
218+
</div>
219+
<CommitList commits={commitList} owner={sourceDbOwner} database={sourceDbName} />
220+
</div>
221+
</>);
222+
}

webui/main.go

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -747,19 +747,6 @@ func createMergeHandler(w http.ResponseWriter, r *http.Request) {
747747
return
748748
}
749749

750-
// Extract and validate the form variables
751-
userName, err := com.GetUsername(r, false)
752-
if err != nil {
753-
w.WriteHeader(http.StatusUnauthorized)
754-
fmt.Fprint(w, err.Error())
755-
return
756-
}
757-
if userName == "" {
758-
w.WriteHeader(http.StatusBadRequest)
759-
fmt.Fprint(w, "Missing username in supplied fields")
760-
return
761-
}
762-
763750
// Retrieve source owner
764751
o := r.PostFormValue("sourceowner")
765752
srcOwner, err := url.QueryUnescape(o)

0 commit comments

Comments
 (0)