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

Commit 29b4dc6

Browse files
committed
webui: Migrate discussion and MR list pages to React
1 parent 0f57a4d commit 29b4dc6

File tree

6 files changed

+98
-203
lines changed

6 files changed

+98
-203
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ webui/js/database-tags.js
5858
webui/js/database-view.js
5959
webui/js/database-watchers.js
6060
webui/js/db-header.js
61+
webui/js/discussion-list.js
6162
webui/js/markdown-editor.js
6263

6364
# Local secrets

cypress/e2e/1-webui/auth0_dialog.cy.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ describe('ensure auth0 dialog is available on all pages', () => {
5656

5757
it('discussion page', () => {
5858
cy.visit('discuss/default/Assembly Election 2017.sqlite')
59-
cy.get('[data-cy="nodisc"]').should('contain.text', 'This database doesn\'t have any discussions yet')
59+
cy.get('[data-cy="nodisc"]').should('contain.text', 'This database does not have any discussions yet')
6060
cy.get('[data-cy="loginlnk"]').click()
6161
cy.get('.auth0-lock-name').should('contain.text', 'Auth0')
6262
})

webui/jsx/app.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import DatabaseTags from "./database-tags";
1010
import DatabaseView from "./database-view";
1111
import DatabaseWatchers from "./database-watchers";
1212
import DbHeader from "./db-header";
13+
import DiscussionList from "./discussion-list";
1314
import MarkdownEditor from "./markdown-editor";
1415

1516
{
@@ -72,6 +73,16 @@ import MarkdownEditor from "./markdown-editor";
7273
}
7374
}
7475

76+
{
77+
const rootNode = document.getElementById("discussion-list");
78+
if (rootNode) {
79+
const mergeRequests = rootNode.dataset.mergeRequests;
80+
81+
const root = ReactDOM.createRoot(rootNode);
82+
root.render(<DiscussionList mergeRequests={mergeRequests} />);
83+
}
84+
}
85+
7586
{
7687
document.querySelectorAll(".markdown-editor").forEach((rootNode) => {
7788
const editorId = rootNode.dataset.id;

webui/jsx/discussion-list.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
const React = require("react");
2+
const ReactDOM = require("react-dom");
3+
4+
function DiscussionListRow({data, mergeRequests}) {
5+
return (
6+
<tr>
7+
<td width="80px">
8+
<div className="pull-right" style={{paddingTop: "6px"}}>
9+
{data.open === true ? <i className="fa fa-minus-square-o fa-lg text-success" /> : <i className="fa fa fa-check-square-o fa-lg text-danger" />}
10+
</div>
11+
<div style={{paddingTop: "6px"}}># {data.disc_id}</div>
12+
</td>
13+
<td>
14+
<span className="lead"><a className="blackLink" href={"/" + (mergeRequests ? "merge" : "discuss") + "/" + meta.owner + "/" + meta.database + "?id=" + data.disc_id}>{data.title}</a></span>
15+
<div>
16+
Created <span className="text-info" title={new Date(data.creation_date).toLocaleString()}>{getTimePeriod(data.creation_date, true)}</span> by <a className="blackLink" href={"/" + data.creator}>{data.avatar_url !== "" ? <img src={data.avatar_url} style={{verticalAlign: "top", border: "1px solid #8c8c8c"}} height="18" width="18" /> : null} {data.creator}</a>. Last modified <span className="text-info" title={new Date(data.last_modified).toLocaleString()}>{getTimePeriod(data.last_modified, true)}</span>
17+
18+
{data.comment_count > 0 ? <span> <i className="fa fa-comment-o"></i> <a className="blackLink" href={"/" + (mergeRequests ? "merge" : "discuss") + "/" + meta.owner + "/" + meta.database + "?id=" + data.disc_id}>{data.comment_count} comment{data.comment_count > 1 ? "s" : ""}</a></span> : null}
19+
</div>
20+
</td>
21+
</tr>
22+
);
23+
}
24+
25+
export default function DiscussionList({mergeRequests}) {
26+
const [showOpen, setShowOpen] = React.useState(true);
27+
28+
// Switch to the create discussion page
29+
function createDiscussion() {
30+
if (authInfo.loggedInUser) {
31+
window.location = "/" + (mergeRequests ? "compare" : "creatediscuss") + "/" + meta.owner + "/" + meta.database;
32+
} else {
33+
// User needs to be logged in
34+
lock.show();
35+
}
36+
}
37+
38+
// Button row at the top
39+
const buttonRow = (
40+
<div className="row">
41+
<div className="col-md-12">
42+
<div className="text-center">
43+
<button className="btn btn-success" onClick={() => createDiscussion()}>{mergeRequests ? "New Merge Request" : "Start a new discussion"}</button>
44+
&nbsp;
45+
<div className="btn-group" data-toggle="buttons">
46+
<label className={"btn btn-default " + (showOpen ? "active" : null)} onClick={() => setShowOpen(true)}>Open</label>
47+
<label className={"btn btn-default " + (showOpen ? null : "active")} onClick={() => setShowOpen(false)}>Closed</label>
48+
</div>
49+
</div>
50+
</div>
51+
</div>
52+
);
53+
54+
// Special case of no discussions at all
55+
if (discussionData === null) {
56+
return <>{buttonRow}<h4 data-cy="nodisc" className="text-center">This database does not have any {mergeRequests ? "merge requests" : "discussions"} yet</h4></>;
57+
}
58+
59+
// Render discussion items
60+
const rows = discussionData
61+
.filter(item => item.open === showOpen)
62+
.map(item => DiscussionListRow({mergeRequests: mergeRequests, data: item}));
63+
64+
// If no discussions are visible in the current selection print a message
65+
if (rows.length === 0) {
66+
return <>{buttonRow}<h4 data-cy="nodisc" className="text-center">This database does not have any {showOpen ? "open" : "closed"} {mergeRequests ? "merge requests" : "discussions"} yet</h4></>;
67+
}
68+
69+
return (<>
70+
{buttonRow}
71+
<table className="table table-striped table-responsive" style={{marginTop: "1em"}}>
72+
<tbody>
73+
{rows}
74+
</tbody>
75+
</table>
76+
</>);
77+
}

webui/templates/discussionlist.html

Lines changed: 4 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,115 +1,18 @@
11
[[ define "discussListPage" ]]
22
<!doctype html>
3-
<html ng-app="DBHub" ng-controller="discussListView">
3+
<html>
44
[[ template "head" . ]]
55
<body>
66
[[ template "header" . ]]
77
<div>
88
<div id="db-header-root"></div>
9-
<div class="row">
10-
<div class="col-md-12">
11-
<div style="text-align: center; margin-top: 1%; margin-bottom: 1%">
12-
<button class="btn btn-success" ng-click="createDiscussion()">Start a new discussion</button>
13-
<div class="btn-group">
14-
<label class="btn btn-default" ng-model="radioOpen" ng-click="openClick('true')" uib-btn-radio="true">Open</label>
15-
<label class="btn btn-default" ng-model="radioOpen" ng-click="openClick('false')" uib-btn-radio="false">Closed</label>
16-
</div>
17-
</div>
18-
</div>
19-
</div>
20-
<div class="row">
21-
<div class="col-md-12" style="padding-bottom: 10px;">
22-
<table ng-if="(DiscussionList != '') && (((radioOpen === false) && (discussionCount.closed !== 0)) || ((radioOpen === true) && (discussionCount.open !== 0)))" class="table table-striped table-responsive settingsTable">
23-
<tbody>
24-
<tr ng-repeat="row in DiscussionList" ng-if="row.open == radioOpen">
25-
<td style="border-style: none;" width="80px">
26-
<div class="pull-right" style="padding-top: 6px;">
27-
<i ng-if="row.open === true" class="fa fa-minus-square-o fa-lg text-success"></i>
28-
<i ng-if="row.open === false" class="fa fa fa-check-square-o fa-lg text-danger"></i>
29-
</div>
30-
<div style="padding-top: 6px;"># {{ row.disc_id }}</div>
31-
</td>
32-
<td style="border-style: none;">
33-
<a href="/discuss/[[ .DB.Info.Owner ]]/[[ .DB.Info.Database ]]?id={{ row.disc_id }}" style="font-size: x-large; color: #333;">{{ row.title }}</a>
34-
<div>
35-
Created <span title="{{ row.creation_date | date : 'medium' }}" style="color: grey;">{{ getTimePeriodTxt(row.creation_date, true) }}</span> by <a class="blackLink" href="/{{ row.creator }}"><img ng-if="row.avatar_url != ''" ng-attr-src="{{ decodeAmp(row.avatar_url) }}" style="vertical-align: top; border: 1px solid #8c8c8c;" height="18" width="18"/> {{ row.creator }}</a>. Last modified <span title="{{ row.last_modified | date : 'medium' }}" style="color: grey;">{{ getTimePeriodTxt(row.last_modified, true) }}</span>
36-
<span ng-if="row.comment_count > 0"><i class="fa fa-comment-o"></i> <a class="blackLink" href="/discuss/[[ .DB.Info.Owner ]]/[[ .DB.Info.Database ]]?id={{ row.disc_id }}">{{ row.comment_count }} comment<span ng-if="row.comment_count > 1">s</span></a></span>
37-
</div>
38-
</td>
39-
</tr>
40-
</tbody>
41-
</table>
42-
<div ng-if="DiscussionList === null" style="text-align: center; font-size:large;" data-cy="nodisc"><i>This database doesn't have any discussions yet</i></div>
43-
<div ng-if="(radioOpen === false) && (DiscussionList !== null) && (discussionCount.closed === 0)" style="text-align: center; font-size:large;"><i>This database doesn't have any closed discussions</i></div>
44-
<div ng-if="(radioOpen === true) && (DiscussionList !== null) && (discussionCount.open === 0)" style="text-align: center; font-size:large;"><i>This database doesn't have any open discussions</i></div>
45-
</div>
46-
</div>
9+
<div id="discussion-list"></div>
4710
</div>
4811
[[ template "script_db_header" . ]]
49-
[[ template "footer" . ]]
5012
<script>
51-
let app = angular.module('DBHub', ['ui.bootstrap', 'ngSanitize']);
52-
app.controller('discussListView', function($scope, $http) {
53-
// Pre-filled data
54-
$scope.meta = {
55-
Database: "[[ .DB.Info.Database ]]",
56-
Owner: "[[ .DB.Info.Owner ]]",
57-
[[ if .PageMeta.LoggedInUser ]]
58-
Loggedin: "true",
59-
[[ else ]]
60-
Loggedin: "false",
61-
[[ end ]]
62-
}
63-
64-
$scope.DiscussionList = [[ .DiscussionList ]];
65-
66-
// Count the number of open and closed discussions, so we can display an appropriate placeholder when one of
67-
// them has 0 (eg no open discussions, or no closed discussions)
68-
$scope.discussionCount = {"open": 0, "closed": 0};
69-
if ($scope.DiscussionList !== null) {
70-
let numDisc = $scope.DiscussionList.length;
71-
for (let i = 0; i < numDisc; i++) {
72-
if ($scope.DiscussionList[i].open) {
73-
$scope.discussionCount.open += 1;
74-
} else {
75-
$scope.discussionCount.closed += 1;
76-
}
77-
}
78-
}
79-
80-
// Switch to the create discussion page
81-
$scope.createDiscussion = function() {
82-
if ($scope.meta.Loggedin !== "true") {
83-
// User needs to be logged in
84-
lock.show();
85-
} else {
86-
window.location = '/creatediscuss/[[ .DB.Info.Owner ]]/[[ .DB.Info.Database ]]';
87-
}
88-
};
89-
90-
// Change \u0026 to &
91-
$scope.decodeAmp = function(str) {
92-
return decodeURIComponent(str);
93-
};
94-
95-
// Returns a nicely presented "time elapsed" string
96-
$scope.getTimePeriodTxt = function(date1, includeOn) {
97-
return getTimePeriod(date1, includeOn)
98-
};
99-
100-
// Switch between showing open vs closed discussions
101-
$scope.radioOpen = true;
102-
$scope.openClick = function(newValue) {
103-
if (newValue === "true") {
104-
// Only display open discussions
105-
$scope.radioOpen = true;
106-
} else {
107-
// Only display closed discussions
108-
$scope.radioOpen = false;
109-
}
110-
};
111-
});
13+
const discussionData = [[ .DiscussionList ]];
11214
</script>
15+
[[ template "footer" . ]]
11316
</body>
11417
</html>
11518
[[ end ]]

webui/templates/mergerequestlist.html

Lines changed: 4 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,115 +1,18 @@
11
[[ define "mergeRequestListPage" ]]
22
<!doctype html>
3-
<html ng-app="DBHub" ng-controller="mergeRequestListView">
3+
<html>
44
[[ template "head" . ]]
55
<body>
66
[[ template "header" . ]]
77
<div>
88
<div id="db-header-root"></div>
9-
<div class="row">
10-
<div class="col-md-12">
11-
<div style="text-align: center; margin-top: 1%; margin-bottom: 1%">
12-
<button class="btn btn-success" ng-click="createMR()">New Merge Request</button>
13-
<div class="btn-group">
14-
<label class="btn btn-default" ng-model="radioOpen" ng-click="openClick('true')" uib-btn-radio="true">Open</label>
15-
<label class="btn btn-default" ng-model="radioOpen" ng-click="openClick('false')" uib-btn-radio="false">Closed</label>
16-
</div>
17-
</div>
18-
</div>
19-
</div>
20-
<div class="row">
21-
<div class="col-md-12" style="padding-bottom: 10px;">
22-
<table ng-if="(MRList != '') && (((radioOpen === false) && (MRCount.closed !== 0)) || ((radioOpen === true) && (MRCount.open !== 0)))" class="table table-striped table-responsive settingsTable">
23-
<tbody>
24-
<tr ng-repeat="row in MRList" ng-if="row.open == radioOpen">
25-
<td style="border-style: none;" width="80px">
26-
<div class="pull-right" style="padding-top: 6px;">
27-
<i ng-if="row.open === true" class="fa fa-minus-square-o fa-lg text-success"></i>
28-
<i ng-if="row.open === false" class="fa fa fa-check-square-o fa-lg text-danger"></i>
29-
</div>
30-
<div style="padding-top: 6px;"># {{ row.disc_id }}</div>
31-
</td>
32-
<td style="border-style: none;">
33-
<a href="/merge/[[ .DB.Info.Owner ]]/[[ .DB.Info.Database ]]?id={{ row.disc_id }}" style="font-size: x-large; color: #333;">{{ row.title }}</a>
34-
<div>
35-
Created <span title="{{ row.creation_date | date : 'medium' }}" style="color: grey;">{{ getTimePeriodTxt(row.creation_date, true) }}</span> by <a class="blackLink" href="/{{ row.creator }}"><img ng-if="row.avatar_url != ''" ng-attr-src="{{ decodeAmp(row.avatar_url) }}" style="vertical-align: top; border: 1px solid #8c8c8c;" height="18" width="18"/> {{ row.creator }}</a>. Last modified <span title="{{ row.last_modified | date : 'medium' }}" style="color: grey;">{{ getTimePeriodTxt(row.last_modified, true) }}</span>
36-
<span ng-if="row.comment_count > 0"><i class="fa fa-comment-o"></i> <a class="blackLink" href="/merge/[[ .DB.Info.Owner ]]/[[ .DB.Info.Database ]]?id={{ row.disc_id }}">{{ row.comment_count }} comment<span ng-if="row.comment_count > 1">s</span></a></span>
37-
</div>
38-
</td>
39-
</tr>
40-
</tbody>
41-
</table>
42-
<div ng-if="MRList === null" style="text-align: center; font-size:large; padding-bottom: 20px;"><i>This database doesn't have any merge requests yet</i></div>
43-
<div ng-if="(radioOpen === false) && (MRList !== null) && (MRCount.closed === 0)" style="text-align: center; font-size:large; padding-bottom: 20px;"><i>This database doesn't have any closed merge requests</i></div>
44-
<div ng-if="(radioOpen === true) && (MRList !== null) && (MRCount.open === 0)" style="text-align: center; font-size:large; padding-bottom: 20px;"><i>This database doesn't have any open merge requests</i></div>
45-
</div>
46-
</div>
9+
<div id="discussion-list" data-merge-requests="1"></div>
4710
</div>
4811
[[ template "script_db_header" . ]]
49-
[[ template "footer" . ]]
5012
<script>
51-
let app = angular.module('DBHub', ['ui.bootstrap', 'ngSanitize']);
52-
app.controller('mergeRequestListView', function($scope, $http) {
53-
// Pre-filled data
54-
$scope.meta = {
55-
Database: "[[ .DB.Info.Database ]]",
56-
Owner: "[[ .DB.Info.Owner ]]",
57-
[[ if .PageMeta.LoggedInUser ]]
58-
Loggedin: "true",
59-
[[ else ]]
60-
Loggedin: "false",
61-
[[ end ]]
62-
}
63-
64-
$scope.MRList = [[ .MRList ]];
65-
66-
// Count the number of open and closed MRs, so we can display an appropriate placeholder when one of them has
67-
// 0 (eg no open MRs, or no closed MRs)
68-
$scope.MRCount = {"open": 0, "closed": 0};
69-
if ($scope.MRList !== null) {
70-
let numDisc = $scope.MRList.length;
71-
for (let i = 0; i < numDisc; i++) {
72-
if ($scope.MRList[i].open) {
73-
$scope.MRCount.open += 1;
74-
} else {
75-
$scope.MRCount.closed += 1;
76-
}
77-
}
78-
}
79-
80-
// Switch to the create MR page
81-
$scope.createMR = function() {
82-
if ($scope.meta.Loggedin !== "true") {
83-
// User needs to be logged in
84-
lock.show();
85-
} else {
86-
window.location = '/compare/[[ .DB.Info.Owner ]]/[[ .DB.Info.Database ]]';
87-
}
88-
};
89-
90-
// Change \u0026 to &
91-
$scope.decodeAmp = function(str) {
92-
return decodeURIComponent(str);
93-
};
94-
95-
// Returns a nicely presented "time elapsed" string
96-
$scope.getTimePeriodTxt = function(date1, includeOn) {
97-
return getTimePeriod(date1, includeOn)
98-
};
99-
100-
// Switch between showing open vs closed MRs
101-
$scope.radioOpen = true;
102-
$scope.openClick = function(newValue) {
103-
if (newValue === "true") {
104-
// Only display open MRs
105-
$scope.radioOpen = true;
106-
} else {
107-
// Only display closed MRs
108-
$scope.radioOpen = false;
109-
}
110-
};
111-
});
13+
const discussionData = [[ .MRList ]];
11214
</script>
15+
[[ template "footer" . ]]
11316
</body>
11417
</html>
11518
[[ end ]]

0 commit comments

Comments
 (0)