implemented navigation within source browser

This commit is contained in:
Sebastian Sdorra
2018-09-28 11:31:38 +02:00
parent 9606a8af29
commit d1a9a1c63a
9 changed files with 233 additions and 59 deletions

View File

@@ -15,53 +15,78 @@ export const FETCH_SOURCES_PENDING = `${FETCH_SOURCES}_${types.PENDING_SUFFIX}`;
export const FETCH_SOURCES_SUCCESS = `${FETCH_SOURCES}_${types.SUCCESS_SUFFIX}`;
export const FETCH_SOURCES_FAILURE = `${FETCH_SOURCES}_${types.FAILURE_SUFFIX}`;
export function fetchSources(repository: Repository) {
export function fetchSources(
repository: Repository,
revision: string,
path: string
) {
return function(dispatch: any) {
dispatch(fetchSourcesPending(repository));
dispatch(fetchSourcesPending(repository, revision, path));
return apiClient
.get(repository._links.sources.href)
.get(createUrl(repository, revision, path))
.then(response => response.json())
.then(sources => {
dispatch(fetchSourcesSuccess(repository, sources));
dispatch(fetchSourcesSuccess(repository, revision, path, sources));
})
.catch(err => {
const error = new Error(`failed to fetch sources: ${err.message}`);
dispatch(fetchSourcesFailure(repository, error));
dispatch(fetchSourcesFailure(repository, revision, path, error));
});
};
}
export function fetchSourcesPending(repository: Repository): Action {
function createUrl(repository: Repository, revision: string, path: string) {
const base = repository._links.sources.href;
if (!revision && !path) {
return base;
}
// TODO handle trailing slash
const pathDefined = path ? path : "";
return `${base}${revision}/${pathDefined}`;
}
export function fetchSourcesPending(
repository: Repository,
revision: string,
path: string
): Action {
return {
type: FETCH_SOURCES_PENDING,
itemId: createItemId(repository)
itemId: createItemId(repository, revision, path)
};
}
export function fetchSourcesSuccess(
repository: Repository,
revision: string,
path: string,
sources: SourcesCollection
) {
return {
type: FETCH_SOURCES_SUCCESS,
payload: sources,
itemId: createItemId(repository)
itemId: createItemId(repository, revision, path)
};
}
export function fetchSourcesFailure(
repository: Repository,
revision: string,
path: string,
error: Error
): Action {
return {
type: FETCH_SOURCES_FAILURE,
payload: error,
itemId: createItemId(repository)
itemId: createItemId(repository, revision, path)
};
}
function createItemId(repository: Repository) {
return `${repository.namespace}/${repository.name}`;
function createItemId(repository: Repository, revision: string, path: string) {
const revPart = revision ? revision : "_";
const pathPart = path ? path : "";
return `${repository.namespace}/${repository.name}/${revPart}/${pathPart}`;
}
// reducer
@@ -83,24 +108,38 @@ export default function reducer(
export function getSources(
state: any,
repository: Repository
repository: Repository,
revision: string,
path: string
): ?SourcesCollection {
if (state.sources) {
return state.sources[createItemId(repository)];
return state.sources[createItemId(repository, revision, path)];
}
return null;
}
export function isFetchSourcesPending(
state: any,
repository: Repository
repository: Repository,
revision: string,
path: string
): boolean {
return isPending(state, FETCH_SOURCES, createItemId(repository));
return isPending(
state,
FETCH_SOURCES,
createItemId(repository, revision, path)
);
}
export function getFetchSourcesFailure(
state: any,
repository: Repository
repository: Repository,
revision: string,
path: string
): ?Error {
return getFailure(state, FETCH_SOURCES, createItemId(repository));
return getFailure(
state,
FETCH_SOURCES,
createItemId(repository, revision, path)
);
}

View File

@@ -18,7 +18,7 @@ import {
} from "./sources";
const sourcesUrl =
"http://localhost:8081/scm/rest/api/v2/repositories/scm/core/sources";
"http://localhost:8081/scm/rest/api/v2/repositories/scm/core/sources/";
const repository: Repository = {
name: "core",
@@ -91,10 +91,10 @@ describe("sources fetch", () => {
fetchMock.getOnce(sourcesUrl, collection);
const expectedActions = [
{ type: FETCH_SOURCES_PENDING, itemId: "scm/core" },
{ type: FETCH_SOURCES_PENDING, itemId: "scm/core/_/" },
{
type: FETCH_SOURCES_SUCCESS,
itemId: "scm/core",
itemId: "scm/core/_/",
payload: collection
}
];
@@ -105,6 +105,24 @@ describe("sources fetch", () => {
});
});
it("should fetch the sources of the repository with the given revision and path", () => {
fetchMock.getOnce(sourcesUrl + "abc/src", collection);
const expectedActions = [
{ type: FETCH_SOURCES_PENDING, itemId: "scm/core/abc/src" },
{
type: FETCH_SOURCES_SUCCESS,
itemId: "scm/core/abc/src",
payload: collection
}
];
const store = mockStore({});
return store.dispatch(fetchSources(repository, "abc", "src")).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
it("should dispatch FETCH_SOURCES_FAILURE on server error", () => {
fetchMock.getOnce(sourcesUrl, {
status: 500
@@ -115,7 +133,7 @@ describe("sources fetch", () => {
const actions = store.getActions();
expect(actions[0].type).toBe(FETCH_SOURCES_PENDING);
expect(actions[1].type).toBe(FETCH_SOURCES_FAILURE);
expect(actions[1].itemId).toBe("scm/core");
expect(actions[1].itemId).toBe("scm/core/_/");
expect(actions[1].payload).toBeDefined();
});
});
@@ -127,13 +145,25 @@ describe("reducer tests", () => {
expect(reducer(state)).toBe(state);
});
it("should store the collection", () => {
it("should store the collection, without revision and path", () => {
const expectedState = {
"scm/core": collection
"scm/core/_/": collection
};
expect(reducer({}, fetchSourcesSuccess(repository, collection))).toEqual(
expectedState
);
expect(
reducer({}, fetchSourcesSuccess(repository, null, null, collection))
).toEqual(expectedState);
});
it("should store the collection, with revision and path", () => {
const expectedState = {
"scm/core/abc/src/main": collection
};
expect(
reducer(
{},
fetchSourcesSuccess(repository, "abc", "src/main", collection)
)
).toEqual(expectedState);
});
});
@@ -142,19 +172,28 @@ describe("selector tests", () => {
expect(getSources({}, repository)).toBeFalsy();
});
it("should return the source collection", () => {
it("should return the source collection without revision and path", () => {
const state = {
sources: {
"scm/core": collection
"scm/core/_/": collection
}
};
expect(getSources(state, repository)).toBe(collection);
});
it("should return the source collection without revision and path", () => {
const state = {
sources: {
"scm/core/abc/src/main": collection
}
};
expect(getSources(state, repository, "abc", "src/main")).toBe(collection);
});
it("should return true, when fetch sources is pending", () => {
const state = {
pending: {
[FETCH_SOURCES + "/scm/core"]: true
[FETCH_SOURCES + "/scm/core/_/"]: true
}
};
expect(isFetchSourcesPending(state, repository)).toEqual(true);
@@ -169,7 +208,7 @@ describe("selector tests", () => {
it("should return error when fetch sources did fail", () => {
const state = {
failure: {
[FETCH_SOURCES + "/scm/core"]: error
[FETCH_SOURCES + "/scm/core/_/"]: error
}
};
expect(getFetchSourcesFailure(state, repository)).toEqual(error);