Skip to content

Commit 1cdab1b

Browse files
Adding name form to repository config detail page (#18943)
* Adding repo config name form * fixing validation logic * use a toast vs. disabling button when there's an error * update comment with more context * cleanup * remove protocol * bumping up name limit to 100 to account for recent changes --------- Co-authored-by: Filip Troníček <[email protected]>
1 parent f09c35a commit 1cdab1b

File tree

6 files changed

+208
-39
lines changed

6 files changed

+208
-39
lines changed

components/dashboard/src/data/projects/list-projects-query.ts

Lines changed: 0 additions & 36 deletions
This file was deleted.
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/**
2+
* Copyright (c) 2023 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 { Project } from "@gitpod/gitpod-protocol/lib/teams-projects-protocol";
8+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
9+
import { useCurrentOrg } from "../organizations/orgs-query";
10+
import { listAllProjects, projectsService } from "../../service/public-api";
11+
import { PartialProject } from "@gitpod/gitpod-protocol";
12+
import { getGitpodService } from "../../service/service";
13+
14+
const BASE_KEY = "projects";
15+
16+
type UseProjectArgs = {
17+
id: string;
18+
};
19+
export const useProject = ({ id }: UseProjectArgs) => {
20+
const { data: org } = useCurrentOrg();
21+
22+
return useQuery<Project | null, Error>(
23+
getProjectQueryKey(org?.id || "", id),
24+
async () => {
25+
if (!org) {
26+
throw new Error("No current org");
27+
}
28+
29+
// TODO: This is temporary until we create a project by id endpoint
30+
// Waiting to tackle that once we have the new grpc setup for server
31+
const projects = await listAllProjects({ orgId: org.id });
32+
const project = projects.find((p) => p.id === id);
33+
34+
return project || null;
35+
},
36+
{
37+
enabled: !!org,
38+
},
39+
);
40+
};
41+
42+
const getProjectQueryKey = (orgId: string, id: string) => {
43+
return [BASE_KEY, { orgId, id }];
44+
};
45+
46+
type ListProjectsQueryArgs = {
47+
page: number;
48+
pageSize: number;
49+
};
50+
51+
export const useListProjectsQuery = ({ page, pageSize }: ListProjectsQueryArgs) => {
52+
const { data: org } = useCurrentOrg();
53+
54+
return useQuery(
55+
getListProjectsQueryKey(org?.id || "", { page, pageSize }),
56+
async () => {
57+
if (!org) {
58+
throw new Error("No org currently selected");
59+
}
60+
61+
return projectsService.listProjects({ teamId: org.id, pagination: { page, pageSize } });
62+
},
63+
{
64+
enabled: !!org,
65+
},
66+
);
67+
};
68+
69+
export const getListProjectsQueryKey = (orgId: string, args?: ListProjectsQueryArgs) => {
70+
const key: any[] = [BASE_KEY, "list", { orgId }];
71+
if (args) {
72+
key.push(args);
73+
}
74+
75+
return key;
76+
};
77+
78+
export const useUpdateProject = () => {
79+
const { data: org } = useCurrentOrg();
80+
const client = useQueryClient();
81+
82+
return useMutation<void, Error, PartialProject>(async ({ id, name }) => {
83+
if (!org) {
84+
throw new Error("No org currently selected");
85+
}
86+
87+
await getGitpodService().server.updateProjectPartial({ id, name });
88+
89+
// Invalidate project
90+
client.invalidateQueries(getProjectQueryKey(org.id, id));
91+
// Invalidate project list queries
92+
client.invalidateQueries(getListProjectsQueryKey(org.id));
93+
});
94+
};

components/dashboard/src/hooks/use-pretty-repo-url.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,19 @@
44
* See License.AGPL.txt in the project root for license information.
55
*/
66

7+
import { useMemo } from "react";
8+
9+
// Given a URL string:
10+
// * Strips protocol
11+
// * Removes a trailing .git if present
712
export const usePrettyRepoURL = (url: string) => {
8-
return url.endsWith(".git") ? url.slice(0, -4) : url;
13+
return useMemo(() => {
14+
let urlString = url;
15+
try {
16+
const parsedURL = new URL(url);
17+
urlString = `${parsedURL.host}${parsedURL.pathname}`;
18+
} catch (e) {}
19+
20+
return urlString.endsWith(".git") ? urlString.slice(0, -4) : urlString;
21+
}, [url]);
922
};

components/dashboard/src/repositories/detail/RepositoryDetail.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,43 @@
77
import { FC } from "react";
88
import Header from "../../components/Header";
99
import { useParams } from "react-router";
10+
import { useProject } from "../../data/projects/project-queries";
11+
import { Button } from "../../components/Button";
12+
import { RepositoryNameForm } from "./RepositoryName";
13+
import { Loader2 } from "lucide-react";
14+
import Alert from "../../components/Alert";
1015

1116
type PageRouteParams = {
1217
id: string;
1318
};
1419
const RepositoryDetailPage: FC = () => {
1520
const { id } = useParams<PageRouteParams>();
21+
const { data, error, isLoading, refetch } = useProject({ id });
1622

1723
return (
1824
<>
1925
<Header title="Repository Detail" subtitle="" />
2026
<div className="app-container">
21-
<span>id: {id}</span>
27+
{isLoading && <Loader2 className="animate-spin" />}
28+
{error && (
29+
<div className="gap-4">
30+
<Alert type="error">
31+
<span>Failed to load repository configuration</span>
32+
<pre>{error.message}</pre>
33+
</Alert>
34+
35+
<Button type="danger" onClick={refetch}>
36+
Retry
37+
</Button>
38+
</div>
39+
)}
40+
{!isLoading &&
41+
(!data ? (
42+
// TODO: add a better not-found UI w/ link back to repositories
43+
<div>Sorry, we couldn't find that repository configuration.</div>
44+
) : (
45+
<RepositoryNameForm project={data} />
46+
))}
2247
</div>
2348
</>
2449
);
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/**
2+
* Copyright (c) 2023 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+
// TODO: fix mismatched project types when we build repo configuration apis
8+
import { Project } from "@gitpod/gitpod-protocol/lib/teams-projects-protocol";
9+
import { Button } from "../../components/Button";
10+
import { TextInputField } from "../../components/forms/TextInputField";
11+
import { FC, useCallback, useState } from "react";
12+
import { useUpdateProject } from "../../data/projects/project-queries";
13+
import { useToast } from "../../components/toasts/Toasts";
14+
import { useOnBlurError } from "../../hooks/use-onblur-error";
15+
16+
const MAX_LENGTH = 100;
17+
18+
type Props = {
19+
project: Project;
20+
};
21+
22+
export const RepositoryNameForm: FC<Props> = ({ project }) => {
23+
const { toast } = useToast();
24+
const updateProject = useUpdateProject();
25+
const [projectName, setProjectName] = useState(project.name);
26+
27+
const nameError = useOnBlurError("Sorry, this name is too long.", projectName.length <= MAX_LENGTH);
28+
29+
const updateName = useCallback(
30+
async (e: React.FormEvent) => {
31+
e.preventDefault();
32+
33+
if (!nameError.isValid) {
34+
toast("Please correct the errors with the name.");
35+
return;
36+
}
37+
38+
updateProject.mutate(
39+
{
40+
id: project.id,
41+
name: projectName,
42+
},
43+
{
44+
onSuccess: () => {
45+
toast(`Configuration name set to "${projectName}".`);
46+
},
47+
},
48+
);
49+
},
50+
[nameError.isValid, updateProject, project.id, projectName, toast],
51+
);
52+
53+
return (
54+
<form onSubmit={updateName}>
55+
<TextInputField
56+
hint={`The name can be up to ${MAX_LENGTH} characters long.`}
57+
value={projectName}
58+
error={nameError.message}
59+
onChange={setProjectName}
60+
onBlur={nameError.onBlur}
61+
/>
62+
63+
<Button
64+
className="mt-4"
65+
htmlType="submit"
66+
disabled={project.name === projectName}
67+
loading={updateProject.isLoading}
68+
>
69+
Update Name
70+
</Button>
71+
</form>
72+
);
73+
};

components/dashboard/src/repositories/list/RepositoryList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import { FC, useCallback, useState } from "react";
88
import Header from "../../components/Header";
9-
import { useListProjectsQuery } from "../../data/projects/list-projects-query";
9+
import { useListProjectsQuery } from "../../data/projects/project-queries";
1010
import { Loader2 } from "lucide-react";
1111
import { useHistory } from "react-router-dom";
1212
import { Project } from "@gitpod/gitpod-protocol";

0 commit comments

Comments
 (0)