Skip to content

Commit 71912f7

Browse files
authored
Allow renaming project (#18630)
remove project#slug
1 parent d6e1866 commit 71912f7

File tree

12 files changed

+175
-196
lines changed

12 files changed

+175
-196
lines changed

components/dashboard/src/projects/ProjectSettings.tsx

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ import { getProjectSettingsMenu, getProjectTabs } from "./projects.routes";
1616
import { Heading2, Subheading } from "../components/typography/headings";
1717
import { RemoveProjectModal } from "./RemoveProjectModal";
1818
import SelectWorkspaceClassComponent from "../components/SelectWorkspaceClassComponent";
19+
import { TextInputField } from "../components/forms/TextInputField";
20+
import { Button } from "../components/Button";
21+
import { useRefreshProjects } from "../data/projects/list-projects-query";
22+
import { useToast } from "../components/toasts/Toasts";
1923

2024
export function ProjectSettingsPage(props: { project?: Project; children?: React.ReactNode }) {
2125
return (
@@ -34,10 +38,30 @@ export default function ProjectSettingsView() {
3438
const { setProject } = useContext(ProjectContext);
3539
const { project } = useCurrentProject();
3640
const [showRemoveModal, setShowRemoveModal] = useState(false);
41+
const [projectName, setProjectName] = useState(project?.name || "");
42+
let badProjectName = projectName.length > 0 ? undefined : "Project name can not be blank.";
43+
if (projectName.length > 32) {
44+
badProjectName = "Project name can not be longer than 32 characters.";
45+
}
3746
const history = useHistory();
47+
const refreshProjects = useRefreshProjects();
48+
const toast = useToast();
49+
50+
const updateProjectName = useCallback(
51+
async (e: React.FormEvent) => {
52+
e.preventDefault();
53+
if (!project || badProjectName) return;
54+
55+
await getGitpodService().server.updateProjectPartial({ id: project.id, name: projectName });
56+
setProject({ ...project, name: projectName });
57+
refreshProjects(project.teamId);
58+
toast.toast(`Project ${projectName} updated.`);
59+
},
60+
[project, badProjectName, projectName, setProject, refreshProjects, toast],
61+
);
3862

3963
const updateProjectSettings = useCallback(
40-
(settings: ProjectSettings) => {
64+
async (settings: ProjectSettings) => {
4165
if (!project) return;
4266

4367
const newSettings = { ...project.settings, ...settings };
@@ -80,7 +104,20 @@ export default function ProjectSettingsView() {
80104

81105
return (
82106
<ProjectSettingsPage project={project}>
83-
<Heading2>Prebuilds</Heading2>
107+
<Heading2>Project Name</Heading2>
108+
<form onSubmit={updateProjectName}>
109+
<TextInputField
110+
hint="The name can be up to 32 characters long."
111+
value={projectName}
112+
error={badProjectName}
113+
onChange={setProjectName}
114+
/>
115+
116+
<Button className="mt-4" htmlType="submit" disabled={project?.name === projectName || !!badProjectName}>
117+
Update Name
118+
</Button>
119+
</form>
120+
<Heading2 className="mt-12">Prebuilds</Heading2>
84121
<Subheading>Choose the workspace machine type for your prebuilds.</Subheading>
85122
<div className="max-w-md">
86123
<SelectWorkspaceClassComponent

components/dashboard/src/service/public-api.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ export function projectToProtocol(project: Project): ProtocolProject {
9999
name: project.name,
100100
cloneUrl: project.cloneUrl,
101101
creationTime: project.creationTime?.toDate().toISOString() || "",
102-
slug: project.slug,
103102
teamId: project.teamId,
104103
appInstallationId: "undefined",
105104
settings: {

components/gitpod-db/src/project-db.spec.db.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ class ProjectDBSpec {
4545

4646
const project = Project.create({
4747
name: "some-project",
48-
slug: "some-project",
4948
cloneUrl: "some-random-clone-url",
5049
teamId: "team-1",
5150
appInstallationId: "app-1",

components/gitpod-db/src/typeorm/entity/db-project.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ export class DBProject {
1818
@Column()
1919
name: string;
2020

21-
@Column()
22-
slug?: string;
23-
2421
@Index("ind_cloneUrl")
2522
@Column()
2623
cloneUrl: string;

components/gitpod-protocol/src/teams-projects-protocol.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ export interface ProjectSettings {
2727
export interface Project {
2828
id: string;
2929
name: string;
30-
slug?: string;
3130
cloneUrl: string;
3231
teamId: string;
3332
appInstallationId: string;
@@ -52,7 +51,7 @@ export namespace Project {
5251
};
5352

5453
export function slug(p: Project): string {
55-
return (p.slug || p.name) + "-" + p.id;
54+
return p.name + "-" + p.id;
5655
}
5756

5857
export interface Overview {

components/public-api-server/pkg/apiv1/project.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,6 @@ func (s *ProjectsService) CreateProject(ctx context.Context, req *connect.Reques
4040
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("Name is a required argument."))
4141
}
4242

43-
slug := strings.TrimSpace(spec.GetSlug())
44-
if slug == "" {
45-
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("Slug is a required argument."))
46-
}
47-
4843
cloneURL := strings.TrimSpace(spec.GetCloneUrl())
4944
if cloneURL == "" {
5045
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("Clone URL is a required argument."))
@@ -63,7 +58,6 @@ func (s *ProjectsService) CreateProject(ctx context.Context, req *connect.Reques
6358

6459
project, err := conn.CreateProject(ctx, &protocol.CreateProjectOptions{
6560
Name: name,
66-
Slug: slug,
6761
TeamID: teamID,
6862
CloneURL: cloneURL,
6963
AppInstallationID: "undefined", // sadly that's how we store cases where there is no AppInstallationID

components/public-api-server/pkg/apiv1/project_test.go

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -45,34 +45,17 @@ func TestProjectsService_CreateProject(t *testing.T) {
4545
},
4646
ExpectedError: "Name is a required argument.",
4747
},
48-
{
49-
Name: "slug is required",
50-
Spec: &v1.Project{
51-
Name: "name",
52-
},
53-
ExpectedError: "Slug is a required argument.",
54-
},
55-
{
56-
Name: "whitespace slug is rejected",
57-
Spec: &v1.Project{
58-
Name: "name",
59-
Slug: " ",
60-
},
61-
ExpectedError: "Slug is a required argument.",
62-
},
6348
{
6449
Name: "clone url is required",
6550
Spec: &v1.Project{
6651
Name: "name",
67-
Slug: "slug",
6852
},
6953
ExpectedError: "Clone URL is a required argument.",
7054
},
7155
{
7256
Name: "whitespace clone url is rejected",
7357
Spec: &v1.Project{
7458
Name: "name",
75-
Slug: "slug",
7659
CloneUrl: " ",
7760
},
7861
ExpectedError: "Clone URL is a required argument.",
@@ -81,7 +64,6 @@ func TestProjectsService_CreateProject(t *testing.T) {
8164
Name: "team ID must be a valid UUID",
8265
Spec: &v1.Project{
8366
Name: "name",
84-
Slug: "slug",
8567
CloneUrl: "some.clone.url",
8668
TeamId: "my-user",
8769
},
@@ -111,15 +93,13 @@ func TestProjectsService_CreateProject(t *testing.T) {
11193
projectsMock.EXPECT().CreateProject(gomock.Any(), &protocol.CreateProjectOptions{
11294
TeamID: project.TeamID,
11395
Name: project.Name,
114-
Slug: project.Slug,
11596
CloneURL: project.CloneURL,
11697
AppInstallationID: "undefined",
11798
}).Return(project, nil)
11899

119100
response, err := client.CreateProject(context.Background(), connect.NewRequest(&v1.CreateProjectRequest{
120101
Project: &v1.Project{
121102
Name: project.Name,
122-
Slug: project.Slug,
123103
CloneUrl: project.CloneURL,
124104
TeamId: project.TeamID,
125105
},
@@ -351,7 +331,6 @@ func newProject(p *protocol.Project) *protocol.Project {
351331
result := &protocol.Project{
352332
ID: uuid.New().String(),
353333
Name: fmt.Sprintf("team-%d", r),
354-
Slug: fmt.Sprintf("team-%d", r),
355334
TeamID: uuid.New().String(),
356335
CloneURL: "https://github.com/easyCZ/foobar",
357336
AppInstallationID: "1337",
@@ -375,9 +354,6 @@ func newProject(p *protocol.Project) *protocol.Project {
375354
if p.Name != "" {
376355
result.Name = p.Name
377356
}
378-
if p.Slug != "" {
379-
result.Slug = p.Slug
380-
}
381357
if p.UserID != "" {
382358
result.UserID = p.UserID
383359
}

components/public-api-server/pkg/apiv1/team.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,6 @@ func teamToAPIResponse(team *protocol.Team, members []*protocol.TeamMemberInfo,
295295
return &v1.Team{
296296
Id: team.ID,
297297
Name: team.Name,
298-
Slug: team.Slug,
299298
Members: teamMembersToAPIResponse(members),
300299
TeamInvitation: teamInviteToAPIResponse(invite),
301300
}

components/public-api/gitpod/experimental/v1/projects.proto

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@ message Project {
2323
// Required.
2424
string name = 4;
2525

26-
// Slug is a short-hand identifier for a project.
27-
// Read-only.
28-
string slug = 5;
26+
reserved 5;
2927

3028
// Clone URL is the clone URL on which this Project is based.
3129
// Required.

0 commit comments

Comments
 (0)