Skip to content

Commit c9a6134

Browse files
jeremy-davis-sonarsourcesonartech
authored andcommitted
SONAR-26498 Migrate system page (#3939)
GitOrigin-RevId: 777c195e7627863dc5032ca44edec777dee50053
1 parent 6eca48c commit c9a6134

File tree

23 files changed

+635
-237
lines changed

23 files changed

+635
-237
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* SonarQube
3+
* Copyright (C) 2009-2025 SonarSource Sàrl
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
21+
import { mockReactComponent } from '~shared/helpers/test-utils';
22+
23+
export const GlobalFooter = mockReactComponent('GlobalFooter');

apps/sq-server/jest.config.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ module.exports = {
4343
moduleNameMapper: {
4444
...baseConfig.projectConfig.moduleNameMapper,
4545

46+
// mock global footer to speed up tests
47+
'~adapters/components/layout/GlobalFooter':
48+
'<rootDir>/apps/sq-server/__mocks__/GlobalFooter.tsx',
49+
4650
// adapters aliases
4751
'~adapters/(.+)': '<rootDir>/libs/sq-server-commons/src/sq-server-adapters/$1',
4852

apps/sq-server/src/main/js/app/components/AdminContainer.tsx

Lines changed: 89 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,15 @@
1818
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1919
*/
2020

21+
import styled from '@emotion/styled';
22+
import { Layout } from '@sonarsource/echoes-react';
23+
import { noop } from 'lodash';
2124
import * as React from 'react';
22-
import { createPortal } from 'react-dom';
2325
import { Helmet } from 'react-helmet-async';
26+
import { useIntl } from 'react-intl';
2427
import { Outlet } from 'react-router-dom';
28+
import { useFlags } from '~adapters/helpers/feature-flags';
29+
import useEffectOnce from '~shared/helpers/useEffectOnce';
2530
import { Extension } from '~shared/types/common';
2631
import { getSettingsNavigation } from '~sq-server-commons/api/navigation';
2732
import { getPendingPlugins } from '~sq-server-commons/api/plugins';
@@ -32,142 +37,121 @@ import AdminContext, {
3237
} from '~sq-server-commons/context/AdminContext';
3338
import withAppStateContext from '~sq-server-commons/context/app-state/withAppStateContext';
3439
import { translate } from '~sq-server-commons/helpers/l10n';
35-
import { getIntl } from '~sq-server-commons/helpers/l10nBundle';
3640
import { AdminPagesContext } from '~sq-server-commons/types/admin';
3741
import { AppState } from '~sq-server-commons/types/appstate';
3842
import { PendingPluginResult } from '~sq-server-commons/types/plugins';
3943
import { SysStatus } from '~sq-server-commons/types/types';
4044
import handleRequiredAuthorization from '../utils/handleRequiredAuthorization';
45+
import { AdministrationSidebar } from './nav/administration/AdministrationSidebar';
4146
import SettingsNav from './nav/settings/SettingsNav';
4247

4348
export interface AdminContainerProps {
4449
appState: AppState;
4550
}
4651

47-
interface State {
48-
adminPages: Extension[];
49-
pendingPlugins: PendingPluginResult;
50-
systemStatus: SysStatus;
51-
}
52-
53-
export class AdminContainer extends React.PureComponent<AdminContainerProps, State> {
54-
intl = getIntl();
55-
mounted = false;
56-
portalAnchor: Element | null = null;
57-
state: State = {
58-
pendingPlugins: defaultPendingPlugins,
59-
systemStatus: defaultSystemStatus,
60-
adminPages: [],
61-
};
62-
63-
componentDidMount() {
64-
this.mounted = true;
65-
this.portalAnchor = document.getElementById('component-nav-portal');
66-
if (!this.props.appState.canAdmin) {
52+
export function AdminContainer({ appState }: Readonly<AdminContainerProps>) {
53+
const intl = useIntl();
54+
55+
const { frontEndEngineeringEnableSidebarNavigation } = useFlags();
56+
57+
const [pendingPlugins, setPendingPlugins] =
58+
React.useState<PendingPluginResult>(defaultPendingPlugins);
59+
const [systemStatus, setSystemStatus] = React.useState<SysStatus>(defaultSystemStatus);
60+
const [adminPages, setAdminPages] = React.useState<Extension[]>([]);
61+
62+
const fetchNavigationSettings = React.useCallback(() => {
63+
getSettingsNavigation().then((r) => {
64+
setAdminPages(r.extensions);
65+
}, noop);
66+
}, []);
67+
68+
const fetchPendingPlugins = React.useCallback(() => {
69+
getPendingPlugins().then((pendingPlugins) => {
70+
setPendingPlugins(pendingPlugins);
71+
}, noop);
72+
}, []);
73+
74+
const waitRestartingDone = React.useCallback(() => {
75+
waitSystemUPStatus().then(({ status }) => {
76+
setSystemStatus(status);
77+
window.location.reload();
78+
}, noop);
79+
}, []);
80+
81+
const fetchSystemStatus = React.useCallback(() => {
82+
getSystemStatus().then(({ status }) => {
83+
setSystemStatus(status);
84+
if (status === 'RESTARTING') {
85+
waitRestartingDone();
86+
}
87+
}, noop);
88+
}, [waitRestartingDone]);
89+
90+
useEffectOnce(() => {
91+
if (!appState.canAdmin) {
6792
handleRequiredAuthorization();
68-
} else {
69-
this.fetchNavigationSettings();
70-
this.fetchPendingPlugins();
71-
this.fetchSystemStatus();
93+
return;
7294
}
73-
}
7495

75-
componentWillUnmount() {
76-
this.mounted = false;
96+
fetchNavigationSettings();
97+
fetchPendingPlugins();
98+
fetchSystemStatus();
99+
});
100+
101+
const adminContextValue = React.useMemo(
102+
() => ({
103+
fetchSystemStatus,
104+
fetchPendingPlugins,
105+
pendingPlugins,
106+
systemStatus,
107+
}),
108+
[fetchPendingPlugins, fetchSystemStatus, pendingPlugins, systemStatus],
109+
);
110+
111+
// Check that the adminPages are loaded
112+
if (!adminPages) {
113+
return null;
77114
}
78115

79-
fetchNavigationSettings = () => {
80-
getSettingsNavigation().then(
81-
(r) => {
82-
this.setState({ adminPages: r.extensions });
83-
},
84-
() => {},
85-
);
86-
};
87-
88-
fetchPendingPlugins = () => {
89-
getPendingPlugins().then(
90-
(pendingPlugins) => {
91-
if (this.mounted) {
92-
this.setState({ pendingPlugins });
93-
}
94-
},
95-
() => {},
96-
);
97-
};
98-
99-
fetchSystemStatus = () => {
100-
getSystemStatus().then(
101-
({ status }) => {
102-
if (this.mounted) {
103-
this.setState({ systemStatus: status });
104-
if (status === 'RESTARTING') {
105-
this.waitRestartingDone();
106-
}
107-
}
108-
},
109-
() => {},
110-
);
111-
};
112-
113-
waitRestartingDone = () => {
114-
waitSystemUPStatus().then(
115-
({ status }) => {
116-
if (this.mounted) {
117-
this.setState({ systemStatus: status });
118-
window.location.reload();
119-
}
120-
},
121-
() => {},
122-
);
123-
};
124-
125-
render() {
126-
const { adminPages } = this.state;
127-
128-
// Check that the adminPages are loaded
129-
if (!adminPages) {
130-
return null;
131-
}
116+
const adminPagesContext: AdminPagesContext = { adminPages };
132117

133-
const { pendingPlugins, systemStatus } = this.state;
134-
const adminPagesContext: AdminPagesContext = { adminPages };
118+
return (
119+
<>
120+
{frontEndEngineeringEnableSidebarNavigation && (
121+
<AdministrationSidebar extensions={adminPages} />
122+
)}
135123

136-
return (
137-
<>
124+
<Layout.ContentGrid>
138125
<Helmet
139126
defer={false}
140-
titleTemplate={this.intl.formatMessage(
127+
titleTemplate={intl.formatMessage(
141128
{ id: 'page_title.template.with_category' },
142129
{ page: translate('layout.settings') },
143130
)}
144131
/>
145132

146-
{this.portalAnchor &&
147-
createPortal(
133+
{!frontEndEngineeringEnableSidebarNavigation && (
134+
<ContentHeader>
148135
<SettingsNav
149136
extensions={adminPages}
150-
fetchPendingPlugins={this.fetchPendingPlugins}
151-
fetchSystemStatus={this.fetchSystemStatus}
137+
fetchPendingPlugins={fetchPendingPlugins}
138+
fetchSystemStatus={fetchSystemStatus}
152139
pendingPlugins={pendingPlugins}
153140
systemStatus={systemStatus}
154-
/>,
155-
this.portalAnchor,
156-
)}
141+
/>
142+
</ContentHeader>
143+
)}
157144

158-
<AdminContext.Provider
159-
value={{
160-
fetchSystemStatus: this.fetchSystemStatus,
161-
fetchPendingPlugins: this.fetchPendingPlugins,
162-
pendingPlugins,
163-
systemStatus,
164-
}}
165-
>
145+
<AdminContext.Provider value={adminContextValue}>
166146
<Outlet context={adminPagesContext} />
167147
</AdminContext.Provider>
168-
</>
169-
);
170-
}
148+
</Layout.ContentGrid>
149+
</>
150+
);
171151
}
172152

173153
export default withAppStateContext(AdminContainer);
154+
155+
const ContentHeader = styled.div`
156+
grid-area: content-header;
157+
`;

0 commit comments

Comments
 (0)