diff --git a/gradle/changelog/fix_paging.yaml b/gradle/changelog/fix_paging.yaml
new file mode 100644
index 0000000000..bb9c476082
--- /dev/null
+++ b/gradle/changelog/fix_paging.yaml
@@ -0,0 +1,2 @@
+- type: fixed
+ description: Fix paging for too large page numbers ([#2097](https://github.com/scm-manager/scm-manager/pull/2097))
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 18331a4410..bdf7daec5f 100644
--- a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap
+++ b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap
@@ -4996,7 +4996,7 @@ exports[`Storyshots MarkdownView Custom code renderer 1`] = `
}
}
>
- To render plantuml as images within markdown, please install the scm-markdown-plantuml-plguin
+ To render plantuml as images within markdown, please install the scm-markdown-plantuml-plugin
actor Foo1
diff --git a/scm-ui/ui-components/src/markdown/MarkdownView.stories.tsx b/scm-ui/ui-components/src/markdown/MarkdownView.stories.tsx
index 326164fa05..724d76de9a 100644
--- a/scm-ui/ui-components/src/markdown/MarkdownView.stories.tsx
+++ b/scm-ui/ui-components/src/markdown/MarkdownView.stories.tsx
@@ -101,7 +101,7 @@ storiesOf("MarkdownView", module)
return (
- To render plantuml as images within markdown, please install the scm-markdown-plantuml-plguin
+ To render plantuml as images within markdown, please install the scm-markdown-plantuml-plugin
{value}
diff --git a/scm-ui/ui-webapp/src/admin/roles/containers/RepositoryRoles.tsx b/scm-ui/ui-webapp/src/admin/roles/containers/RepositoryRoles.tsx
index f3c64062c1..d732f34d15 100644
--- a/scm-ui/ui-webapp/src/admin/roles/containers/RepositoryRoles.tsx
+++ b/scm-ui/ui-webapp/src/admin/roles/containers/RepositoryRoles.tsx
@@ -22,8 +22,9 @@
* SOFTWARE.
*/
import React, { FC } from "react";
-import { useParams } from "react-router-dom";
+import { Redirect, useParams } from "react-router-dom";
import { useTranslation } from "react-i18next";
+import { RepositoryRoleCollection } from "@scm-manager/ui-types";
import {
CreateButton,
ErrorNotification,
@@ -32,11 +33,33 @@ import {
Notification,
Subtitle,
Title,
- urls
+ urls,
} from "@scm-manager/ui-components";
import PermissionRoleTable from "../components/PermissionRoleTable";
import { useRepositoryRoles } from "@scm-manager/ui-api";
+type RepositoryRolesPageProps = {
+ data?: RepositoryRoleCollection;
+ page: number;
+ baseUrl: string;
+};
+
+const RepositoryRolesPage: FC = ({ data, page, baseUrl }) => {
+ const [t] = useTranslation("users");
+ const roles = data?._embedded?.repositoryRoles;
+
+ if (!data || !roles || roles.length === 0) {
+ return {t("repositoryRole.overview.noPermissionRoles")};
+ }
+
+ return (
+ <>
+
+
+ >
+ );
+};
+
type Props = {
baseUrl: string;
};
@@ -44,29 +67,9 @@ type Props = {
const RepositoryRoles: FC = ({ baseUrl }) => {
const params = useParams();
const page = urls.getPageFromMatch({ params });
- const { isLoading: loading, error, data: list } = useRepositoryRoles({ page: page - 1 });
+ const { isLoading: loading, error, data } = useRepositoryRoles({ page: page - 1 });
const [t] = useTranslation("admin");
- const roles = list?._embedded.repositoryRoles;
- const canAddRoles = !!list?._links.create;
-
- const renderPermissionsTable = () => {
- if (list && roles && roles.length > 0) {
- return (
- <>
-
-
- >
- );
- }
- return {t("repositoryRole.overview.noPermissionRoles")};
- };
-
- const renderCreateButton = () => {
- if (canAddRoles) {
- return ;
- }
- return null;
- };
+ const canAddRoles = !!data?._links.create;
if (error) {
return ;
@@ -76,12 +79,18 @@ const RepositoryRoles: FC = ({ baseUrl }) => {
return ;
}
+ if (data && data.pageTotal < page && page > 1) {
+ return ;
+ }
+
return (
<>
- {renderPermissionsTable()}
- {renderCreateButton()}
+
+ {canAddRoles ? (
+
+ ) : null}
>
);
};
diff --git a/scm-ui/ui-webapp/src/groups/components/table/GroupTable.tsx b/scm-ui/ui-webapp/src/groups/components/table/GroupTable.tsx
index 27b42cff74..4027bf6110 100644
--- a/scm-ui/ui-webapp/src/groups/components/table/GroupTable.tsx
+++ b/scm-ui/ui-webapp/src/groups/components/table/GroupTable.tsx
@@ -21,34 +21,33 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-import React from "react";
-import { WithTranslation, withTranslation } from "react-i18next";
-import GroupRow from "./GroupRow";
+import React, { FC } from "react";
+import { useTranslation } from "react-i18next";
import { Group } from "@scm-manager/ui-types";
+import GroupRow from "./GroupRow";
-type Props = WithTranslation & {
+type Props = {
groups: Group[];
};
-class GroupTable extends React.Component {
- render() {
- const { groups, t } = this.props;
- return (
-
-
-
- | {t("group.name")} |
- {t("group.description")} |
-
-
-
- {groups.map((group, index) => {
- return ;
- })}
-
-
- );
- }
-}
+const GroupTable: FC = ({ groups }) => {
+ const [t] = useTranslation("groups");
-export default withTranslation("groups")(GroupTable);
+ return (
+
+
+
+ | {t("group.name")} |
+ {t("group.description")} |
+
+
+
+ {groups.map((group, index) => {
+ return ;
+ })}
+
+
+ );
+};
+
+export default GroupTable;
diff --git a/scm-ui/ui-webapp/src/groups/containers/Groups.tsx b/scm-ui/ui-webapp/src/groups/containers/Groups.tsx
index f6f922c4a1..7e22da2855 100644
--- a/scm-ui/ui-webapp/src/groups/containers/Groups.tsx
+++ b/scm-ui/ui-webapp/src/groups/containers/Groups.tsx
@@ -22,8 +22,10 @@
* SOFTWARE.
*/
import React, { FC } from "react";
+import { Redirect, useLocation, useParams } from "react-router-dom";
import { useTranslation } from "react-i18next";
-import { useLocation, useParams } from "react-router-dom";
+import { useGroups } from "@scm-manager/ui-api";
+import { Group, GroupCollection } from "@scm-manager/ui-types";
import {
CreateButton,
LinkPaginator,
@@ -31,32 +33,44 @@ import {
OverviewPageActions,
Page,
PageActions,
- urls
+ urls,
} from "@scm-manager/ui-components";
import { GroupTable } from "./../components/table";
-import { useGroups } from "@scm-manager/ui-api";
+
+type GroupPageProps = {
+ data?: GroupCollection;
+ groups?: Group[];
+ page: number;
+ search?: string;
+};
+
+const GroupPage: FC = ({ data, groups, page, search }) => {
+ const [t] = useTranslation("groups");
+
+ if (!data || !groups || groups.length === 0) {
+ return {t("groups.noGroups")};
+ }
+
+ return (
+ <>
+
+
+ >
+ );
+};
const Groups: FC = () => {
const location = useLocation();
const params = useParams();
const search = urls.getQueryStringFromLocation(location);
const page = urls.getPageFromMatch({ params });
- const { isLoading, error, data: list } = useGroups({ search, page: page - 1 });
+ const { isLoading, error, data } = useGroups({ search, page: page - 1 });
const [t] = useTranslation("groups");
- const groups = list?._embedded.groups;
- const canCreateGroups = !!list?._links.create;
-
- const renderGroupTable = () => {
- if (list && groups && groups.length > 0) {
- return (
- <>
-
-
- >
- );
- }
- return {t("groups.noGroups")};
- };
+ const groups = data?._embedded?.groups;
+ const canCreateGroups = !!data?._links.create;
+ if (data && data.pageTotal < page && page > 1) {
+ return ;
+ }
return (
{
loading={isLoading || !groups}
error={error || undefined}
>
- {renderGroupTable()}
- {canCreateGroups ? : null}
+
+ {canCreateGroups ? : null}
{
const match = useRouteMatch();
return urls.getPageFromMatch(match);
};
-const Changesets: FC = ({ repository, branch }) => {
- const page = usePage();
-
- const { isLoading, error, data } = useChangesets(repository, { branch, page: page - 1 });
-
- return ;
+type Props = {
+ repository: Repository;
+ branch?: Branch;
+ url: string;
};
-export const ChangesetsPanel: FC = ({ repository, error, isLoading, data }) => {
- const [t] = useTranslation("repos");
+const Changesets: FC = ({ repository, branch, url }) => {
const page = usePage();
+ const { isLoading, error, data } = useChangesets(repository, { branch, page: page - 1 });
+ const [t] = useTranslation("repos");
const changesets = data?._embedded?.changesets;
if (error) {
@@ -72,7 +60,11 @@ export const ChangesetsPanel: FC = ({ repository, error, isLoadi
return ;
}
- if (!changesets || changesets.length === 0) {
+ if (data && data.pageTotal < page && page > 1) {
+ return ;
+ }
+
+ if (!data || !changesets || changesets.length === 0) {
return {t("changesets.noChangesets")};
}
@@ -82,7 +74,7 @@ export const ChangesetsPanel: FC = ({ repository, error, isLoadi
-
+
);
diff --git a/scm-ui/ui-webapp/src/repos/containers/ChangesetsRoot.tsx b/scm-ui/ui-webapp/src/repos/containers/ChangesetsRoot.tsx
index ae02fb92d1..dc9aecafeb 100644
--- a/scm-ui/ui-webapp/src/repos/containers/ChangesetsRoot.tsx
+++ b/scm-ui/ui-webapp/src/repos/containers/ChangesetsRoot.tsx
@@ -59,8 +59,7 @@ const ChangesetRoot: FC = ({ repository, baseUrl, branches, selectedBranc
const onSelectBranch = (branch?: Branch) => {
if (branch) {
- const url = `${baseUrl}/branch/${encodeURIComponent(branch.name)}/changesets/`;
- history.push(url);
+ history.push(`${baseUrl}/branch/${encodeURIComponent(branch.name)}/changesets/`);
} else {
history.push(`${baseUrl}/changesets/`);
}
@@ -75,7 +74,7 @@ const ChangesetRoot: FC = ({ repository, baseUrl, branches, selectedBranc
switchViewLink={evaluateSwitchViewLink()}
/>
- b.name === selectedBranch)[0]} />
+ b.name === selectedBranch)[0]} url={url} />
>
);
diff --git a/scm-ui/ui-webapp/src/search/Hits.tsx b/scm-ui/ui-webapp/src/search/Hits.tsx
index e6cbb431b5..eff27eef23 100644
--- a/scm-ui/ui-webapp/src/search/Hits.tsx
+++ b/scm-ui/ui-webapp/src/search/Hits.tsx
@@ -34,7 +34,7 @@ import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
type Props = {
type: string;
- hits: HitType[];
+ hits?: HitType[];
};
const hitComponents: { [name: string]: FC } = {
@@ -69,12 +69,12 @@ const HitComponent: FC = ({ hit, type }) => (
const NoHits: FC = () => {
const [t] = useTranslation("commons");
- return {t("search.noHits")};
+ return {t("search.noHits")};
};
const Hits: FC = ({ type, hits }) => (
- {hits.length > 0 ? (
+ {hits && hits.length > 0 ? (
hits.map((hit, c) =>
)
) : (
diff --git a/scm-ui/ui-webapp/src/search/Results.tsx b/scm-ui/ui-webapp/src/search/Results.tsx
index d40839b050..654dd4ee85 100644
--- a/scm-ui/ui-webapp/src/search/Results.tsx
+++ b/scm-ui/ui-webapp/src/search/Results.tsx
@@ -26,6 +26,7 @@ import React, { FC } from "react";
import { QueryResult } from "@scm-manager/ui-types";
import Hits from "./Hits";
import { LinkPaginator } from "@scm-manager/ui-components";
+import { Redirect, useLocation } from "react-router-dom";
type Props = {
result: QueryResult;
@@ -35,9 +36,21 @@ type Props = {
};
const Results: FC
= ({ result, type, page, query }) => {
+ const location = useLocation();
+ const hits = result?._embedded?.hits;
+
+ let pathname = location.pathname;
+ if (!pathname.endsWith("/")) {
+ pathname = pathname.substring(0, pathname.lastIndexOf("/") + 1);
+ }
+
+ if (result && result.pageTotal < page && page > 1) {
+ return ;
+ }
+
return (
-
+
diff --git a/scm-ui/ui-webapp/src/users/components/table/UserTable.tsx b/scm-ui/ui-webapp/src/users/components/table/UserTable.tsx
index 586c01dfda..8240982499 100644
--- a/scm-ui/ui-webapp/src/users/components/table/UserTable.tsx
+++ b/scm-ui/ui-webapp/src/users/components/table/UserTable.tsx
@@ -21,35 +21,34 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-import React from "react";
-import { WithTranslation, withTranslation } from "react-i18next";
+import React, { FC } from "react";
+import { useTranslation } from "react-i18next";
import { User } from "@scm-manager/ui-types";
import UserRow from "./UserRow";
-type Props = WithTranslation & {
+type Props = {
users: User[];
};
-class UserTable extends React.Component
{
- render() {
- const { users, t } = this.props;
- return (
-
-
-
- | {t("user.name")} |
- {t("user.displayName")} |
- {t("user.mail")} |
-
-
-
- {users.map((user, index) => {
- return ;
- })}
-
-
- );
- }
-}
+const UserTable: FC = ({ users }) => {
+ const [t] = useTranslation("users");
-export default withTranslation("users")(UserTable);
+ return (
+
+
+
+ | {t("user.name")} |
+ {t("user.displayName")} |
+ {t("user.mail")} |
+
+
+
+ {users.map((user, index) => {
+ return ;
+ })}
+
+
+ );
+};
+
+export default UserTable;
diff --git a/scm-ui/ui-webapp/src/users/containers/Users.tsx b/scm-ui/ui-webapp/src/users/containers/Users.tsx
index bdbb5e8875..e1e69ccd86 100644
--- a/scm-ui/ui-webapp/src/users/containers/Users.tsx
+++ b/scm-ui/ui-webapp/src/users/containers/Users.tsx
@@ -22,8 +22,10 @@
* SOFTWARE.
*/
import React, { FC } from "react";
+import { Redirect, useLocation, useParams } from "react-router-dom";
import { useTranslation } from "react-i18next";
-import { useLocation, useParams } from "react-router-dom";
+import { useUsers } from "@scm-manager/ui-api";
+import { User, UserCollection } from "@scm-manager/ui-types";
import {
CreateButton,
LinkPaginator,
@@ -31,10 +33,31 @@ import {
OverviewPageActions,
Page,
PageActions,
- urls
+ urls,
} from "@scm-manager/ui-components";
import { UserTable } from "./../components/table";
-import { useUsers } from "@scm-manager/ui-api";
+
+type UserPageProps = {
+ data?: UserCollection;
+ users?: User[];
+ page: number;
+ search?: string;
+};
+
+const UserPage: FC = ({ data, users, page, search }) => {
+ const [t] = useTranslation("users");
+
+ if (!data || !users || users.length === 0) {
+ return {t("users.noUsers")};
+ }
+
+ return (
+ <>
+
+
+ >
+ );
+};
const Users: FC = () => {
const location = useLocation();
@@ -42,28 +65,13 @@ const Users: FC = () => {
const search = urls.getQueryStringFromLocation(location);
const page = urls.getPageFromMatch({ params });
const { isLoading, error, data } = useUsers({ page: page - 1, search });
- const users = data?._embedded.users;
const [t] = useTranslation("users");
+ const users = data?._embedded?.users;
const canAddUsers = !!data?._links.create;
- const renderUserTable = () => {
- if (data && users && users.length > 0) {
- return (
- <>
-
-
- >
- );
- }
- return {t("users.noUsers")};
- };
-
- const renderCreateButton = () => {
- if (canAddUsers) {
- return ;
- }
- return null;
- };
+ if (data && data.pageTotal < page && page > 1) {
+ return ;
+ }
return (
{
loading={isLoading || !users}
error={error || undefined}
>
- {renderUserTable()}
- {renderCreateButton()}
+
+ {canAddUsers ? : null}