Skip to content

Commit 376ea27

Browse files
committed
[Dashboard] Start ws for recently used repos
1 parent 6e48d21 commit 376ea27

File tree

6 files changed

+144
-24
lines changed

6 files changed

+144
-24
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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+
import { useState } from "react";
8+
import Modal from "../components/Modal";
9+
10+
export interface WsStartEntry {
11+
title: string
12+
description: string
13+
startUrl: string
14+
}
15+
16+
interface StartWorkspaceModalProps {
17+
visible: boolean;
18+
recent: WsStartEntry[];
19+
examples: WsStartEntry[];
20+
selected?: Mode;
21+
onClose: () => void;
22+
}
23+
24+
type Mode = 'Recent' | 'Examples';
25+
26+
function Tab(p: { name: Mode, selection: Mode, setSelection: (selection: Mode) => any }) {
27+
const selectedTab = 'border-gray-800 text-gray-800';
28+
const inactiveTab = 'border-none text-gray-400';
29+
return <div onClick={() => p.setSelection(p.name)} className={"cursor-pointer py-2 px-4 border-b-4 " + (p.selection === p.name ? selectedTab : inactiveTab)}>{p.name}</div>
30+
}
31+
32+
export function StartWorkspaceModal(p: StartWorkspaceModalProps) {
33+
const [selection, setSelection] = useState(p.selected || 'Recent');
34+
35+
const list = (selection === 'Recent' ? p.recent : p.examples).map(e =>
36+
<a key={e.title} href={e.startUrl} className="rounded-xl group hover:bg-gray-100 flex p-4 my-1">
37+
<div className="w-full">
38+
<p className="text-base text-gray-800 font-semibold">{e.title}</p>
39+
<p>{e.description}</p>
40+
</div>
41+
</a>);
42+
43+
return <Modal onClose={p.onClose} visible={p.visible}>
44+
<h3 className="pb-2">New Workspace</h3>
45+
{/* separator */}
46+
<div className="border-t mt-2 -mx-6 px-6 pt-2">
47+
<div className="flex">
48+
<Tab name='Recent' setSelection={setSelection} selection={selection} />
49+
<Tab name='Examples' setSelection={setSelection} selection={selection} />
50+
</div>
51+
</div>
52+
<div className="border-t -mx-6 px-6 py-2">
53+
{list.length > 0 ?
54+
<p className="my-4 text-base">
55+
{selection === 'Recent' ?
56+
'Create a new workspace using the default branch.' :
57+
'Create a new workspace using an example project.'}
58+
</p> : <p className="h-6 my-4"></p>}
59+
<div className="space-y-2 mt-4 overflow-y-scroll h-80 pr-2">
60+
{list.length > 0 ? list :
61+
(selection === 'Recent' ?
62+
<div className="flex flex-col pt-12 items-center px-2">
63+
<h3 className="mb-2 text-gray-500">No Recent Projects</h3>
64+
<p className="text-center">Projects you use frequently will show up here.</p>
65+
<p className="text-center">Prefix a git repository URL with gitpod.io/# or start with an example.</p>
66+
<button onClick={() => setSelection('Examples')} className="font-medium mt-8">Select Example</button>
67+
</div> :
68+
<div className="flex flex-col pt-10 items-center px-2">
69+
<h3 className="mb-2">No Example Projects</h3>
70+
<p className="text-center">Sorry there seem to be no example projects, that work with your current git provider.</p>
71+
</div>)
72+
}
73+
</div>
74+
</div>
75+
</Modal>;
76+
}

components/dashboard/src/workspaces/Workspaces.tsx

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

77
import React from "react";
8-
import { WhitelistedRepository, WorkspaceInfo } from "@gitpod/gitpod-protocol";
8+
import { WhitelistedRepository, Workspace, WorkspaceInfo } from "@gitpod/gitpod-protocol";
99
import Header from "../components/Header";
1010
import DropDown from "../components/DropDown"
1111
import { WorkspaceModel } from "./workspace-model";
1212
import { WorkspaceEntry } from "./WorkspaceEntry";
13-
import Modal from "../components/Modal";
1413
import { getGitpodService, gitpodHostUrl } from "../service/service";
14+
import {StartWorkspaceModal, WsStartEntry} from "./StartWorkspaceModal";
1515

1616
export interface WorkspacesProps {
1717
}
@@ -46,7 +46,7 @@ export class Workspaces extends React.Component<WorkspacesProps, WorkspacesState
4646

4747
render() {
4848
const wsModel = this.workspaceModel;
49-
const toggleTemplateModal = () => this.setState({
49+
const toggleStartWSModal = () => this.setState({
5050
isTemplateModelOpen: !this.state?.isTemplateModelOpen
5151
});
5252
const onActive = () => wsModel!.active = true;
@@ -58,7 +58,7 @@ export class Workspaces extends React.Component<WorkspacesProps, WorkspacesState
5858
<div className="flex">
5959
<div className="py-4">
6060
<svg width="16" height="16" fill="none" xmlns="http://www.w3.org/2000/svg">
61-
<path fill-rule="evenodd" clip-rule="evenodd" d="M6 2a4 4 0 100 8 4 4 0 000-8zM0 6a6 6 0 1110.89 3.477l4.817 4.816a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 010 6z" fill="#A8A29E"/>
61+
<path fillRule="evenodd" clipRule="evenodd" d="M6 2a4 4 0 100 8 4 4 0 000-8zM0 6a6 6 0 1110.89 3.477l4.817 4.816a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 010 6z" fill="#A8A29E"/>
6262
</svg>
6363
</div>
6464
<input className="border-0" type="text" placeholder="Search Workspaces" onChange={(v) => { if (wsModel) wsModel.setSearch(v.target.value) }} />
@@ -85,6 +85,10 @@ export class Workspaces extends React.Component<WorkspacesProps, WorkspacesState
8585
onClick: () => { if (wsModel) wsModel.limit = 200; }
8686
}]} />
8787
</div>
88+
{wsModel && this.state?.workspaces.length > 0 ?
89+
<button onClick={toggleStartWSModal} className="ml-2 font-medium">New Workspace</button>
90+
: null
91+
}
8892
</div>
8993
{wsModel && (
9094
this.state?.workspaces.length > 0 || wsModel.searchTerm ?
@@ -131,30 +135,56 @@ export class Workspaces extends React.Component<WorkspacesProps, WorkspacesState
131135
<div className="px-6 py-3 flex justify-between space-x-2 text-sm text-gray-400 border-t border-gray-200 h-96">
132136
<div className="flex flex-col items-center w-96 m-auto">
133137
<h3 className="text-center pb-3">No Active Workspaces</h3>
134-
<div className="text-center pb-6 text-gray-500">Prefix a git repository URL with gitpod.io/# or open a workspace template. <a className="text-gray-400 underline underline-thickness-thin underline-offset-small hover:text-gray-600" href="https://www.gitpod.io/docs/getting-started/">Learn how to get started</a></div>
135-
<button onClick={toggleTemplateModal} className="font-medium">Select Template</button>
138+
<div className="text-center pb-6 text-gray-500">Prefix any git repository URL with gitpod.io/# or start a new workspace for a recently used project. <a className="text-gray-400 underline underline-thickness-thin underline-offset-small hover:text-gray-600" href="https://www.gitpod.io/docs/getting-started/">Learn more</a></div>
139+
<button onClick={toggleStartWSModal} className="font-medium">New Workspace</button>
136140
</div>
137141
</div>
138142
</div>
139143
)}
140-
<Modal onClose={toggleTemplateModal} visible={!!this.state?.isTemplateModelOpen}>
141-
<h3 className="pb-2">Select Template</h3>
142-
{/* separator */}
143-
<div className="border-t mt-2 -mx-6 px-6 py-2">
144-
<p className="mt-1 mb-2 text-base">Select a template to open a workspace.</p>
145-
<div className="space-y-2 mt-4 overflow-y-scroll h-80 pr-2">
146-
{this.state?.repos && this.state.repos.map(r => {
147-
const url = gitpodHostUrl.withContext(r.url).toString();
148-
return <a key={r.name} href={url} className="rounded-xl group hover:bg-gray-100 flex p-4 my-1">
149-
<div className="w-full">
150-
<p className="text-base text-gray-800 font-semibold">{r.name}</p>
151-
<p>{r.url}</p>
152-
</div>
153-
</a>;
154-
})}
155-
</div>
156-
</div>
157-
</Modal>
144+
<StartWorkspaceModal
145+
onClose={toggleStartWSModal}
146+
visible={!!this.state?.isTemplateModelOpen}
147+
examples={this.state?.repos && this.state.repos.map(r => ({
148+
title: r.name,
149+
description: r.description || r.url,
150+
startUrl: gitpodHostUrl.withContext(r.url).toString()
151+
}))}
152+
recent={wsModel && this.state?.workspaces ?
153+
this.getRecentSuggestions()
154+
: []} />
158155
</>;
159156
}
157+
158+
protected getRecentSuggestions(): WsStartEntry[] {
159+
if (this.workspaceModel) {
160+
const all = this.workspaceModel.getAllFetchedWorkspaces();
161+
if (all && all.size > 0) {
162+
const index = new Map<string, WsStartEntry & {lastUse: string}>();
163+
for (const ws of Array.from(all.values())) {
164+
const repoUrl = Workspace.getFullRepositoryUrl(ws.workspace);
165+
if (repoUrl) {
166+
const lastUse = WorkspaceInfo.lastActiveISODate(ws);
167+
let entry = index.get(repoUrl);
168+
if (!entry) {
169+
entry = {
170+
title: Workspace.getFullRepositoryName(ws.workspace) || repoUrl,
171+
description: repoUrl,
172+
startUrl: gitpodHostUrl.withContext(repoUrl).toString(),
173+
lastUse,
174+
};
175+
index.set(repoUrl, entry);
176+
} else {
177+
if (entry.lastUse.localeCompare(lastUse) < 0) {
178+
entry.lastUse = lastUse;
179+
}
180+
}
181+
}
182+
}
183+
const list = Array.from(index.values());
184+
list.sort((a,b) => b.lastUse.localeCompare(a.lastUse));
185+
return list;
186+
}
187+
}
188+
return [];
189+
}
160190
}

components/dashboard/src/workspaces/workspace-model.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,8 @@ export class WorkspaceModel implements Disposable, Partial<GitpodClient> {
124124
info.latestInstance?.status?.phase !== 'stopped';
125125
}
126126

127+
public getAllFetchedWorkspaces(): Map<string, WorkspaceInfo> {
128+
return this.workspaces;
129+
}
130+
127131
}

components/gitpod-cli/go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ go 1.16
55
require (
66
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect
77
github.com/golang/mock v1.4.4
8+
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf
89
github.com/google/tcpproxy v0.0.0-20180808230851-dfa16c61dad2
910
github.com/gorilla/handlers v1.4.2
1011
github.com/manifoldco/promptui v0.3.2
1112
github.com/nicksnyder/go-i18n v1.10.1 // indirect
1213
github.com/pkg/errors v0.8.1
1314
github.com/sirupsen/logrus v1.7.0
1415
github.com/spf13/cobra v0.0.5
16+
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037
1517
gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780 // indirect
1618
gopkg.in/yaml.v2 v2.2.2
1719
)

components/supervisor/go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,11 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
9292
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
9393
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
9494
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
95+
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
9596
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
9697
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
98+
github.com/fatih/gomodifytags v1.13.0/go.mod h1:TbUyEjH1Zo0GkJd2Q52oVYqYcJ0eGNqG8bsiOb75P9c=
99+
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
97100
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
98101
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
99102
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
@@ -557,6 +560,7 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb
557560
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
558561
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
559562
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
563+
golang.org/x/tools v0.0.0-20180824175216-6c1c5e93cdc1/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
560564
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
561565
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
562566
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

components/workspacekit/go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,10 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
5454
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
5555
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
5656
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
57+
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
5758
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
59+
github.com/fatih/gomodifytags v1.13.0/go.mod h1:TbUyEjH1Zo0GkJd2Q52oVYqYcJ0eGNqG8bsiOb75P9c=
60+
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
5861
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
5962
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
6063
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@@ -368,6 +371,7 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb
368371
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
369372
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
370373
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
374+
golang.org/x/tools v0.0.0-20180824175216-6c1c5e93cdc1/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
371375
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
372376
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
373377
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

0 commit comments

Comments
 (0)