Get links for repositories by namespaces from server

This commit is contained in:
René Pfeuffer
2020-09-04 21:44:48 +02:00
parent 2cfdaf1b0b
commit e3a2783bfd
8 changed files with 145 additions and 21 deletions

View File

@@ -39,12 +39,19 @@ export type RepositoryCreation = Repository & {
contextEntries: { [key: string]: any };
};
export type Namespace = {
namespace: string;
_links: Links;
};
export type RepositoryCollection = PagedCollection & {
_embedded: {
repositories: Repository[] | string[];
};
};
export type NamespaceCollection = Namespace[];
export type RepositoryGroup = {
name: string;
repositories: Repository[];

View File

@@ -29,7 +29,7 @@ export { Me } from "./Me";
export { DisplayedUser, User } from "./User";
export { Group, Member } from "./Group";
export { Repository, RepositoryCollection, RepositoryGroup, RepositoryCreation } from "./Repositories";
export { Repository, RepositoryCollection, RepositoryGroup, RepositoryCreation, Namespace, NamespaceCollection } from "./Repositories";
export { RepositoryType, RepositoryTypeCollection } from "./RepositoryTypes";
export { Branch, BranchRequest } from "./Branches";

View File

@@ -234,6 +234,10 @@ export function getRepositoriesLink(state: object) {
return getLink(state, "repositories");
}
export function getNamespacesLink(state: object) {
return getLink(state, "namespaces");
}
export function getHgConfigLink(state: object) {
return getLink(state, "hgConfig");
}

View File

@@ -25,7 +25,7 @@ import React from "react";
import { connect } from "react-redux";
import { RouteComponentProps, withRouter } from "react-router-dom";
import { WithTranslation, withTranslation } from "react-i18next";
import { RepositoryCollection } from "@scm-manager/ui-types";
import { NamespaceCollection, RepositoryCollection } from "@scm-manager/ui-types";
import {
CreateButton,
LinkPaginator,
@@ -35,13 +35,16 @@ import {
PageActions,
urls
} from "@scm-manager/ui-components";
import { getRepositoriesLink } from "../../modules/indexResource";
import { getNamespacesLink, getRepositoriesLink } from "../../modules/indexResource";
import {
fetchReposByPage,
getFetchReposFailure,
getRepositoryCollection,
isAbleToCreateRepos,
isFetchReposPending
isFetchReposPending,
isFetchNamespacesPending,
getNamespaceCollection,
fetchNamespaces
} from "../modules/repos";
import RepositoryList from "../components/list";
@@ -51,30 +54,52 @@ type Props = WithTranslation &
error: Error;
showCreateButton: boolean;
collection: RepositoryCollection;
namespaces: NamespaceCollection;
page: number;
namespace: string;
reposLink: string;
namespacesLink: string;
// dispatched functions
fetchReposByPage: (link: string, page: number, namespace?: string, filter?: string) => void;
fetchNamespaces: (link: string) => void;
};
class Overview extends React.Component<Props> {
componentDidMount() {
const { fetchReposByPage, reposLink, namespace, page, location } = this.props;
fetchReposByPage(reposLink, page, namespace, urls.getQueryStringFromLocation(location));
const { fetchReposByPage, fetchNamespaces, namespacesLink, namespace, page, location } = this.props;
fetchNamespaces(namespacesLink);
const link = this.getReposLink();
if (link) {
fetchReposByPage(link, page, namespace, urls.getQueryStringFromLocation(location));
}
}
componentDidUpdate = (prevProps: Props) => {
const { loading, collection, namespace, page, reposLink, location, fetchReposByPage } = this.props;
if (collection && page && !loading) {
const { loading, collection, namespaces, namespace, page, location, fetchReposByPage } = this.props;
if (!collection && namespace && prevProps.namespaces !== namespaces) {
const link = this.getReposLink();
fetchReposByPage(link, page, namespace, urls.getQueryStringFromLocation(location));
} else if (collection && page && !loading) {
const statePage: number = collection.page + 1;
if (page !== statePage || prevProps.location.search !== location.search) {
fetchReposByPage(reposLink, page, namespace, urls.getQueryStringFromLocation(location));
const link = this.getReposLink();
if (link) {
fetchReposByPage(link, page, namespace, urls.getQueryStringFromLocation(location));
}
}
}
};
getReposLink = () => {
const { namespace, namespaces, reposLink } = this.props;
if (namespace) {
return namespaces?.find(n => n.namespace === namespace)?._links?.repositories?.href;
} else {
return reposLink;
}
};
render() {
const { error, loading, showCreateButton, namespace, t } = this.props;
@@ -134,26 +159,33 @@ class Overview extends React.Component<Props> {
const mapStateToProps = (state: any, ownProps: Props) => {
const { match } = ownProps;
const collection = getRepositoryCollection(state);
const loading = isFetchReposPending(state);
const namespaces = getNamespaceCollection(state);
const loading = isFetchReposPending(state) || isFetchNamespacesPending(state);
const error = getFetchReposFailure(state);
const { namespace, page } = urls.getNamespaceAndPageFromMatch(match);
const showCreateButton = isAbleToCreateRepos(state);
const reposLink = getRepositoriesLink(state);
const namespacesLink = getNamespacesLink(state);
return {
collection,
namespaces,
loading,
error,
page,
namespace,
showCreateButton,
reposLink
reposLink,
namespacesLink
};
};
const mapDispatchToProps = (dispatch: any) => {
return {
fetchReposByPage: (link: string, page: number, namespace?: string, filter?: string) => {
dispatch(fetchReposByPage(link, page, namespace, filter));
fetchReposByPage: (link: string, page: number, filter?: string) => {
dispatch(fetchReposByPage(link, page, filter));
},
fetchNamespaces: (link: string) => {
dispatch(fetchNamespaces(link));
}
};
};

View File

@@ -24,7 +24,7 @@
import { apiClient } from "@scm-manager/ui-components";
import * as types from "../../modules/types";
import { Action, Repository, RepositoryCollection, RepositoryCreation } from "@scm-manager/ui-types";
import { Action, Repository, RepositoryCollection, RepositoryCreation, NamespaceCollection } from "@scm-manager/ui-types";
import { isPending } from "../../modules/pending";
import { getFailure } from "../../modules/failure";
@@ -33,6 +33,11 @@ export const FETCH_REPOS_PENDING = `${FETCH_REPOS}_${types.PENDING_SUFFIX}`;
export const FETCH_REPOS_SUCCESS = `${FETCH_REPOS}_${types.SUCCESS_SUFFIX}`;
export const FETCH_REPOS_FAILURE = `${FETCH_REPOS}_${types.FAILURE_SUFFIX}`;
export const FETCH_NAMESPACES = "scm/repos/FETCH_NAMESPACES";
export const FETCH_NAMESPACES_PENDING = `${FETCH_NAMESPACES}_${types.PENDING_SUFFIX}`;
export const FETCH_NAMESPACES_SUCCESS = `${FETCH_NAMESPACES}_${types.SUCCESS_SUFFIX}`;
export const FETCH_NAMESPACES_FAILURE = `${FETCH_NAMESPACES}_${types.FAILURE_SUFFIX}`;
export const FETCH_REPO = "scm/repos/FETCH_REPO";
export const FETCH_REPO_PENDING = `${FETCH_REPO}_${types.PENDING_SUFFIX}`;
export const FETCH_REPO_SUCCESS = `${FETCH_REPO}_${types.SUCCESS_SUFFIX}`;
@@ -67,11 +72,10 @@ export function fetchRepos(link: string) {
return fetchReposByLink(link);
}
export function fetchReposByPage(link: string, page: number, namespace?: string, filter?: string) {
const namespacePath = namespace ? `${namespace}/` : "";
const linkWithPage = `${link}${namespacePath}?page=${page - 1}`;
export function fetchReposByPage(link: string, page: number, filter?: string) {
const linkWithPage = `${link}?page=${page - 1}`;
if (filter) {
return fetchReposByLink(`${linkWithPage}}&q=${decodeURIComponent(filter)}`);
return fetchReposByLink(`${linkWithPage}&q=${decodeURIComponent(filter)}`);
}
return fetchReposByLink(linkWithPage);
}
@@ -125,6 +129,42 @@ export function fetchReposFailure(err: Error): Action {
};
}
// fetch namespaces
export function fetchNamespaces(link: string) {
return function(dispatch: any) {
dispatch(fetchNamespacesPending());
return apiClient
.get(link)
.then(response => response.json())
.then(namespaces => {
dispatch(fetchNamespacesSuccess(namespaces));
})
.catch(err => {
dispatch(fetchNamespacesFailure(err));
});
};
}
export function fetchNamespacesPending(): Action {
return {
type: FETCH_NAMESPACES_PENDING
};
}
export function fetchNamespacesSuccess(namespaces: NamespaceCollection): Action {
return {
type: FETCH_NAMESPACES_SUCCESS,
payload: namespaces
};
}
export function fetchNamespacesFailure(err: Error): Action {
return {
type: FETCH_NAMESPACES_FAILURE,
payload: err
};
}
// fetch repo
export function fetchRepoByLink(repo: Repository) {
return fetchRepo(repo._links.self.href, repo.namespace, repo.name);
@@ -348,7 +388,7 @@ function createIdentifier(repository: Repository) {
return repository.namespace + "/" + repository.name;
}
function normalizeByNamespaceAndName(repositoryCollection: RepositoryCollection) {
function normalizeByNamespaceAndName(state: object, repositoryCollection: RepositoryCollection) {
const names = [];
const byNames = {};
for (const repository of repositoryCollection._embedded.repositories) {
@@ -357,6 +397,7 @@ function normalizeByNamespaceAndName(repositoryCollection: RepositoryCollection)
byNames[identifier] = repository;
}
return {
...state,
list: {
...repositoryCollection,
_embedded: {
@@ -378,6 +419,13 @@ const reducerByNames = (state: object, repository: Repository) => {
};
};
const reducerForNamespaces = (state: object, namespaces: NamespaceCollection) => {
return {
...state,
namespaces: namespaces._embedded
};
};
export default function reducer(
state: object = {},
action: Action = {
@@ -390,7 +438,9 @@ export default function reducer(
switch (action.type) {
case FETCH_REPOS_SUCCESS:
return normalizeByNamespaceAndName(action.payload);
return normalizeByNamespaceAndName(state, action.payload);
case FETCH_NAMESPACES_SUCCESS:
return reducerForNamespaces(state, action.payload);
case FETCH_REPO_SUCCESS:
return reducerByNames(state, action.payload);
default:
@@ -408,6 +458,7 @@ export function getRepositoryCollection(state: object) {
}
return {
...state.repos.list,
...state.repos.namespaces,
_embedded: {
repositories
}
@@ -415,6 +466,10 @@ export function getRepositoryCollection(state: object) {
}
}
export function getNamespaceCollection(state: object) {
return state.repos.namespaces?.namespaces;
}
export function isFetchReposPending(state: object) {
return isPending(state, FETCH_REPOS);
}
@@ -429,6 +484,20 @@ export function getRepository(state: object, namespace: string, name: string) {
}
}
export function isFetchNamespacesPending(state: object) {
return isPending(state, FETCH_NAMESPACES);
}
export function getFetchNamespacesFailure(state: object) {
return getFailure(state, FETCH_NAMESPACES);
}
export function getNamespace(state: object, namespace: string) {
if (state.namespaces) {
return state.namespaces[namespace];
}
}
export function isFetchRepoPending(state: object, namespace: string, name: string) {
return isPending(state, FETCH_REPO, namespace + "/" + name);
}

View File

@@ -104,6 +104,7 @@ public class IndexDtoGenerator extends HalAppenderMapper {
builder.single(link("config", resourceLinks.config().self()));
}
builder.single(link("repositories", resourceLinks.repositoryCollection().self()));
builder.single(link("namespaces", resourceLinks.namespaceCollection().self()));
if (PermissionPermissions.list().isPermitted()) {
builder.single(link("permissions", resourceLinks.permissions().self()));
}

View File

@@ -26,6 +26,7 @@ package sonia.scm.api.v2.resources;
import javax.inject.Inject;
import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo;
class NamespaceToNamespaceDtoMapper {
@@ -38,6 +39,12 @@ class NamespaceToNamespaceDtoMapper {
}
NamespaceDto map(String namespace) {
return new NamespaceDto(namespace, linkingTo().self(links.namespace().self(namespace)).build());
return new NamespaceDto(
namespace,
linkingTo()
.self(links.namespace().self(namespace))
.single(link("repositories", links.repositoryCollection().forNamespace(namespace)))
.build()
);
}
}

View File

@@ -911,5 +911,9 @@ class ResourceLinks {
String self(String namespace) {
return namespaceLinkBuilder.method("getNamespaceResource").parameters().method("get").parameters(namespace).href();
}
String repositories(String namespace) {
return namespaceLinkBuilder.method("getNamespaceResource").parameters().method("get").parameters(namespace).href();
}
}
}