diff --git a/docs/de/user/repo/assets/repository-overview-branches.png b/docs/de/user/repo/assets/repository-overview-branches.png deleted file mode 100644 index bd71d1d6cd..0000000000 Binary files a/docs/de/user/repo/assets/repository-overview-branches.png and /dev/null differ diff --git a/docs/de/user/repo/assets/repository-overview-changesets.png b/docs/de/user/repo/assets/repository-overview-changesets.png deleted file mode 100644 index 3130d770da..0000000000 Binary files a/docs/de/user/repo/assets/repository-overview-changesets.png and /dev/null differ diff --git a/docs/de/user/repo/assets/repository-overview-settings.png b/docs/de/user/repo/assets/repository-overview-settings.png deleted file mode 100644 index 658bd1933e..0000000000 Binary files a/docs/de/user/repo/assets/repository-overview-settings.png and /dev/null differ diff --git a/docs/de/user/repo/assets/repository-overview-sources.png b/docs/de/user/repo/assets/repository-overview-sources.png deleted file mode 100644 index 3ddc6ff598..0000000000 Binary files a/docs/de/user/repo/assets/repository-overview-sources.png and /dev/null differ diff --git a/docs/de/user/repo/assets/repository-overview.png b/docs/de/user/repo/assets/repository-overview.png index 5cb093f65a..750d41f7fb 100644 Binary files a/docs/de/user/repo/assets/repository-overview.png and b/docs/de/user/repo/assets/repository-overview.png differ diff --git a/docs/de/user/repo/index.md b/docs/de/user/repo/index.md index c9874b9967..a80cb2108d 100644 --- a/docs/de/user/repo/index.md +++ b/docs/de/user/repo/index.md @@ -12,21 +12,12 @@ Der Bereich Repository umfasst alles auf Basis von Repositories in Namespaces. D ### Übersicht -Auf der Übersichtsseite der Repositories werden die einzelnen Repositories nach Namespaces gegliedert aufgelistet. Jedes Repository wird durch eine Kachel dargestellt. Durch einen Klick auf diese Kachel öffnet sich die Readme Seite des jeweiligen Repositories. +Auf der Übersichtsseite der Repositories werden die einzelnen Repositories nach Namespaces gegliedert aufgelistet. ![Repository Übersicht](assets/repository-overview.png) Mithilfe der Auswahlbox oben auf der Seite kann die Anzeige der Repositories auf einen Namespace eingeschränkt werden. Alternativ kann die Überschrift eines Namespace angeklickt werden, um nur Repositories aus diesem Namespace anzuzeigen. Über die Suchleiste neben der Auswahlbox können die Repositories frei gefiltert werden. Die Suche filtert dabei nach dem Namespace, dem Namen und der Beschreibung der Repositories. -Ein bestimmter Tab des Repositories wie Branches, Changesets oder Sources kann über die blauen Icons geöffnet werden. - -Icon | Beschreibung ----|--- -![Repository Branches](assets/repository-overview-branches.png) | Öffnet die Branches-Übersicht für das Repository -![Repository Changesets](assets/repository-overview-changesets.png) | Öffnet die Changesets-Übersicht für das Repository -![Repository Sources](assets/repository-overview-sources.png) | Öffnet die Sources-Übersicht für das Repository -![Repository Einstellungen](assets/repository-overview-settings.png) | Öffnet die Einstellungen für das Repository - Zusätzlich können über das Icon rechts neben den Überschriften für die Namespaces weitere Einstellungen auf Namespace-Ebene vorgenommen werden. ### Repository erstellen diff --git a/docs/en/user/repo/assets/repository-overview-branches.png b/docs/en/user/repo/assets/repository-overview-branches.png deleted file mode 100644 index bd71d1d6cd..0000000000 Binary files a/docs/en/user/repo/assets/repository-overview-branches.png and /dev/null differ diff --git a/docs/en/user/repo/assets/repository-overview-changesets.png b/docs/en/user/repo/assets/repository-overview-changesets.png deleted file mode 100644 index 3130d770da..0000000000 Binary files a/docs/en/user/repo/assets/repository-overview-changesets.png and /dev/null differ diff --git a/docs/en/user/repo/assets/repository-overview-settings.png b/docs/en/user/repo/assets/repository-overview-settings.png deleted file mode 100644 index 658bd1933e..0000000000 Binary files a/docs/en/user/repo/assets/repository-overview-settings.png and /dev/null differ diff --git a/docs/en/user/repo/assets/repository-overview-sources.png b/docs/en/user/repo/assets/repository-overview-sources.png deleted file mode 100644 index 3ddc6ff598..0000000000 Binary files a/docs/en/user/repo/assets/repository-overview-sources.png and /dev/null differ diff --git a/docs/en/user/repo/assets/repository-overview.png b/docs/en/user/repo/assets/repository-overview.png index 813b19ae5b..09c34ffa47 100644 Binary files a/docs/en/user/repo/assets/repository-overview.png and b/docs/en/user/repo/assets/repository-overview.png differ diff --git a/docs/en/user/repo/index.md b/docs/en/user/repo/index.md index b3bd4153b8..e72becc28b 100644 --- a/docs/en/user/repo/index.md +++ b/docs/en/user/repo/index.md @@ -10,21 +10,12 @@ The Repository area includes everything based on repositories in namespaces. Thi * [Settings](settings/) ### Overview -The repository overview screen shows all repositories sorted by namespaces. Each repository is shown as a tile. After clicking on the tile the readme screen of the repository is shown. +The repository overview screen shows all repositories sorted by namespaces. ![Repository Overview](assets/repository-overview.png) Using the select box at the top of the page you can restrict the repositories shown for one namespace. Alternatively you can click on one namespace heading to show only repositories of this namespace. The search bar aside the select box can be used to arbitrarily filter the repositories by namespace, name and description. -The different tabs like branches, changesets or sources of the repository can be accessed through the blue icons. - -Icon | Description ----|--- -![Repository Branches](assets/repository-overview-branches.png) | Opens the branches overview for the repository -![Repository Changesets](assets/repository-overview-changesets.png) | Opens the changeset overview for the repository -![Repository Sources](assets/repository-overview-sources.png) | Opens the sources overview for the repository -![Repository Settings](assets/repository-overview-settings.png) | Opens the settings for the repository - Clicking the icon on the right-hand side of each namespace caption, you can change additional settings for this namespace. ### Create a Repository diff --git a/gradle/changelog/redesign_repo_overview.yaml b/gradle/changelog/redesign_repo_overview.yaml new file mode 100644 index 0000000000..5c08c5a5e7 --- /dev/null +++ b/gradle/changelog/redesign_repo_overview.yaml @@ -0,0 +1,2 @@ +- type: Changed + description: Redesign repository overview ([#1740](https://github.com/scm-manager/scm-manager/pull/1740)) diff --git a/gradle/changelog/remove_repo_shortlinks.yaml b/gradle/changelog/remove_repo_shortlinks.yaml new file mode 100644 index 0000000000..4118e05153 --- /dev/null +++ b/gradle/changelog/remove_repo_shortlinks.yaml @@ -0,0 +1,2 @@ +- type: changed + description: Remove repository shortlinks ([#1720](https://github.com/scm-manager/scm-manager/pull/1720)) diff --git a/scm-plugins/scm-git-plugin/src/main/js/CloneInformation.tsx b/scm-plugins/scm-git-plugin/src/main/js/CloneInformation.tsx index b0cf16a55a..60a58e852c 100644 --- a/scm-plugins/scm-git-plugin/src/main/js/CloneInformation.tsx +++ b/scm-plugins/scm-git-plugin/src/main/js/CloneInformation.tsx @@ -21,10 +21,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -import React, { FC, useEffect, useState } from "react"; +import React, { FC } from "react"; import { useTranslation } from "react-i18next"; -import { Link, Repository } from "@scm-manager/ui-types"; -import { apiClient } from "@scm-manager/ui-components"; +import { Repository } from "@scm-manager/ui-types"; +import { ErrorNotification, Loading } from "@scm-manager/ui-components"; +import { useChangesets, useDefaultBranch } from "@scm-manager/ui-api"; type Props = { url: string; @@ -33,32 +34,28 @@ type Props = { const CloneInformation: FC = ({ url, repository }) => { const [t] = useTranslation("plugins"); - const [defaultBranch, setDefaultBranch] = useState("main"); - const [emptyRepository, setEmptyRepository] = useState(); + const { + data: changesets, + error: changesetsError, + isLoading: changesetsLoading, + } = useChangesets(repository, { limit: 1 }); + const { + data: defaultBranchData, + isLoading: defaultBranchLoading, + error: defaultBranchError, + } = useDefaultBranch(repository); - useEffect(() => { - if (repository) { - apiClient - .get((repository._links.changesets as Link).href + "?limit=1") - .then(r => r.json()) - .then(result => { - const empty = result._embedded.changesets.length === 0; - setEmptyRepository(empty); - }); - } - }, [repository]); + if (changesetsLoading || defaultBranchLoading) { + return ; + } - useEffect(() => { - if (repository) { - apiClient - .get((repository._links.defaultBranch as Link).href) - .then(r => r.json()) - .then(r => r.defaultBranch && setDefaultBranch(r.defaultBranch)); - } - }, [repository]); + const error = changesetsError || defaultBranchError; + const branch = defaultBranchData?.defaultBranch; + const emptyRepository = changesets?._embedded.changesets.length === 0; return ( -
+
+

{t("scm-git-plugin.information.clone")}

         
@@ -68,7 +65,7 @@ const CloneInformation: FC = ({ url, repository }) => {
           {emptyRepository && (
             <>
               
- git checkout -b {defaultBranch} + git checkout -b {branch} )}
@@ -82,7 +79,7 @@ const CloneInformation: FC = ({ url, repository }) => {
cd {repository.name}
- git checkout -b {defaultBranch} + git checkout -b {branch}
echo "# {repository.name} " > README.md @@ -93,7 +90,7 @@ const CloneInformation: FC = ({ url, repository }) => {
git remote add origin {url}
- git push -u origin {defaultBranch} + git push -u origin {branch}
diff --git a/scm-plugins/scm-hg-plugin/src/main/js/ProtocolInformation.tsx b/scm-plugins/scm-hg-plugin/src/main/js/ProtocolInformation.tsx index 9a5a7e68ff..360dad1d2b 100644 --- a/scm-plugins/scm-hg-plugin/src/main/js/ProtocolInformation.tsx +++ b/scm-plugins/scm-hg-plugin/src/main/js/ProtocolInformation.tsx @@ -21,10 +21,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -import React, { FC, useEffect, useState } from "react"; +import React, { FC } from "react"; import { useTranslation } from "react-i18next"; -import { Link, Repository } from "@scm-manager/ui-types"; -import { apiClient, repositories } from "@scm-manager/ui-components"; +import { Repository } from "@scm-manager/ui-types"; +import { ErrorNotification, Loading, repositories } from "@scm-manager/ui-components"; +import { useChangesets } from "@scm-manager/ui-api"; type Props = { repository: Repository; @@ -32,27 +33,22 @@ type Props = { const ProtocolInformation: FC = ({ repository }) => { const [t] = useTranslation("plugins"); - const [emptyRepository, setEmptyRepository] = useState(); - + const { data, error, isLoading } = useChangesets(repository, { limit: 1 }); const href = repositories.getProtocolLinkByType(repository, "http"); - useEffect(() => { - if (repository) { - apiClient - .get((repository._links.changesets as Link).href) - .then(r => r.json()) - .then(result => { - const empty = result._embedded.changesets.length === 0; - setEmptyRepository(empty); - }); - } - }, [repository]); - if (!href) { return null; } + + if (isLoading) { + return ; + } + + const emptyRepository = data?._embedded.changesets.length === 0; + return (
+

{t("scm-hg-plugin.information.clone")}

         hg clone {href}
diff --git a/scm-ui/ui-api/src/branches.ts b/scm-ui/ui-api/src/branches.ts
index e65e9aa007..b5650e43de 100644
--- a/scm-ui/ui-api/src/branches.ts
+++ b/scm-ui/ui-api/src/branches.ts
@@ -33,7 +33,7 @@ export const useBranches = (repository: Repository): ApiResult
   const link = requiredLink(repository, "branches");
   return useQuery(
     repoQueryKey(repository, "branches"),
-    () => apiClient.get(link).then(response => response.json())
+    () => apiClient.get(link).then((response) => response.json())
     // we do not populate the cache for a single branch,
     // because we have no pagination for branches and if we have a lot of them
     // the population slows us down
@@ -43,7 +43,7 @@ export const useBranches = (repository: Repository): ApiResult
 export const useBranch = (repository: Repository, name: string): ApiResult => {
   const link = requiredLink(repository, "branches");
   return useQuery(branchQueryKey(repository, name), () =>
-    apiClient.get(concat(link, name)).then(response => response.json())
+    apiClient.get(concat(link, name)).then((response) => response.json())
   );
 };
 
@@ -51,14 +51,14 @@ const createBranch = (link: string) => {
   return (branch: BranchCreation) => {
     return apiClient
       .post(link, branch, "application/vnd.scmm-branchRequest+json;v=2")
-      .then(response => {
+      .then((response) => {
         const location = response.headers.get("Location");
         if (!location) {
           throw new Error("Server does not return required Location header");
         }
         return apiClient.get(location);
       })
-      .then(response => response.json());
+      .then((response) => response.json());
   };
 };
 
@@ -66,23 +66,23 @@ export const useCreateBranch = (repository: Repository) => {
   const queryClient = useQueryClient();
   const link = requiredLink(repository, "branches");
   const { mutate, isLoading, error, data } = useMutation(createBranch(link), {
-    onSuccess: async branch => {
+    onSuccess: async (branch) => {
       queryClient.setQueryData(branchQueryKey(repository, branch), branch);
       await queryClient.invalidateQueries(repoQueryKey(repository, "branches"));
-    }
+    },
   });
   return {
     create: (branch: BranchCreation) => mutate(branch),
     isLoading,
     error,
-    branch: data
+    branch: data,
   };
 };
 
 export const useDeleteBranch = (repository: Repository) => {
   const queryClient = useQueryClient();
   const { mutate, isLoading, error, data } = useMutation(
-    branch => {
+    (branch) => {
       const deleteUrl = (branch._links.delete as Link).href;
       return apiClient.delete(deleteUrl);
     },
@@ -90,13 +90,22 @@ export const useDeleteBranch = (repository: Repository) => {
       onSuccess: async (_, branch) => {
         queryClient.removeQueries(branchQueryKey(repository, branch));
         await queryClient.invalidateQueries(repoQueryKey(repository, "branches"));
-      }
+      },
     }
   );
   return {
     remove: (branch: Branch) => mutate(branch),
     isLoading,
     error,
-    isDeleted: !!data
+    isDeleted: !!data,
   };
 };
+
+type DefaultBranch = { defaultBranch: string };
+
+export const useDefaultBranch = (repository: Repository): ApiResult => {
+  const link = requiredLink(repository, "defaultBranch");
+  return useQuery(branchQueryKey(repository, "__default-branch"), () =>
+    apiClient.get(link).then((response) => response.json())
+  );
+};
diff --git a/scm-ui/ui-api/src/changesets.ts b/scm-ui/ui-api/src/changesets.ts
index dfd4d43aaa..b10854acb4 100644
--- a/scm-ui/ui-api/src/changesets.ts
+++ b/scm-ui/ui-api/src/changesets.ts
@@ -32,6 +32,7 @@ import { concat } from "./urls";
 type UseChangesetsRequest = {
   branch?: Branch;
   page?: string | number;
+  limit?: number;
 };
 
 export const changesetQueryKey = (repository: NamespaceAndName, id: string) => {
@@ -53,23 +54,29 @@ export const useChangesets = (
     link = requiredLink(repository, "changesets");
   }
 
-  if (request?.page) {
-    link = `${link}?page=${request.page}`;
+  if (request?.page || request?.limit) {
+    if (request?.page && request?.limit) {
+      link = `${link}?page=${request.page}&limit=${request.limit}`;
+    } else if (request.page) {
+      link = `${link}?page=${request.page}`;
+    } else if (request.limit) {
+      link = `${link}?limit=${request.limit}`;
+    }
   }
 
   const key = branchQueryKey(repository, branch, "changesets", request?.page || 0);
-  return useQuery(key, () => apiClient.get(link).then(response => response.json()), {
-    onSuccess: changesetCollection => {
-      changesetCollection._embedded.changesets.forEach(changeset => {
+  return useQuery(key, () => apiClient.get(link).then((response) => response.json()), {
+    onSuccess: (changesetCollection) => {
+      changesetCollection._embedded.changesets.forEach((changeset) => {
         queryClient.setQueryData(changesetQueryKey(repository, changeset.id), changeset);
       });
-    }
+    },
   });
 };
 
 export const useChangeset = (repository: Repository, id: string): ApiResult => {
   const changesetsLink = requiredLink(repository, "changesets");
   return useQuery(changesetQueryKey(repository, id), () =>
-    apiClient.get(concat(changesetsLink, id)).then(response => response.json())
+    apiClient.get(concat(changesetsLink, id)).then((response) => response.json())
   );
 };
diff --git a/scm-ui/ui-components/src/BranchSelector.tsx b/scm-ui/ui-components/src/BranchSelector.tsx
index c3421ad74f..27cd0a414c 100644
--- a/scm-ui/ui-components/src/BranchSelector.tsx
+++ b/scm-ui/ui-components/src/BranchSelector.tsx
@@ -25,7 +25,7 @@ import React, { FC } from "react";
 import classNames from "classnames";
 import styled from "styled-components";
 import { Branch } from "@scm-manager/ui-types";
-import DropDown from "./forms/DropDown";
+import { Select } from "./forms";
 
 type Props = {
   branches: Branch[];
@@ -58,12 +58,12 @@ const BranchSelector: FC = ({ branches, onSelectBranch, selectedBranch, l
         
- b.name)} - optionSelected={branch => onSelectBranch(branches.filter(b => b.name === branch)[0])} + options={branches.map((b) => ({ label: b.name, value: b.name }))} + onChange={(branch) => onSelectBranch(branches.filter((b) => b.name === branch)[0])} disabled={!!disabled} - preselectedOption={selectedBranch} + value={selectedBranch} /> diff --git a/scm-ui/ui-components/src/OverviewPageActions.tsx b/scm-ui/ui-components/src/OverviewPageActions.tsx index 1dd667afbe..2eb060e816 100644 --- a/scm-ui/ui-components/src/OverviewPageActions.tsx +++ b/scm-ui/ui-components/src/OverviewPageActions.tsx @@ -24,8 +24,8 @@ import React, { FC, useState } from "react"; import { useHistory, useLocation } from "react-router-dom"; import classNames from "classnames"; -import { Button, DropDown, urls } from "./index"; -import { FilterInput } from "./forms"; +import { Button, urls } from "./index"; +import { FilterInput, Select } from "./forms"; type Props = { showCreateButton: boolean; @@ -52,7 +52,7 @@ const OverviewPageActions: FC = ({ groupSelected, label, testId, - searchPlaceholder + searchPlaceholder, }) => { const history = useHistory(); const location = useLocation(); @@ -61,11 +61,11 @@ const OverviewPageActions: FC = ({ const groupSelector = groups && (
- ({ value: g, label: g }))} + value={currentGroup} + onChange={groupSelected} />
); diff --git a/scm-ui/ui-components/src/Tooltip.tsx b/scm-ui/ui-components/src/Tooltip.tsx index 9277ad0d01..6285055014 100644 --- a/scm-ui/ui-components/src/Tooltip.tsx +++ b/scm-ui/ui-components/src/Tooltip.tsx @@ -26,14 +26,16 @@ import React, { ReactNode } from "react"; type Props = { message: string; className?: string; - location: string; + location: TooltipLocation; multiline?: boolean; children: ReactNode; }; +export type TooltipLocation = "bottom" | "right" | "top" | "left"; + class Tooltip extends React.Component { static defaultProps = { - location: "right" + location: "right", }; render() { diff --git a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap index 748535bfae..b0f1618ab2 100644 --- a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap +++ b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap @@ -1628,29 +1628,31 @@ exports[`Storyshots BranchSelector Default 1`] = `
-
- -
+ + + +
+
@@ -57074,6 +57076,160 @@ Array [ ] `; +exports[`Storyshots GroupEntry Default 1`] = ` + +`; + +exports[`Storyshots GroupEntry With long texts 1`] = ` + +`; + exports[`Storyshots Help Default 1`] = ` - +
`; exports[`Storyshots RepositoryEntry Avatar EP 1`] = ` `; exports[`Storyshots RepositoryEntry Before Title EP 1`] = ` `; exports[`Storyshots RepositoryEntry Default 1`] = ` `; exports[`Storyshots RepositoryEntry Exporting 1`] = ` - + `; exports[`Storyshots RepositoryEntry HealthCheck Failure 1`] = ` - + `; exports[`Storyshots RepositoryEntry MultiRepositoryTags 1`] = ` -`; - -exports[`Storyshots RepositoryEntry Quick Link EP 1`] = ` -
- -
-
-

- Logo -

-
-
-
-
-

- - heartOfGold -

- -

+ +
+ + + repository.archived + + + + + repository.exporting + + +
+
+
+

- The starship Heart of Gold was the first spacecraft to make use of the Infinite Improbability Drive + Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

-
+
`; exports[`Storyshots RepositoryEntry RepositoryFlag EP 1`] = ` - + + +`; + +exports[`Storyshots RepositoryEntry With long texts 1`] = ` +
+
+ +
+
+
+
+ Logo +
+
+
+
+
+ + veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeery-loooooooooooooooooooooooooooooooooooooooooooooooooooong-repooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo-naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaame + + + +
+ +
+
+

+ Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. +

+
+
+
+
+ + + + + + +
+
+
+
`; diff --git a/scm-ui/ui-components/src/forms/Select.tsx b/scm-ui/ui-components/src/forms/Select.tsx index 8eff28ef84..06d9a3cea1 100644 --- a/scm-ui/ui-components/src/forms/Select.tsx +++ b/scm-ui/ui-components/src/forms/Select.tsx @@ -44,6 +44,7 @@ type BaseProps = { testId?: string; defaultValue?: string; readOnly?: boolean; + className?: string; }; const InnerSelect: FC> = ({ @@ -56,6 +57,7 @@ const InnerSelect: FC> = ({ disabled, testId, readOnly, + className, ...props }) => { const field = useInnerRef(props.innerRef); @@ -100,7 +102,7 @@ const InnerSelect: FC> = ({ return (
-
+