Skip to content

Commit 11afae4

Browse files
committed
[dashboard] Implement a ProjectContext and refactor ProjectSettingsPage component
1 parent bb65586 commit 11afae4

File tree

7 files changed

+82
-65
lines changed

7 files changed

+82
-65
lines changed

components/dashboard/src/Menu.tsx

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,20 @@ import PillMenuItem from "./components/PillMenuItem";
2323
import TabMenuItem from "./components/TabMenuItem";
2424
import { getTeamSettingsMenu } from "./teams/TeamSettings";
2525
import { getProjectSettingsMenu } from "./projects/ProjectSettings";
26+
import { ProjectContext } from "./projects/project-context";
2627

2728
interface Entry {
2829
title: string,
2930
link: string,
3031
alternatives?: string[]
3132
}
3233

33-
3434
export default function Menu() {
3535
const { user } = useContext(UserContext);
3636
const { teams } = useContext(TeamsContext);
3737
const location = useLocation();
38+
const team = getCurrentTeam(location, teams);
39+
const { project, setProject } = useContext(ProjectContext);
3840

3941
const match = useRouteMatch<{ segment1?: string, segment2?: string, segment3?: string }>("/(t/)?:segment1/:segment2?/:segment3?");
4042
const projectSlug = (() => {
@@ -57,7 +59,6 @@ export default function Menu() {
5759
}
5860

5961
const userFullName = user?.fullName || user?.name || '...';
60-
const team = getCurrentTeam(location, teams);
6162

6263
{
6364
// updating last team selection
@@ -88,6 +89,24 @@ export default function Menu() {
8889
})();
8990
}, [ teams ]);
9091

92+
useEffect(() => {
93+
if (!teams || !projectSlug) {
94+
return;
95+
}
96+
(async () => {
97+
const projects = (!!team
98+
? await getGitpodService().server.getTeamProjects(team.id)
99+
: await getGitpodService().server.getUserProjects());
100+
101+
// Find project matching with slug, otherwise with name
102+
const project = projectSlug && projects.find(p => p.slug ? p.slug === projectSlug : p.name === projectSlug);
103+
if (!project) {
104+
return;
105+
}
106+
setProject(project);
107+
})();
108+
}, [projectSlug, setProject, team, teams]);
109+
91110
const teamOrUserSlug = !!team ? '/t/' + team.slug : '/projects';
92111
const leftMenu: Entry[] = (() => {
93112
// Project menu
@@ -226,7 +245,7 @@ export default function Menu() {
226245
{ projectSlug && (
227246
<div className="flex h-full rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 px-2 py-1">
228247
<Link to={`${teamOrUserSlug}/${projectSlug}${prebuildId ? "/prebuilds" : ""}`}>
229-
<span className="text-base text-gray-600 dark:text-gray-400 font-semibold">{projectSlug}</span>
248+
<span className="text-base text-gray-600 dark:text-gray-400 font-semibold">{project?.name}</span>
230249
</Link>
231250
</div>
232251
)}

components/dashboard/src/index.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import ReactDOM from 'react-dom';
99
import App from './App';
1010
import { UserContextProvider } from './user-context';
1111
import { TeamsContextProvider } from './teams/teams-context';
12+
import { ProjectContextProvider } from './projects/project-context';
1213
import { ThemeContextProvider } from './theme-context';
1314
import { BrowserRouter } from 'react-router-dom';
1415

@@ -18,11 +19,13 @@ ReactDOM.render(
1819
<React.StrictMode>
1920
<UserContextProvider>
2021
<TeamsContextProvider>
21-
<ThemeContextProvider>
22-
<BrowserRouter>
23-
<App />
24-
</BrowserRouter>
25-
</ThemeContextProvider>
22+
<ProjectContextProvider>
23+
<ThemeContextProvider>
24+
<BrowserRouter>
25+
<App />
26+
</BrowserRouter>
27+
</ThemeContextProvider>
28+
</ProjectContextProvider>
2629
</TeamsContextProvider>
2730
</UserContextProvider>
2831
</React.StrictMode>,

components/dashboard/src/projects/ConfigureProject.tsx

Lines changed: 9 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,10 @@
55
*/
66

77
import React, { Suspense, useContext, useEffect, useState } from "react";
8-
import { useLocation, useRouteMatch } from "react-router";
98
import { Project, StartPrebuildResult, WorkspaceInstance } from "@gitpod/gitpod-protocol";
109
import PrebuildLogs from "../components/PrebuildLogs";
1110
import TabMenuItem from "../components/TabMenuItem";
1211
import { getGitpodService } from "../service/service";
13-
import { getCurrentTeam, TeamsContext } from "../teams/teams-context";
1412
import Spinner from "../icons/Spinner.svg";
1513
import NoAccess from "../icons/NoAccess.svg";
1614
import PrebuildLogsEmpty from "../images/prebuild-logs-empty.svg";
@@ -19,8 +17,8 @@ import { ThemeContext } from "../theme-context";
1917
import { PrebuildInstanceStatus } from "./Prebuilds";
2018
import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
2119
import { openAuthorizeWindow } from "../provider-utils";
22-
import { PageWithSubMenu } from "../components/PageWithSubMenu";
23-
import { getProjectSettingsMenu } from "./ProjectSettings";
20+
import { ProjectSettingsPage } from "./ProjectSettings";
21+
import { ProjectContext } from "./project-context";
2422

2523
const MonacoEditor = React.lazy(() => import('../components/MonacoEditor'));
2624

@@ -42,12 +40,7 @@ const TASKS = {
4240
// }
4341

4442
export default function () {
45-
const { teams } = useContext(TeamsContext);
46-
const location = useLocation();
47-
const team = getCurrentTeam(location, teams);
48-
const routeMatch = useRouteMatch<{ teamSlug: string, projectSlug: string }>("/(t/)?:teamSlug/:projectSlug/configure");
49-
const projectSlug = routeMatch?.params.projectSlug;
50-
const [project, setProject] = useState<Project | undefined>();
43+
const { project } = useContext(ProjectContext);
5144
const [gitpodYml, setGitpodYml] = useState<string>('');
5245
const [dockerfile, setDockerfile] = useState<string>('');
5346
const [editorMessage, setEditorMessage] = useState<React.ReactNode | null>(null);
@@ -68,26 +61,12 @@ export default function () {
6861
setIsDetecting(true);
6962
setIsEditorDisabled(true);
7063
setEditorMessage(null);
71-
if (!teams) {
72-
setIsDetecting(false);
73-
setEditorMessage(<EditorMessage type="warning" heading="Couldn't load teams information." message="Please try to reload this page." />);
74-
return;
75-
}
7664
(async () => {
77-
const projects = (!!team
78-
? await getGitpodService().server.getTeamProjects(team.id)
79-
: await getGitpodService().server.getUserProjects());
80-
81-
const project = projectSlug && projects.find(
82-
p => p.slug ? p.slug === projectSlug :
83-
p.name === projectSlug);
84-
8565
if (!project) {
8666
setIsDetecting(false);
8767
setEditorMessage(<EditorMessage type="warning" heading="Couldn't load project information." message="Please try to reload this page." />);
8868
return;
8969
}
90-
setProject(project);
9170
try {
9271
await detectProjectConfiguration(project);
9372
} catch (error) {
@@ -106,7 +85,7 @@ export default function () {
10685
}
10786
}
10887
})();
109-
}, [teams, team]);
88+
}, [project]);
11089

11190
const detectProjectConfiguration = async (project: Project) => {
11291
const guessedConfigStringPromise = getGitpodService().server.guessProjectConfiguration(project.id);
@@ -227,7 +206,7 @@ export default function () {
227206
redirectToNewWorkspace();
228207
}
229208

230-
return <PageWithSubMenu subMenu={getProjectSettingsMenu(project, team)} title="Configuration" subtitle="View and edit project configuration.">
209+
return <ProjectSettingsPage project={project}>
231210
<div className="flex space-x-4">
232211
<div className="flex-1 h-96 rounded-xl overflow-hidden relative flex flex-col">
233212
<div className="flex bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-600 px-6 pt-3">
@@ -245,7 +224,7 @@ export default function () {
245224
{showAuthBanner ? (
246225
<div className="mt-8 text-gray-500 flex-col">
247226
<div className="p-16 text-center">
248-
<img src={NoAccess} title="No Access" className="m-auto mb-4" />
227+
<img alt="" src={NoAccess} title="No Access" className="m-auto mb-4" />
249228
<div className="text-center text-gray-600 dark:text-gray-50 pb-3 font-bold">
250229
No Access
251230
</div>
@@ -256,7 +235,7 @@ export default function () {
256235
</div>
257236
</div>
258237
) : (<>
259-
<img className="h-5 w-5 animate-spin" src={Spinner} />
238+
<img alt="" className="h-5 w-5 animate-spin" src={Spinner} />
260239
<span className="font-semibold text-gray-400">Detecting project configuration ...</span>
261240
</>
262241
)}
@@ -266,7 +245,7 @@ export default function () {
266245
<div className="flex-grow flex">{startPrebuildResult
267246
? <PrebuildLogs workspaceId={startPrebuildResult.wsid} onInstanceUpdate={onInstanceUpdate} />
268247
: (!prebuildWasTriggered && <div className="flex-grow flex flex-col items-center justify-center">
269-
<img className="w-14" role="presentation" src={isDark ? PrebuildLogsEmptyDark : PrebuildLogsEmpty} />
248+
<img alt="" className="w-14" role="presentation" src={isDark ? PrebuildLogsEmptyDark : PrebuildLogsEmpty} />
270249
<h3 className="text-center text-lg text-gray-500 dark:text-gray-50 mt-4">No Recent Prebuild</h3>
271250
<p className="text-center text-base text-gray-500 dark:text-gray-400 mt-2 w-64">Edit the project configuration on the left to get started. <a className="gp-link" href="https://www.gitpod.io/docs/config-gitpod-file/">Learn more</a></p>
272251
</div>)
@@ -283,7 +262,7 @@ export default function () {
283262
</div>
284263
</div>
285264
</div>
286-
</PageWithSubMenu>;
265+
</ProjectSettingsPage>;
287266
}
288267

289268
function EditorMessage(props: { heading: string, message: string, type: 'success' | 'warning' }) {

components/dashboard/src/projects/ProjectSettings.tsx

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
*/
66

77
import { useContext, useEffect, useState } from "react";
8-
import { useLocation, useRouteMatch } from "react-router";
8+
import { useLocation } from "react-router";
99
import { Project, Team } from "@gitpod/gitpod-protocol";
1010
import CheckBox from "../components/CheckBox";
1111
import { getGitpodService } from "../service/service";
1212
import { getCurrentTeam, TeamsContext } from "../teams/teams-context";
1313
import { PageWithSubMenu } from "../components/PageWithSubMenu";
1414
import PillLabel from "../components/PillLabel";
15+
import { ProjectContext } from "./project-context";
1516

1617
export function getProjectSettingsMenu(project?: Project, team?: Team) {
1718
const teamOrUserSlug = !!team ? 't/' + team.slug : 'projects';
@@ -27,34 +28,21 @@ export function getProjectSettingsMenu(project?: Project, team?: Team) {
2728
];
2829
}
2930

30-
export default function () {
31+
export function ProjectSettingsPage(props: { project?: Project, children?: React.ReactNode }) {
3132
const location = useLocation();
3233
const { teams } = useContext(TeamsContext);
3334
const team = getCurrentTeam(location, teams);
34-
const match = useRouteMatch<{ team: string, resource: string }>("/(t/)?:team/:resource");
35-
const projectSlug = match?.params?.resource;
36-
const [ project, setProject ] = useState<Project | undefined>();
3735

38-
const [ isLoading, setIsLoading ] = useState<boolean>(true);
39-
const [ isIncrementalPrebuildsEnabled, setIsIncrementalPrebuildsEnabled ] = useState<boolean>(false);
36+
return <PageWithSubMenu subMenu={getProjectSettingsMenu(props.project, team)} title="Settings" subtitle="Manage project settings and configuration">
37+
{props.children}
38+
</PageWithSubMenu>
39+
}
4040

41-
useEffect(() => {
42-
if (!teams || !projectSlug) {
43-
return;
44-
}
45-
(async () => {
46-
const projects = (!!team
47-
? await getGitpodService().server.getTeamProjects(team.id)
48-
: await getGitpodService().server.getUserProjects());
41+
export default function () {
42+
const { project } = useContext(ProjectContext);
4943

50-
// Find project matching with slug, otherwise with name
51-
const project = projectSlug && projects.find(p => p.slug ? p.slug === projectSlug : p.name === projectSlug);
52-
if (!project) {
53-
return;
54-
}
55-
setProject(project);
56-
})();
57-
}, [ projectSlug, team, teams ]);
44+
const [ isLoading, setIsLoading ] = useState<boolean>(true);
45+
const [ isIncrementalPrebuildsEnabled, setIsIncrementalPrebuildsEnabled ] = useState<boolean>(false);
5846

5947
useEffect(() => {
6048
if (!project) {
@@ -82,13 +70,13 @@ export default function () {
8270
}
8371
}
8472

85-
return <PageWithSubMenu subMenu={getProjectSettingsMenu(project, team)} title="Settings" subtitle="Manage project settings and configuration">
73+
return <ProjectSettingsPage project={project}>
8674
<h3>Incremental Prebuilds</h3>
8775
<CheckBox
8876
title={<span>Enable Incremental Prebuilds <PillLabel type="warn" className="font-semibold mt-2 py-0.5 px-2 self-center">Beta</PillLabel></span>}
8977
desc={<span>When possible, use an earlier successful prebuild as a base to create new prebuilds. This can make your prebuilds significantly faster, especially if they normally take longer than 10 minutes. <a className="gp-link" href="https://www.gitpod.io/changelog/faster-incremental-prebuilds">Learn more</a></span>}
9078
checked={isIncrementalPrebuildsEnabled}
9179
disabled={isLoading}
9280
onChange={toggleIncrementalPrebuilds} />
93-
</PageWithSubMenu>;
81+
</ProjectSettingsPage>;
9482
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* Copyright (c) 2021 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License-AGPL.txt in the project root for license information.
5+
*/
6+
/**
7+
* Copyright (c) 2021 Gitpod GmbH. All rights reserved.
8+
* Licensed under the GNU Affero General Public License (AGPL).
9+
* See License-AGPL.txt in the project root for license information.
10+
*/
11+
12+
import { Project } from '@gitpod/gitpod-protocol';
13+
import React, { createContext, useState } from 'react';
14+
15+
export const ProjectContext = createContext<{
16+
project?: Project,
17+
setProject: React.Dispatch<Project>,
18+
}>({
19+
setProject: () => null,
20+
});
21+
22+
export const ProjectContextProvider: React.FC = ({ children }) => {
23+
const [ project, setProject ] = useState<Project>();
24+
return (
25+
<ProjectContext.Provider value={{ project, setProject }}>
26+
{children}
27+
</ProjectContext.Provider>
28+
)
29+
}

components/dashboard/src/settings/Account.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export default function Account() {
4545
<input autoFocus className="w-full" type="text" onChange={e => setTypedEmail(e.target.value)}></input>
4646
</ConfirmationModal>
4747

48-
<PageWithSubMenu subMenu={settingsMenu} title='Account' subtitle='Manage account and Git configuration.'>
48+
<PageWithSubMenu subMenu={settingsMenu} title='Account' subtitle='Manage account and Git configuration.'>
4949
<h3>Profile</h3>
5050
<p className="text-base text-gray-500 pb-4 max-w-2xl">The following information will be used to set up Git configuration. You can override Git author name and email per project by using the default environment variables <CodeText>GIT_AUTHOR_NAME</CodeText>, <CodeText>GIT_COMMITTER_NAME</CodeText>, <CodeText>GIT_AUTHOR_EMAIL</CodeText> and <CodeText>GIT_COMMITTER_EMAIL</CodeText>.</p>
5151
<div className="flex flex-col lg:flex-row">

components/dashboard/src/user-context.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ const UserContext = createContext<{
1414
setUser: () => null,
1515
});
1616

17-
1817
const UserContextProvider: React.FC = ({ children }) => {
1918
const [ user, setUser ] = useState<User>();
2019
return (

0 commit comments

Comments
 (0)