Skip to content

[dashboard] single workspaces list #7606

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions components/dashboard/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ function App() {
<Route path="/admin/workspaces" component={WorkspacesSearch} />

<Route path={["/", "/login"]} exact>
<Redirect to="/projects" />
<Redirect to="/workspaces" />
</Route>
<Route path={["/settings"]} exact>
<Redirect to="/account" />
Expand Down Expand Up @@ -307,9 +307,6 @@ function App() {
if (resourceOrPrebuild === "configure") {
return <ConfigureProject />;
}
if (resourceOrPrebuild === "workspaces") {
return <Workspaces />;
}
if (resourceOrPrebuild === "prebuilds") {
return <Prebuilds />;
}
Expand Down Expand Up @@ -346,9 +343,6 @@ function App() {
if (resourceOrPrebuild === "settings") {
return <ProjectSettings />;
}
if (resourceOrPrebuild === "workspaces") {
return <Workspaces />;
}
if (resourceOrPrebuild === "prebuilds") {
return <Prebuilds />;
}
Expand Down
27 changes: 6 additions & 21 deletions components/dashboard/src/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,6 @@ export default function Menu() {
title: 'Branches',
link: `${teamOrUserSlug}/${projectSlug}`,
},
{
title: 'Workspaces',
link: `${teamOrUserSlug}/${projectSlug}/workspaces`,
},
{
title: 'Prebuilds',
link: `${teamOrUserSlug}/${projectSlug}/prebuilds`,
Expand All @@ -120,11 +116,7 @@ export default function Menu() {
{
title: 'Projects',
link: `/t/${team.slug}/projects`,
},
{
title: 'Workspaces',
link: `/t/${team.slug}/workspaces`,
alternatives: [`/t/${team.slug}`]
alternatives: ([] as string[])
},
{
title: 'Members',
Expand All @@ -143,15 +135,15 @@ export default function Menu() {
}
// User menu
return [
{
title: 'Projects',
link: '/projects'
},
{
title: 'Workspaces',
link: '/workspaces',
alternatives: ['/']
},
{
title: 'Projects',
link: '/projects'
},
{
title: 'Settings',
link: '/settings',
Expand Down Expand Up @@ -240,18 +232,11 @@ export default function Menu() {
)
}

const gitpodIconUrl = () => {
if (team) {
return `/t/${team.slug}`;
}
return "/"
}

return <>
<header className={`app-container flex flex-col pt-4 space-y-4 ${isMinimalUI || !!prebuildId ? 'pb-4' : ''}`} data-analytics='{"button_type":"menu"}'>
<div className="flex h-10">
<div className="flex justify-between items-center pr-3">
<Link to={gitpodIconUrl()}>
<Link to="/">
<img src={gitpodIcon} className="h-6" alt="Gitpod's logo" />
</Link>
{!isMinimalUI && <div className="ml-2 text-base">
Expand Down
94 changes: 4 additions & 90 deletions components/dashboard/src/workspaces/Workspaces.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import { useContext, useEffect, useState } from "react";
import { Project, Team, WhitelistedRepository, Workspace, WorkspaceInfo } from "@gitpod/gitpod-protocol";
import { WhitelistedRepository, Workspace, WorkspaceInfo } from "@gitpod/gitpod-protocol";
import Header from "../components/Header";
import DropDown from "../components/DropDown";
import { WorkspaceModel } from "./workspace-model";
Expand All @@ -15,8 +15,6 @@ import { StartWorkspaceModal, WsStartEntry } from "./StartWorkspaceModal";
import { ItemsList } from "../components/ItemsList";
import { getCurrentTeam, TeamsContext } from "../teams/teams-context";
import { useLocation, useRouteMatch } from "react-router";
import { toRemoteURL } from "../projects/render-utils";
import { Link, useHistory } from "react-router-dom";

export interface WorkspacesProps {
}
Expand All @@ -29,64 +27,24 @@ export interface WorkspacesState {

export default function () {
const location = useLocation();
const history = useHistory();

const { teams } = useContext(TeamsContext);
const team = getCurrentTeam(location, teams);
const match = useRouteMatch<{ team: string, resource: string }>("/(t/)?:team/:resource");
const projectSlug = match?.params?.resource !== 'workspaces' ? match?.params?.resource : undefined;
const [projects, setProjects] = useState<Project[]>([]);
const [activeWorkspaces, setActiveWorkspaces] = useState<WorkspaceInfo[]>([]);
const [inactiveWorkspaces, setInactiveWorkspaces] = useState<WorkspaceInfo[]>([]);
const [repos, setRepos] = useState<WhitelistedRepository[]>([]);
const [isTemplateModelOpen, setIsTemplateModelOpen] = useState<boolean>(false);
const [workspaceModel, setWorkspaceModel] = useState<WorkspaceModel>();
const [teamsProjects, setTeamsProjects] = useState<Project[]>([]);
const [teamsWorkspaceModel, setTeamsWorkspaceModel] = useState<WorkspaceModel|undefined>();
const [teamsActiveWorkspaces, setTeamsActiveWorkspaces] = useState<WorkspaceInfo[]>([]);

const newProjectUrl = !!team ? `/new?team=${team.slug}` : '/new?user=1';
const onNewProject = () => {
history.push(newProjectUrl);
}

const fetchTeamsProjects = async () => {
const projectsPerTeam = await Promise.all((teams || []).map(t => getGitpodService().server.getTeamProjects(t.id)));
const allTeamsProjects = projectsPerTeam.flat(1);
setTeamsProjects(allTeamsProjects);
return allTeamsProjects;
}

useEffect(() => {
// only show example repos on the global user context
if (!team && !projectSlug) {
getGitpodService().server.getFeaturedRepositories().then(setRepos);
}
(async () => {
const projects = (!!team
? await getGitpodService().server.getTeamProjects(team.id)
: await getGitpodService().server.getUserProjects());

let project: Project | undefined = undefined;
if (projectSlug) {
project = projects.find(p => p.slug ? p.slug === projectSlug : p.name === projectSlug);
if (project) {
setProjects([project]);
}
} else {
setProjects(projects);
}
let workspaceModel;
if (!!project) {
workspaceModel = new WorkspaceModel(setActiveWorkspaces, setInactiveWorkspaces, Promise.resolve([project.id]), false);
} else if (!!team) {
workspaceModel = new WorkspaceModel(setActiveWorkspaces, setInactiveWorkspaces, getGitpodService().server.getTeamProjects(team?.id).then(projects => projects.map(p => p.id)), false);
} else {
workspaceModel = new WorkspaceModel(setActiveWorkspaces, setInactiveWorkspaces, getGitpodService().server.getUserProjects().then(projects => projects.map(p => p.id)), true);
// Don't await
const teamsProjectIdsPromise = fetchTeamsProjects().then(tp => tp.map(p => p.id));
setTeamsWorkspaceModel(new WorkspaceModel(setTeamsActiveWorkspaces, () => {}, teamsProjectIdsPromise, false));
}
const workspaceModel = new WorkspaceModel(setActiveWorkspaces, setInactiveWorkspaces);
setWorkspaceModel(workspaceModel);
})();
}, [teams, location]);
Expand All @@ -95,16 +53,6 @@ export default function () {
const hideStartWSModal = () => setIsTemplateModelOpen(false);

const getRecentSuggestions: () => WsStartEntry[] = () => {
if (projectSlug || team) {
return projects.map(p => {
const remoteUrl = toRemoteURL(p.cloneUrl);
return {
title: (team ? team.name + '/' : '') + p.name,
description: remoteUrl,
startUrl: gitpodHostUrl.withContext(remoteUrl).toString()
};
});
}
if (workspaceModel) {
const all = workspaceModel.getAllFetchedWorkspaces();
if (all && all.size > 0) {
Expand Down Expand Up @@ -169,9 +117,6 @@ export default function () {
</div>
<ItemsList className="app-container pb-40">
<div className="border-t border-gray-200 dark:border-gray-800"></div>
{
teamsWorkspaceModel?.initialized && <ActiveTeamWorkspaces teams={teams} teamProjects={teamsProjects} teamWorkspaces={teamsActiveWorkspaces} />
}
{
activeWorkspaces.map(e => {
return <WorkspaceEntry key={e.workspace.id} desc={e} model={workspaceModel} stopWorkspace={wsId => getGitpodService().server.stopWorkspace(wsId)} />
Expand All @@ -193,23 +138,14 @@ export default function () {
:
<div className="app-container flex flex-col space-y-2">
<div className="px-6 py-3 flex flex-col text-gray-400 border-t border-gray-200 dark:border-gray-800">
{teamsWorkspaceModel?.initialized && <ActiveTeamWorkspaces teams={teams} teamProjects={teamsProjects} teamWorkspaces={teamsActiveWorkspaces} />}
<div className="flex flex-col items-center justify-center h-96 w-96 mx-auto">
{!!team && projects.length === 0
?<>
<h3 className="text-center pb-3 text-gray-500 dark:text-gray-400">No Projects</h3>
<div className="text-center pb-6 text-gray-500">This team doesn't have any projects, yet.</div>
<span>
<button onClick={onNewProject}>New Project</button>
</span>
</>
:<>
<>
<h3 className="text-center pb-3 text-gray-500 dark:text-gray-400">No Workspaces</h3>
<div className="text-center pb-6 text-gray-500">Prefix any Git repository URL with {window.location.host}/# or create a new workspace for a recently used project. <a className="gp-link" href="https://www.gitpod.io/docs/getting-started/">Learn more</a></div>
<span>
<button onClick={showStartWSModal}>New Workspace</button>
</span>
</>}
</>
</div>
</div>
</div>
Expand All @@ -229,25 +165,3 @@ export default function () {

}

function ActiveTeamWorkspaces(props: { teams?: Team[], teamProjects: Project[], teamWorkspaces: WorkspaceInfo[] }) {
if (!props.teams || props.teamWorkspaces.length === 0) {
return <></>;
}
return <div className="p-3 text-gray-400 bg-gray-50 dark:bg-gray-800 rounded-xl text-sm flex items-center justify-center space-x-1">
<div className="mr-2 rounded-full w-3 h-3 bg-green-500" />
<span>There are currently more active workspaces in the following teams:</span>
<span>{
props.teams
.map(t => {
const projects = props.teamProjects.filter(p => p.teamId === t.id);
const count = props.teamWorkspaces.filter(w => projects.some(p => p.id === w.workspace.projectId)).length;
if (count < 1) {
return undefined;
}
return <Link className="gp-link" to={`/t/${t.slug}/workspaces`}>{t.name}</Link>;
})
.filter(t => !!t)
.map((t, i) => <>{i > 0 && <span>, </span>}{t}</>)
}</span>
</div>;
}
20 changes: 4 additions & 16 deletions components/dashboard/src/workspaces/workspace-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@ export class WorkspaceModel implements Disposable, Partial<GitpodClient> {

constructor(
protected setActiveWorkspaces: (ws: WorkspaceInfo[]) => void,
protected setInActiveWorkspaces: (ws: WorkspaceInfo[]) => void,
protected projectIds: Promise<string[]>,
protected includeWithoutProject?: boolean) {
protected setInActiveWorkspaces: (ws: WorkspaceInfo[]) => void) {
this.internalRefetch();
}

Expand All @@ -38,14 +36,12 @@ export class WorkspaceModel implements Disposable, Partial<GitpodClient> {
const [infos, pinned] = await Promise.all([
getGitpodService().server.getWorkspaces({
limit: this.internalLimit,
projectId: await this.projectIds,
includeWithoutProject: !!this.includeWithoutProject
includeWithoutProject: true
}),
getGitpodService().server.getWorkspaces({
limit: this.internalLimit,
pinnedOnly: true,
projectId: await this.projectIds,
includeWithoutProject: !!this.includeWithoutProject
includeWithoutProject: true
})
]);

Expand All @@ -63,14 +59,6 @@ export class WorkspaceModel implements Disposable, Partial<GitpodClient> {
}
}

protected async isIncluded(info: WorkspaceInfo): Promise<boolean> {
if (info.workspace.projectId) {
return (await this.projectIds).some(id => id === info.workspace.projectId);
} else {
return !!this.includeWithoutProject;
}
}

dispose(): void {
this.disposables.dispose();
}
Expand All @@ -87,7 +75,7 @@ export class WorkspaceModel implements Disposable, Partial<GitpodClient> {
try {
this.currentlyFetching.add(instance.workspaceId);
const info = await getGitpodService().server.getWorkspace(instance.workspaceId);
if (info.workspace.type === 'regular' && await this.isIncluded(info)) {
if (info.workspace.type === 'regular') {
this.workspaces.set(instance.workspaceId, info);
this.notifyWorkpaces();
}
Expand Down