Skip to content

Commit 4db3840

Browse files
committed
DRAFT: Frontend refactoring for context and portal
Frontend updates for admin portal-edit and portal-list are working React context used to track active dashboard and project, instead of browser local storage. Dashboard, IbutsuHeader, IbutsuPage rewritten for using the context. Static routes set for project dashboard/runs/results/reportbuilder using UUID. Page component state needs to be updated in some areas when navigation orginates from the URL with params set and not component selection. Project selection and dashboard selection are hooked up to the routing with ugly event handler prop passing. Moving these to functional components will allow using useEffect with dependencies instead to control interaction between these components In progress: sidebar moved into new functional component, project views need to be accounted for in component state or in the context itself. Now when a project is not selected, there is no page rendered (no sidebar). This will need an empty state page to look good, something easy just saying to select a project or portal.
1 parent 3d78ab7 commit 4db3840

File tree

20 files changed

+1270
-222
lines changed

20 files changed

+1270
-222
lines changed

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ repos:
1111
- id: debug-statements
1212

1313
- repo: https://github.com/astral-sh/ruff-pre-commit
14-
rev: v0.4.8
14+
rev: v0.5.2
1515
hooks:
1616
- id: ruff
1717
args:
@@ -31,7 +31,7 @@ repos:
3131

3232
## ES
3333
- repo: https://github.com/pre-commit/mirrors-eslint
34-
rev: v9.4.0
34+
rev: v9.7.0
3535
hooks:
3636
- id: eslint
3737
additional_dependencies:

backend/ibutsu_server/controllers/admin/portal_controller.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ def admin_update_portal(id_, portal=None, body=None, token_info=None, user=None)
132132
portal.update(portal_dict)
133133
session.add(portal)
134134
session.commit()
135-
return portal.to_dict()
135+
return portal.to_dict(), HTTPStatus.OK
136136
else:
137137
abort(HTTPStatus.NOT_FOUND)
138138

frontend/eslint.config.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// eslint.config.js
2+
export default [
3+
{
4+
rules: {
5+
"no-unused-vars": "warn", // this isn't actually working through pre-commit or webpack
6+
}
7+
}
8+
];

frontend/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
"@babel/plugin-syntax-jsx": "^7.24.7",
1010
"@babel/plugin-transform-class-properties": "^7.24.7",
1111
"@babel/plugin-transform-private-methods": "^7.24.7",
12+
"@babel/plugin-transform-private-property-in-object": "^7.24.7",
13+
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
1214
"@babel/preset-flow": "^7.24.7",
1315
"@babel/preset-react": "^7.24.7",
1416
"@greatsumini/react-facebook-login": "^3.3.3",
@@ -44,6 +46,7 @@
4446
"typescript": "^4.9.5",
4547
"wolfy87-eventemitter": "^5.2.9"
4648
},
49+
"devDependencies": {"@babel/plugin-proposal-private-property-in-object": "^7.21.11"},
4750
"scripts": {
4851
"start": "serve -s build -l tcp://0.0.0.0:8080",
4952
"build": "./bin/write-version-file.js && react-scripts build",

frontend/src/admin.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ import { UserEdit } from './pages/admin/user-edit';
1616
import { ProjectList } from './pages/admin/project-list';
1717
import { ProjectEdit } from './pages/admin/project-edit';
1818
import { AuthService } from './services/auth';
19+
import { PortalList } from './pages/admin/portal-list';
20+
import { PortalEdit } from './pages/admin/portal-edit';
21+
1922
import './app.css';
2023

2124

@@ -46,6 +49,9 @@ export class Admin extends React.Component {
4649
<li className="pf-v5-c-nav__item">
4750
<NavLink to="/admin/projects" className="pf-v5-c-nav__link">Projects</NavLink>
4851
</li>
52+
<li className="pf-v5-c-nav__item">
53+
<NavLink to="/admin/portals" className="pf-v5-c-nav__link">Portals</NavLink>
54+
</li>
4955
</NavList>
5056
</Nav>
5157
);
@@ -59,6 +65,11 @@ export class Admin extends React.Component {
5965
<Route path="/users/:id" element={<ElementWrapper routeElement={UserEdit} />} />
6066
<Route path="/projects" element={<ElementWrapper routeElement={ProjectList} />} />
6167
<Route path="/projects/:id" element={<ElementWrapper routeElement={ProjectEdit} />} />
68+
<Route path="/portals" element={<ElementWrapper routeElement={PortalList} />} />
69+
<Route path="/portals/:id" element={<ElementWrapper routeElement={PortalEdit} />} />
70+
{/*
71+
TODO: route for admin portal/ID
72+
*/}
6273
</Routes>
6374
</IbutsuPage>
6475
</React.Fragment>

frontend/src/app.js

Lines changed: 68 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,24 @@
11
import React from 'react';
22

3-
import {
4-
Nav,
5-
NavList
6-
} from '@patternfly/react-core';
73
import EventEmitter from 'wolfy87-eventemitter';
84
import ElementWrapper from './components/elementWrapper';
95

10-
import { NavLink, Route, Routes } from 'react-router-dom';
6+
import { Route, Routes } from 'react-router-dom';
117

128
import { Dashboard } from './dashboard';
139
import { ReportBuilder } from './report-builder';
1410
import { RunList } from './run-list';
1511
import { Run } from './run';
1612
import { ResultList } from './result-list';
1713
import { Result } from './result';
18-
import { Settings } from './settings';
1914
import { View, IbutsuPage } from './components';
20-
import { HttpClient } from './services/http';
21-
import { getActiveProject } from './utilities';
15+
import { IbutsuContext } from './services/context';
16+
2217
import './app.css';
2318

2419

2520
export class App extends React.Component {
21+
static contextType = IbutsuContext;
2622
constructor(props) {
2723
super(props);
2824
this.eventEmitter = new EventEmitter();
@@ -33,79 +29,78 @@ export class App extends React.Component {
3329
searchValue: '',
3430
views: []
3531
};
36-
this.eventEmitter.on('projectChange', () => {
37-
this.getViews();
38-
});
39-
}
40-
41-
getViews() {
42-
let params = {'filter': ['type=view', 'navigable=true']};
43-
let project = getActiveProject();
44-
if (project) {
45-
params['filter'].push('project_id=' + project.id);
46-
}
47-
HttpClient.get([Settings.serverUrl, 'widget-config'], params)
48-
.then(response => HttpClient.handleResponse(response))
49-
.then(data => {
50-
data.widgets.forEach(widget => {
51-
if (project) {
52-
widget.params['project'] = project.id;
53-
}
54-
else {
55-
delete widget.params['project'];
56-
}
57-
});
58-
this.setState({views: data.widgets});
59-
});
6032
}
6133

6234
componentDidMount() {
63-
this.getViews();
6435
}
6536

6637
render() {
6738
document.title = 'Ibutsu';
68-
const { views } = this.state;
69-
const navigation = (
70-
<Nav onSelect={this.onNavSelect} theme="dark" aria-label="Nav">
71-
<NavList>
72-
<li className="pf-v5-c-nav__item">
73-
<NavLink to="/" className="pf-v5-c-nav__link">Dashboard</NavLink>
74-
</li>
75-
<li className="pf-v5-c-nav__item">
76-
<NavLink to="/runs" className="pf-v5-c-nav__link">Runs</NavLink>
77-
</li>
78-
<li className="pf-v5-c-nav__item">
79-
<NavLink to="/results" className="pf-v5-c-nav__link">Test Results</NavLink>
80-
</li>
81-
<li className="pf-v5-c-nav__item">
82-
<NavLink to="/reports" className="pf-v5-c-nav__link">Report Builder</NavLink>
83-
</li>
84-
{views && views.map(view => (
85-
view.widget !== "jenkins-analysis-view" && (
86-
<li className="pf-v5-c-nav__item" key={view.id}>
87-
<NavLink to={`/view/${view.id}`} className="pf-v5-c-nav__link">{view.title}</NavLink>
88-
</li>
89-
)
90-
))}
91-
</NavList>
92-
</Nav>
93-
);
94-
9539
return (
96-
<React.Fragment>
97-
<IbutsuPage eventEmitter={this.eventEmitter} navigation={navigation}>
98-
<Routes>
99-
<Route path="*" element={<Dashboard eventEmitter={this.eventEmitter} />} />
100-
<Route path="/runs" element={<ElementWrapper routeElement={RunList} eventEmitter={this.eventEmitter} />} />
101-
<Route path="/results" element={<ElementWrapper routeElement={ResultList} eventEmitter={this.eventEmitter} />} />
102-
<Route path="/reports" element={<ElementWrapper routeElement={ReportBuilder} eventEmitter={this.eventEmitter} />} />
103-
<Route path="/runs/:id" element={<ElementWrapper routeElement={Run} />} />
104-
<Route path="/results/:id" element={<ElementWrapper routeElement={Result} />} />
105-
<Route path="/view/:id" element={<ElementWrapper routeElement={View} />} />
106-
</Routes>
107-
</IbutsuPage>
108-
</React.Fragment>
40+
<Routes>
41+
<Route
42+
path=""
43+
element={<IbutsuPage eventEmitter={this.eventEmitter} />}
44+
>
45+
46+
{/* Nested project routes */}
47+
<Route
48+
path=":project_id/dashboard"
49+
element={
50+
<ElementWrapper routeElement={Dashboard} eventEmitter={this.eventEmitter} />
51+
}
52+
/>
53+
<Route
54+
path=":project_id/dashboard/:dashboard_id"
55+
element={
56+
<ElementWrapper routeElement={Dashboard} eventEmitter={this.eventEmitter} />
57+
}
58+
/>
59+
<Route
60+
path=":project_id/runs"
61+
element={
62+
<ElementWrapper routeElement={RunList} eventEmitter={this.eventEmitter} />
63+
}
64+
/>
65+
<Route
66+
path=":project_id/runs/:run_id"
67+
element={<ElementWrapper routeElement={Run} />}
68+
/>
69+
<Route
70+
path=":project_id/results"
71+
element={<ElementWrapper routeElement={ResultList} eventEmitter={this.eventEmitter} />}
72+
/>
73+
<Route
74+
path=":project_id/results/:result_id"
75+
element={<ElementWrapper routeElement={Result} />}
76+
/>
77+
<Route
78+
path=":project_id/reports"
79+
element={<ElementWrapper routeElement={ReportBuilder} eventEmitter={this.eventEmitter} />}
80+
/>
81+
<Route
82+
path=":project_id/view/:view_id"
83+
element={<ElementWrapper routeElement={View} />}
84+
/>
85+
{/* <Route path="*" element={<Navigate to="project" replace />} /> */}
86+
</Route>
87+
88+
{/* Nested Portal routes */}
89+
{/* <Route
90+
path="/portal/*"
91+
element={<ElementWrapper routeElement={Dashboard} eventEmitter={this.eventEmitter} />}
92+
/>
93+
<Route
94+
path="/portal/:portal_id"
95+
element={<ElementWrapper routeElement={Dashboard} eventEmitter={this.eventEmitter} />}
96+
/>
97+
<Route
98+
path="/portal/:portal_id/dashboard/:dashboard_id"
99+
element={<ElementWrapper routeElement={Dashboard} eventEmitter={this.eventEmitter} />}
100+
/> */}
101+
102+
103+
</Routes>
109104
);
110105
}
111106
}

frontend/src/base.js

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,28 +10,32 @@ import { ForgotPassword } from './forgot-password';
1010
import { ResetPassword } from './reset-password';
1111
import { AuthService } from './services/auth';
1212
import ElementWrapper from './components/elementWrapper';
13+
import { IbutsuContextProvider } from './services/context';
1314

1415
export const Base = () => {
1516
return (
16-
<Router>
17-
<Routes>
18-
<Route path="/login" element={<ElementWrapper routeElement={Login} />} />
19-
<Route path="/sign-up" element={<ElementWrapper routeElement={SignUp} />} />
20-
<Route path="/forgot-password" element={<ElementWrapper routeElement={ForgotPassword} />} />
21-
<Route path="/reset-password/:activationCode" element={<ElementWrapper routeElement={ResetPassword} />} />
22-
<Route
23-
path="/profile/*"
24-
element={AuthService.isLoggedIn() ? <Profile /> : <Navigate to="/login" />}
25-
/>
26-
<Route
27-
path="/admin/*"
28-
element={AuthService.isLoggedIn() ? <Admin /> : <Navigate to="/" />}
29-
/>
30-
<Route
31-
path="*"
32-
element={AuthService.isLoggedIn() ? <App /> : <Navigate to="/login" />}
33-
/>
34-
</Routes>
35-
</Router>
17+
<IbutsuContextProvider>
18+
<Router>
19+
<Routes>
20+
<Route path="login" element={<ElementWrapper routeElement={Login} />} />
21+
<Route path="sign-up" element={<ElementWrapper routeElement={SignUp} />} />
22+
<Route path="forgot-password" element={<ElementWrapper routeElement={ForgotPassword} />} />
23+
<Route path="reset-password/:activationCode" element={<ElementWrapper routeElement={ResetPassword} />} />
24+
<Route
25+
path="profile"
26+
element={AuthService.isLoggedIn() ? <Profile /> : <Navigate to="/login" />}
27+
/>
28+
<Route
29+
path="admin"
30+
element={AuthService.isLoggedIn() ? <Admin /> : <Navigate to="/login" />}
31+
/>
32+
<Route
33+
path="project/*"
34+
element={AuthService.isLoggedIn() ? <App /> : <Navigate to="/login" />}
35+
/>
36+
<Route path="*" element={<Navigate to="project" replace />} />
37+
</Routes>
38+
</Router>
39+
</IbutsuContextProvider>
3640
);
3741
};

frontend/src/components/elementWrapper.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const ElementWrapper = (props) => {
1111
const Element = props.routeElement;
1212
const eventEmitter = props.eventEmitter
1313

14-
return <Element eventEmitter={eventEmitter} location={location} navigate={navigate} params={params} {...props} />;
14+
return <Element eventEmitter={eventEmitter} location={location} navigate={navigate} params={params} />;
1515
};
1616

1717
ElementWrapper.propTypes = {

0 commit comments

Comments
 (0)