Skip to content

Allow renaming project #18630

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
Sep 4, 2023
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
41 changes: 39 additions & 2 deletions components/dashboard/src/projects/ProjectSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ import { getProjectSettingsMenu, getProjectTabs } from "./projects.routes";
import { Heading2, Subheading } from "../components/typography/headings";
import { RemoveProjectModal } from "./RemoveProjectModal";
import SelectWorkspaceClassComponent from "../components/SelectWorkspaceClassComponent";
import { TextInputField } from "../components/forms/TextInputField";
import { Button } from "../components/Button";
import { useRefreshProjects } from "../data/projects/list-projects-query";
import { useToast } from "../components/toasts/Toasts";

export function ProjectSettingsPage(props: { project?: Project; children?: React.ReactNode }) {
return (
Expand All @@ -34,10 +38,30 @@ export default function ProjectSettingsView() {
const { setProject } = useContext(ProjectContext);
const { project } = useCurrentProject();
const [showRemoveModal, setShowRemoveModal] = useState(false);
const [projectName, setProjectName] = useState(project?.name || "");
let badProjectName = projectName.length > 0 ? undefined : "Project name can not be blank.";
if (projectName.length > 32) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not we have it rather on server that it is aligned among all clients? Is it enforced by DB?

badProjectName = "Project name can not be longer than 32 characters.";
}
const history = useHistory();
const refreshProjects = useRefreshProjects();
const toast = useToast();

const updateProjectName = useCallback(
async (e: React.FormEvent) => {
e.preventDefault();
if (!project || badProjectName) return;

await getGitpodService().server.updateProjectPartial({ id: project.id, name: projectName });
setProject({ ...project, name: projectName });
refreshProjects(project.teamId);
toast.toast(`Project ${projectName} updated.`);
},
[project, badProjectName, projectName, setProject, refreshProjects, toast],
);

const updateProjectSettings = useCallback(
(settings: ProjectSettings) => {
async (settings: ProjectSettings) => {
if (!project) return;

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

return (
<ProjectSettingsPage project={project}>
<Heading2>Prebuilds</Heading2>
<Heading2>Project Name</Heading2>
<form onSubmit={updateProjectName}>
<TextInputField
hint="The name can be up to 32 characters long."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: Help text look useful, but is needed here all the time? Since we catch this with the validation anyway, thoughts on dropping the help text all together?

value={projectName}
error={badProjectName}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: Thanks for adding the validation here! 🐈

onChange={setProjectName}
/>

<Button className="mt-4" htmlType="submit" disabled={project?.name === projectName || !!badProjectName}>
Update Name
</Button>
</form>
<Heading2 className="mt-12">Prebuilds</Heading2>
<Subheading>Choose the workspace machine type for your prebuilds.</Subheading>
<div className="max-w-md">
<SelectWorkspaceClassComponent
Expand Down
1 change: 0 additions & 1 deletion components/dashboard/src/service/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ export function projectToProtocol(project: Project): ProtocolProject {
name: project.name,
cloneUrl: project.cloneUrl,
creationTime: project.creationTime?.toDate().toISOString() || "",
slug: project.slug,
teamId: project.teamId,
appInstallationId: "undefined",
settings: {
Expand Down
1 change: 0 additions & 1 deletion components/gitpod-db/src/project-db.spec.db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ class ProjectDBSpec {

const project = Project.create({
name: "some-project",
slug: "some-project",
cloneUrl: "some-random-clone-url",
teamId: "team-1",
appInstallationId: "app-1",
Expand Down
3 changes: 0 additions & 3 deletions components/gitpod-db/src/typeorm/entity/db-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ export class DBProject {
@Column()
name: string;

@Column()
slug?: string;

@Index("ind_cloneUrl")
@Column()
cloneUrl: string;
Expand Down
3 changes: 1 addition & 2 deletions components/gitpod-protocol/src/teams-projects-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ export interface ProjectSettings {
export interface Project {
id: string;
name: string;
slug?: string;
cloneUrl: string;
teamId: string;
appInstallationId: string;
Expand All @@ -52,7 +51,7 @@ export namespace Project {
};

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

export interface Overview {
Expand Down
6 changes: 0 additions & 6 deletions components/public-api-server/pkg/apiv1/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,6 @@ func (s *ProjectsService) CreateProject(ctx context.Context, req *connect.Reques
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("Name is a required argument."))
}

slug := strings.TrimSpace(spec.GetSlug())
if slug == "" {
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("Slug is a required argument."))
}

cloneURL := strings.TrimSpace(spec.GetCloneUrl())
if cloneURL == "" {
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("Clone URL is a required argument."))
Expand All @@ -63,7 +58,6 @@ func (s *ProjectsService) CreateProject(ctx context.Context, req *connect.Reques

project, err := conn.CreateProject(ctx, &protocol.CreateProjectOptions{
Name: name,
Slug: slug,
TeamID: teamID,
CloneURL: cloneURL,
AppInstallationID: "undefined", // sadly that's how we store cases where there is no AppInstallationID
Expand Down
24 changes: 0 additions & 24 deletions components/public-api-server/pkg/apiv1/project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,34 +45,17 @@ func TestProjectsService_CreateProject(t *testing.T) {
},
ExpectedError: "Name is a required argument.",
},
{
Name: "slug is required",
Spec: &v1.Project{
Name: "name",
},
ExpectedError: "Slug is a required argument.",
},
{
Name: "whitespace slug is rejected",
Spec: &v1.Project{
Name: "name",
Slug: " ",
},
ExpectedError: "Slug is a required argument.",
},
{
Name: "clone url is required",
Spec: &v1.Project{
Name: "name",
Slug: "slug",
},
ExpectedError: "Clone URL is a required argument.",
},
{
Name: "whitespace clone url is rejected",
Spec: &v1.Project{
Name: "name",
Slug: "slug",
CloneUrl: " ",
},
ExpectedError: "Clone URL is a required argument.",
Expand All @@ -81,7 +64,6 @@ func TestProjectsService_CreateProject(t *testing.T) {
Name: "team ID must be a valid UUID",
Spec: &v1.Project{
Name: "name",
Slug: "slug",
CloneUrl: "some.clone.url",
TeamId: "my-user",
},
Expand Down Expand Up @@ -111,15 +93,13 @@ func TestProjectsService_CreateProject(t *testing.T) {
projectsMock.EXPECT().CreateProject(gomock.Any(), &protocol.CreateProjectOptions{
TeamID: project.TeamID,
Name: project.Name,
Slug: project.Slug,
CloneURL: project.CloneURL,
AppInstallationID: "undefined",
}).Return(project, nil)

response, err := client.CreateProject(context.Background(), connect.NewRequest(&v1.CreateProjectRequest{
Project: &v1.Project{
Name: project.Name,
Slug: project.Slug,
CloneUrl: project.CloneURL,
TeamId: project.TeamID,
},
Expand Down Expand Up @@ -351,7 +331,6 @@ func newProject(p *protocol.Project) *protocol.Project {
result := &protocol.Project{
ID: uuid.New().String(),
Name: fmt.Sprintf("team-%d", r),
Slug: fmt.Sprintf("team-%d", r),
TeamID: uuid.New().String(),
CloneURL: "https://github.com/easyCZ/foobar",
AppInstallationID: "1337",
Expand All @@ -375,9 +354,6 @@ func newProject(p *protocol.Project) *protocol.Project {
if p.Name != "" {
result.Name = p.Name
}
if p.Slug != "" {
result.Slug = p.Slug
}
if p.UserID != "" {
result.UserID = p.UserID
}
Expand Down
1 change: 0 additions & 1 deletion components/public-api-server/pkg/apiv1/team.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,6 @@ func teamToAPIResponse(team *protocol.Team, members []*protocol.TeamMemberInfo,
return &v1.Team{
Id: team.ID,
Name: team.Name,
Slug: team.Slug,
Members: teamMembersToAPIResponse(members),
TeamInvitation: teamInviteToAPIResponse(invite),
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@ message Project {
// Required.
string name = 4;

// Slug is a short-hand identifier for a project.
// Read-only.
string slug = 5;
reserved 5;

// Clone URL is the clone URL on which this Project is based.
// Required.
Expand Down
Loading