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 }; contextEntries: { [key: string]: any };
}; };
export type Namespace = {
namespace: string;
_links: Links;
};
export type RepositoryCollection = PagedCollection & { export type RepositoryCollection = PagedCollection & {
_embedded: { _embedded: {
repositories: Repository[] | string[]; repositories: Repository[] | string[];
}; };
}; };
export type NamespaceCollection = Namespace[];
export type RepositoryGroup = { export type RepositoryGroup = {
name: string; name: string;
repositories: Repository[]; repositories: Repository[];

View File

@@ -29,7 +29,7 @@ export { Me } from "./Me";
export { DisplayedUser, User } from "./User"; export { DisplayedUser, User } from "./User";
export { Group, Member } from "./Group"; 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 { RepositoryType, RepositoryTypeCollection } from "./RepositoryTypes";
export { Branch, BranchRequest } from "./Branches"; export { Branch, BranchRequest } from "./Branches";

View File

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

View File

@@ -25,7 +25,7 @@ import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { RouteComponentProps, withRouter } from "react-router-dom"; import { RouteComponentProps, withRouter } from "react-router-dom";
import { WithTranslation, withTranslation } from "react-i18next"; import { WithTranslation, withTranslation } from "react-i18next";
import { RepositoryCollection } from "@scm-manager/ui-types"; import { NamespaceCollection, RepositoryCollection } from "@scm-manager/ui-types";
import { import {
CreateButton, CreateButton,
LinkPaginator, LinkPaginator,
@@ -35,13 +35,16 @@ import {
PageActions, PageActions,
urls urls
} from "@scm-manager/ui-components"; } from "@scm-manager/ui-components";
import { getRepositoriesLink } from "../../modules/indexResource"; import { getNamespacesLink, getRepositoriesLink } from "../../modules/indexResource";
import { import {
fetchReposByPage, fetchReposByPage,
getFetchReposFailure, getFetchReposFailure,
getRepositoryCollection, getRepositoryCollection,
isAbleToCreateRepos, isAbleToCreateRepos,
isFetchReposPending isFetchReposPending,
isFetchNamespacesPending,
getNamespaceCollection,
fetchNamespaces
} from "../modules/repos"; } from "../modules/repos";
import RepositoryList from "../components/list"; import RepositoryList from "../components/list";
@@ -51,30 +54,52 @@ type Props = WithTranslation &
error: Error; error: Error;
showCreateButton: boolean; showCreateButton: boolean;
collection: RepositoryCollection; collection: RepositoryCollection;
namespaces: NamespaceCollection;
page: number; page: number;
namespace: string; namespace: string;
reposLink: string; reposLink: string;
namespacesLink: string;
// dispatched functions // dispatched functions
fetchReposByPage: (link: string, page: number, namespace?: string, filter?: string) => void; fetchReposByPage: (link: string, page: number, namespace?: string, filter?: string) => void;
fetchNamespaces: (link: string) => void;
}; };
class Overview extends React.Component<Props> { class Overview extends React.Component<Props> {
componentDidMount() { componentDidMount() {
const { fetchReposByPage, reposLink, namespace, page, location } = this.props; const { fetchReposByPage, fetchNamespaces, namespacesLink, namespace, page, location } = this.props;
fetchReposByPage(reposLink, page, namespace, urls.getQueryStringFromLocation(location)); fetchNamespaces(namespacesLink);
const link = this.getReposLink();
if (link) {
fetchReposByPage(link, page, namespace, urls.getQueryStringFromLocation(location));
}
} }
componentDidUpdate = (prevProps: Props) => { componentDidUpdate = (prevProps: Props) => {
const { loading, collection, namespace, page, reposLink, location, fetchReposByPage } = this.props; const { loading, collection, namespaces, namespace, page, location, fetchReposByPage } = this.props;
if (collection && page && !loading) { 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; const statePage: number = collection.page + 1;
if (page !== statePage || prevProps.location.search !== location.search) { 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() { render() {
const { error, loading, showCreateButton, namespace, t } = this.props; 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 mapStateToProps = (state: any, ownProps: Props) => {
const { match } = ownProps; const { match } = ownProps;
const collection = getRepositoryCollection(state); const collection = getRepositoryCollection(state);
const loading = isFetchReposPending(state); const namespaces = getNamespaceCollection(state);
const loading = isFetchReposPending(state) || isFetchNamespacesPending(state);
const error = getFetchReposFailure(state); const error = getFetchReposFailure(state);
const { namespace, page } = urls.getNamespaceAndPageFromMatch(match); const { namespace, page } = urls.getNamespaceAndPageFromMatch(match);
const showCreateButton = isAbleToCreateRepos(state); const showCreateButton = isAbleToCreateRepos(state);
const reposLink = getRepositoriesLink(state); const reposLink = getRepositoriesLink(state);
const namespacesLink = getNamespacesLink(state);
return { return {
collection, collection,
namespaces,
loading, loading,
error, error,
page, page,
namespace, namespace,
showCreateButton, showCreateButton,
reposLink reposLink,
namespacesLink
}; };
}; };
const mapDispatchToProps = (dispatch: any) => { const mapDispatchToProps = (dispatch: any) => {
return { return {
fetchReposByPage: (link: string, page: number, namespace?: string, filter?: string) => { fetchReposByPage: (link: string, page: number, filter?: string) => {
dispatch(fetchReposByPage(link, page, namespace, filter)); 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 { apiClient } from "@scm-manager/ui-components";
import * as types from "../../modules/types"; 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 { isPending } from "../../modules/pending";
import { getFailure } from "../../modules/failure"; 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_SUCCESS = `${FETCH_REPOS}_${types.SUCCESS_SUFFIX}`;
export const FETCH_REPOS_FAILURE = `${FETCH_REPOS}_${types.FAILURE_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 = "scm/repos/FETCH_REPO";
export const FETCH_REPO_PENDING = `${FETCH_REPO}_${types.PENDING_SUFFIX}`; export const FETCH_REPO_PENDING = `${FETCH_REPO}_${types.PENDING_SUFFIX}`;
export const FETCH_REPO_SUCCESS = `${FETCH_REPO}_${types.SUCCESS_SUFFIX}`; export const FETCH_REPO_SUCCESS = `${FETCH_REPO}_${types.SUCCESS_SUFFIX}`;
@@ -67,11 +72,10 @@ export function fetchRepos(link: string) {
return fetchReposByLink(link); return fetchReposByLink(link);
} }
export function fetchReposByPage(link: string, page: number, namespace?: string, filter?: string) { export function fetchReposByPage(link: string, page: number, filter?: string) {
const namespacePath = namespace ? `${namespace}/` : ""; const linkWithPage = `${link}?page=${page - 1}`;
const linkWithPage = `${link}${namespacePath}?page=${page - 1}`;
if (filter) { if (filter) {
return fetchReposByLink(`${linkWithPage}}&q=${decodeURIComponent(filter)}`); return fetchReposByLink(`${linkWithPage}&q=${decodeURIComponent(filter)}`);
} }
return fetchReposByLink(linkWithPage); 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 // fetch repo
export function fetchRepoByLink(repo: Repository) { export function fetchRepoByLink(repo: Repository) {
return fetchRepo(repo._links.self.href, repo.namespace, repo.name); return fetchRepo(repo._links.self.href, repo.namespace, repo.name);
@@ -348,7 +388,7 @@ function createIdentifier(repository: Repository) {
return repository.namespace + "/" + repository.name; return repository.namespace + "/" + repository.name;
} }
function normalizeByNamespaceAndName(repositoryCollection: RepositoryCollection) { function normalizeByNamespaceAndName(state: object, repositoryCollection: RepositoryCollection) {
const names = []; const names = [];
const byNames = {}; const byNames = {};
for (const repository of repositoryCollection._embedded.repositories) { for (const repository of repositoryCollection._embedded.repositories) {
@@ -357,6 +397,7 @@ function normalizeByNamespaceAndName(repositoryCollection: RepositoryCollection)
byNames[identifier] = repository; byNames[identifier] = repository;
} }
return { return {
...state,
list: { list: {
...repositoryCollection, ...repositoryCollection,
_embedded: { _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( export default function reducer(
state: object = {}, state: object = {},
action: Action = { action: Action = {
@@ -390,7 +438,9 @@ export default function reducer(
switch (action.type) { switch (action.type) {
case FETCH_REPOS_SUCCESS: 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: case FETCH_REPO_SUCCESS:
return reducerByNames(state, action.payload); return reducerByNames(state, action.payload);
default: default:
@@ -408,6 +458,7 @@ export function getRepositoryCollection(state: object) {
} }
return { return {
...state.repos.list, ...state.repos.list,
...state.repos.namespaces,
_embedded: { _embedded: {
repositories 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) { export function isFetchReposPending(state: object) {
return isPending(state, FETCH_REPOS); 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) { export function isFetchRepoPending(state: object, namespace: string, name: string) {
return isPending(state, FETCH_REPO, namespace + "/" + name); 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("config", resourceLinks.config().self()));
} }
builder.single(link("repositories", resourceLinks.repositoryCollection().self())); builder.single(link("repositories", resourceLinks.repositoryCollection().self()));
builder.single(link("namespaces", resourceLinks.namespaceCollection().self()));
if (PermissionPermissions.list().isPermitted()) { if (PermissionPermissions.list().isPermitted()) {
builder.single(link("permissions", resourceLinks.permissions().self())); builder.single(link("permissions", resourceLinks.permissions().self()));
} }

View File

@@ -26,6 +26,7 @@ package sonia.scm.api.v2.resources;
import javax.inject.Inject; import javax.inject.Inject;
import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo; import static de.otto.edison.hal.Links.linkingTo;
class NamespaceToNamespaceDtoMapper { class NamespaceToNamespaceDtoMapper {
@@ -38,6 +39,12 @@ class NamespaceToNamespaceDtoMapper {
} }
NamespaceDto map(String namespace) { 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) { String self(String namespace) {
return namespaceLinkBuilder.method("getNamespaceResource").parameters().method("get").parameters(namespace).href(); return namespaceLinkBuilder.method("getNamespaceResource").parameters().method("get").parameters(namespace).href();
} }
String repositories(String namespace) {
return namespaceLinkBuilder.method("getNamespaceResource").parameters().method("get").parameters(namespace).href();
}
} }
} }