Skip to content

Allow renaming project #18629

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

Closed
wants to merge 1 commit into from
Closed
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
40 changes: 39 additions & 1 deletion 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 || "");
const badProjectName =
projectName.length > 0 && projectName.length <= 32
? undefined
: "Project name can not be blank and must 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,6 +104,20 @@ export default function ProjectSettingsView() {

return (
<ProjectSettingsPage project={project}>
<Heading2>Project Details</Heading2>
<form onSubmit={updateProjectName}>
<TextInputField
label="Name"
hint="The name of your company or organization"
value={project.name}
error={badProjectName}
onChange={setProjectName}
/>

<Button className="mt-4" htmlType="submit" disabled={project?.name === projectName || !badProjectName}>
Update Project
</Button>
</form>
<Heading2>Prebuilds</Heading2>
<Subheading>Choose the workspace machine type for your prebuilds.</Subheading>
<div className="max-w-md">
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