@scm-manager/ui-api 2.20.1-20210621-094534 → 2.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/base.ts +6 -21
- package/src/branches.test.ts +19 -19
- package/src/branches.ts +1 -1
- package/src/changesets.ts +1 -1
- package/src/diff.ts +8 -14
- package/src/import.ts +5 -87
- package/src/index.ts +0 -7
- package/src/links.test.ts +0 -36
- package/src/links.ts +2 -20
- package/src/permissions.ts +16 -62
- package/src/plugins.test.ts +31 -34
- package/src/repositories.ts +34 -63
- package/src/users.ts +21 -63
- package/src/annotations.ts +0 -40
- package/src/apiKeys.ts +0 -82
- package/src/contentType.ts +0 -49
- package/src/fileContent.ts +0 -33
- package/src/history.ts +0 -62
- package/src/publicKeys.ts +0 -83
- package/src/search.ts +0 -80
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scm-manager/ui-api",
|
|
3
|
-
"version": "2.20.1
|
|
3
|
+
"version": "2.20.1",
|
|
4
4
|
"description": "React hook api for the SCM-Manager backend",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"files": [
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"react-test-renderer": "^17.0.1"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@scm-manager/ui-types": "^2.20.1
|
|
28
|
+
"@scm-manager/ui-types": "^2.20.1",
|
|
29
29
|
"fetch-mock-jest": "^1.5.1",
|
|
30
30
|
"gitdiff-parser": "^0.1.2",
|
|
31
31
|
"query-string": "5",
|
package/src/base.ts
CHANGED
|
@@ -22,24 +22,22 @@
|
|
|
22
22
|
* SOFTWARE.
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
import {
|
|
25
|
+
import { IndexResources, Link } from "@scm-manager/ui-types";
|
|
26
26
|
import { useQuery } from "react-query";
|
|
27
27
|
import { apiClient } from "./apiclient";
|
|
28
28
|
import { useLegacyContext } from "./LegacyContext";
|
|
29
29
|
import { MissingLinkError, UnauthorizedError } from "./errors";
|
|
30
|
-
import { requiredLink } from "./links";
|
|
31
30
|
|
|
32
31
|
export type ApiResult<T> = {
|
|
33
32
|
isLoading: boolean;
|
|
34
33
|
error: Error | null;
|
|
35
34
|
data?: T;
|
|
36
35
|
};
|
|
37
|
-
export type DeleteFunction<T> = (entity: T) => void;
|
|
38
36
|
|
|
39
37
|
export const useIndex = (): ApiResult<IndexResources> => {
|
|
40
38
|
const legacy = useLegacyContext();
|
|
41
|
-
return useQuery<IndexResources, Error>("index", () => apiClient.get("/").then(
|
|
42
|
-
onSuccess:
|
|
39
|
+
return useQuery<IndexResources, Error>("index", () => apiClient.get("/").then(response => response.json()), {
|
|
40
|
+
onSuccess: index => {
|
|
43
41
|
// ensure legacy code is notified
|
|
44
42
|
if (legacy.onIndexFetched) {
|
|
45
43
|
legacy.onIndexFetched(index);
|
|
@@ -51,7 +49,7 @@ export const useIndex = (): ApiResult<IndexResources> => {
|
|
|
51
49
|
// This only happens once because the error response automatically invalidates the cookie.
|
|
52
50
|
// In this event, we have to try the request once again.
|
|
53
51
|
return error instanceof UnauthorizedError && failureCount === 0;
|
|
54
|
-
}
|
|
52
|
+
}
|
|
55
53
|
});
|
|
56
54
|
};
|
|
57
55
|
|
|
@@ -96,20 +94,7 @@ export const useVersion = (): string => {
|
|
|
96
94
|
|
|
97
95
|
export const useIndexJsonResource = <T>(name: string): ApiResult<T> => {
|
|
98
96
|
const link = useIndexLink(name);
|
|
99
|
-
return useQuery<T, Error>(name, () => apiClient.get(link!).then(
|
|
100
|
-
enabled: !!link
|
|
97
|
+
return useQuery<T, Error>(name, () => apiClient.get(link!).then(response => response.json()), {
|
|
98
|
+
enabled: !!link
|
|
101
99
|
});
|
|
102
100
|
};
|
|
103
|
-
|
|
104
|
-
export const useJsonResource = <T>(entity: HalRepresentation, name: string, key: string[]): ApiResult<T> =>
|
|
105
|
-
useQuery<T, Error>(key, () => apiClient.get(requiredLink(entity, name)).then((response) => response.json()));
|
|
106
|
-
|
|
107
|
-
export function fetchResourceFromLocationHeader(response: Response) {
|
|
108
|
-
const location = response.headers.get("Location");
|
|
109
|
-
if (!location) {
|
|
110
|
-
throw new Error("Server does not return required Location header");
|
|
111
|
-
}
|
|
112
|
-
return apiClient.get(location);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export const getResponseJson = (response: Response) => response.json();
|
package/src/branches.test.ts
CHANGED
|
@@ -36,9 +36,9 @@ describe("Test branches hooks", () => {
|
|
|
36
36
|
type: "hg",
|
|
37
37
|
_links: {
|
|
38
38
|
branches: {
|
|
39
|
-
href: "/hog/branches"
|
|
40
|
-
}
|
|
41
|
-
}
|
|
39
|
+
href: "/hog/branches"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
const develop: Branch = {
|
|
@@ -46,16 +46,16 @@ describe("Test branches hooks", () => {
|
|
|
46
46
|
revision: "42",
|
|
47
47
|
_links: {
|
|
48
48
|
delete: {
|
|
49
|
-
href: "/hog/branches/develop"
|
|
50
|
-
}
|
|
51
|
-
}
|
|
49
|
+
href: "/hog/branches/develop"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
const branches: BranchCollection = {
|
|
55
55
|
_embedded: {
|
|
56
|
-
branches: [develop]
|
|
56
|
+
branches: [develop]
|
|
57
57
|
},
|
|
58
|
-
_links: {}
|
|
58
|
+
_links: {}
|
|
59
59
|
};
|
|
60
60
|
|
|
61
61
|
const queryClient = createInfiniteCachingClient();
|
|
@@ -73,7 +73,7 @@ describe("Test branches hooks", () => {
|
|
|
73
73
|
fetchMock.getOnce("/api/v2/hog/branches", branches);
|
|
74
74
|
|
|
75
75
|
const { result, waitFor } = renderHook(() => useBranches(repository), {
|
|
76
|
-
wrapper: createWrapper(undefined, queryClient)
|
|
76
|
+
wrapper: createWrapper(undefined, queryClient)
|
|
77
77
|
});
|
|
78
78
|
await waitFor(() => {
|
|
79
79
|
return !!result.current.data;
|
|
@@ -94,7 +94,7 @@ describe("Test branches hooks", () => {
|
|
|
94
94
|
"repository",
|
|
95
95
|
"hitchhiker",
|
|
96
96
|
"heart-of-gold",
|
|
97
|
-
"branches"
|
|
97
|
+
"branches"
|
|
98
98
|
]);
|
|
99
99
|
expect(data).toEqual(branches);
|
|
100
100
|
});
|
|
@@ -105,7 +105,7 @@ describe("Test branches hooks", () => {
|
|
|
105
105
|
fetchMock.getOnce("/api/v2/hog/branches/develop", develop);
|
|
106
106
|
|
|
107
107
|
const { result, waitFor } = renderHook(() => useBranch(repository, "develop"), {
|
|
108
|
-
wrapper: createWrapper(undefined, queryClient)
|
|
108
|
+
wrapper: createWrapper(undefined, queryClient)
|
|
109
109
|
});
|
|
110
110
|
|
|
111
111
|
expect(result.error).toBeUndefined();
|
|
@@ -128,14 +128,14 @@ describe("Test branches hooks", () => {
|
|
|
128
128
|
fetchMock.postOnce("/api/v2/hog/branches", {
|
|
129
129
|
status: 201,
|
|
130
130
|
headers: {
|
|
131
|
-
Location: "/hog/branches/develop"
|
|
132
|
-
}
|
|
131
|
+
Location: "/hog/branches/develop"
|
|
132
|
+
}
|
|
133
133
|
});
|
|
134
134
|
|
|
135
135
|
fetchMock.getOnce("/api/v2/hog/branches/develop", develop);
|
|
136
136
|
|
|
137
137
|
const { result, waitForNextUpdate } = renderHook(() => useCreateBranch(repository), {
|
|
138
|
-
wrapper: createWrapper(undefined, queryClient)
|
|
138
|
+
wrapper: createWrapper(undefined, queryClient)
|
|
139
139
|
});
|
|
140
140
|
|
|
141
141
|
await act(() => {
|
|
@@ -160,7 +160,7 @@ describe("Test branches hooks", () => {
|
|
|
160
160
|
"hitchhiker",
|
|
161
161
|
"heart-of-gold",
|
|
162
162
|
"branch",
|
|
163
|
-
"develop"
|
|
163
|
+
"develop"
|
|
164
164
|
]);
|
|
165
165
|
expect(branch).toEqual(develop);
|
|
166
166
|
});
|
|
@@ -177,11 +177,11 @@ describe("Test branches hooks", () => {
|
|
|
177
177
|
describe("useDeleteBranch tests", () => {
|
|
178
178
|
const deleteBranch = async () => {
|
|
179
179
|
fetchMock.deleteOnce("/api/v2/hog/branches/develop", {
|
|
180
|
-
status: 204
|
|
180
|
+
status: 204
|
|
181
181
|
});
|
|
182
182
|
|
|
183
183
|
const { result, waitForNextUpdate } = renderHook(() => useDeleteBranch(repository), {
|
|
184
|
-
wrapper: createWrapper(undefined, queryClient)
|
|
184
|
+
wrapper: createWrapper(undefined, queryClient)
|
|
185
185
|
});
|
|
186
186
|
|
|
187
187
|
await act(() => {
|
|
@@ -198,12 +198,12 @@ describe("Test branches hooks", () => {
|
|
|
198
198
|
expect(isDeleted).toBe(true);
|
|
199
199
|
});
|
|
200
200
|
|
|
201
|
-
it("should
|
|
201
|
+
it("should invalidate branch", async () => {
|
|
202
202
|
queryClient.setQueryData(["repository", "hitchhiker", "heart-of-gold", "branch", "develop"], develop);
|
|
203
203
|
await deleteBranch();
|
|
204
204
|
|
|
205
205
|
const queryState = queryClient.getQueryState(["repository", "hitchhiker", "heart-of-gold", "branch", "develop"]);
|
|
206
|
-
expect(queryState).
|
|
206
|
+
expect(queryState!.isInvalidated).toBe(true);
|
|
207
207
|
});
|
|
208
208
|
|
|
209
209
|
it("should invalidate cached branches list", async () => {
|
package/src/branches.ts
CHANGED
|
@@ -88,7 +88,7 @@ export const useDeleteBranch = (repository: Repository) => {
|
|
|
88
88
|
},
|
|
89
89
|
{
|
|
90
90
|
onSuccess: async (_, branch) => {
|
|
91
|
-
queryClient.
|
|
91
|
+
await queryClient.invalidateQueries(branchQueryKey(repository, branch));
|
|
92
92
|
await queryClient.invalidateQueries(repoQueryKey(repository, "branches"));
|
|
93
93
|
}
|
|
94
94
|
}
|
package/src/changesets.ts
CHANGED
|
@@ -34,7 +34,7 @@ type UseChangesetsRequest = {
|
|
|
34
34
|
page?: string | number;
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
const changesetQueryKey = (repository: NamespaceAndName, id: string) => {
|
|
38
38
|
return repoQueryKey(repository, "changeset", id);
|
|
39
39
|
};
|
|
40
40
|
|
package/src/diff.ts
CHANGED
|
@@ -30,14 +30,9 @@ import { Diff, Link } from "@scm-manager/ui-types";
|
|
|
30
30
|
|
|
31
31
|
type UseDiffOptions = {
|
|
32
32
|
limit?: number;
|
|
33
|
-
refetchOnWindowFocus?: boolean;
|
|
34
33
|
};
|
|
35
34
|
|
|
36
|
-
const
|
|
37
|
-
refetchOnWindowFocus: true,
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export const useDiff = (link: string, options: UseDiffOptions = defaultOptions) => {
|
|
35
|
+
export const useDiff = (link: string, options: UseDiffOptions = {}) => {
|
|
41
36
|
let initialLink = link;
|
|
42
37
|
if (options.limit) {
|
|
43
38
|
const separator = initialLink.includes("?") ? "&" : "?";
|
|
@@ -46,7 +41,7 @@ export const useDiff = (link: string, options: UseDiffOptions = defaultOptions)
|
|
|
46
41
|
const { isLoading, error, data, isFetchingNextPage, fetchNextPage } = useInfiniteQuery<Diff, Error, Diff>(
|
|
47
42
|
["link", link],
|
|
48
43
|
({ pageParam }) => {
|
|
49
|
-
return apiClient.get(pageParam || initialLink).then(
|
|
44
|
+
return apiClient.get(pageParam || initialLink).then(response => {
|
|
50
45
|
const contentType = response.headers.get("Content-Type");
|
|
51
46
|
if (contentType && contentType.toLowerCase() === "application/vnd.scmm-diffparsed+json;v=2") {
|
|
52
47
|
return response.json();
|
|
@@ -54,19 +49,18 @@ export const useDiff = (link: string, options: UseDiffOptions = defaultOptions)
|
|
|
54
49
|
return response
|
|
55
50
|
.text()
|
|
56
51
|
.then(parser.parse)
|
|
57
|
-
.then(
|
|
52
|
+
.then(parsedGit => {
|
|
58
53
|
return {
|
|
59
54
|
files: parsedGit,
|
|
60
55
|
partial: false,
|
|
61
|
-
_links: {}
|
|
56
|
+
_links: {}
|
|
62
57
|
};
|
|
63
58
|
});
|
|
64
59
|
}
|
|
65
60
|
});
|
|
66
61
|
},
|
|
67
62
|
{
|
|
68
|
-
getNextPageParam:
|
|
69
|
-
refetchOnWindowFocus: options.refetchOnWindowFocus,
|
|
63
|
+
getNextPageParam: lastPage => (lastPage._links.next as Link)?.href
|
|
70
64
|
}
|
|
71
65
|
);
|
|
72
66
|
|
|
@@ -77,7 +71,7 @@ export const useDiff = (link: string, options: UseDiffOptions = defaultOptions)
|
|
|
77
71
|
fetchNextPage: () => {
|
|
78
72
|
fetchNextPage();
|
|
79
73
|
},
|
|
80
|
-
data: merge(data?.pages)
|
|
74
|
+
data: merge(data?.pages)
|
|
81
75
|
};
|
|
82
76
|
};
|
|
83
77
|
|
|
@@ -85,9 +79,9 @@ const merge = (diffs?: Diff[]): Diff | undefined => {
|
|
|
85
79
|
if (!diffs || diffs.length === 0) {
|
|
86
80
|
return;
|
|
87
81
|
}
|
|
88
|
-
const joinedFiles = diffs.flatMap(
|
|
82
|
+
const joinedFiles = diffs.flatMap(diff => diff.files);
|
|
89
83
|
return {
|
|
90
84
|
...diffs[diffs.length - 1],
|
|
91
|
-
files: joinedFiles
|
|
85
|
+
files: joinedFiles
|
|
92
86
|
};
|
|
93
87
|
};
|
package/src/import.ts
CHANGED
|
@@ -22,93 +22,11 @@
|
|
|
22
22
|
* SOFTWARE.
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
import { ApiResult,
|
|
26
|
-
import {
|
|
25
|
+
import { ApiResult, useRequiredIndexLink } from "./base";
|
|
26
|
+
import { useQuery } from "react-query";
|
|
27
27
|
import { apiClient } from "./apiclient";
|
|
28
|
-
import { Repository, RepositoryCreation, RepositoryType, RepositoryUrlImport } from "@scm-manager/ui-types";
|
|
29
|
-
import { requiredLink } from "./links";
|
|
30
28
|
|
|
31
|
-
export const useImportLog = (logId: string): ApiResult<string> => {
|
|
29
|
+
export const useImportLog = (logId: string) : ApiResult<string> => {
|
|
32
30
|
const link = useRequiredIndexLink("importLog").replace("{logId}", logId);
|
|
33
|
-
return useQuery<string, Error>(["importLog", logId], () => apiClient.get(link).then(
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export const useImportRepositoryFromUrl = (repositoryType: RepositoryType) => {
|
|
37
|
-
const url = requiredLink(repositoryType, "import", "url");
|
|
38
|
-
const { isLoading, error, data, mutate } = useMutation<Repository, Error, RepositoryUrlImport>((repo) =>
|
|
39
|
-
apiClient
|
|
40
|
-
.post(url, repo, "application/vnd.scmm-repository+json;v=2")
|
|
41
|
-
.then(fetchResourceFromLocationHeader)
|
|
42
|
-
.then(getResponseJson)
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
return {
|
|
46
|
-
isLoading,
|
|
47
|
-
error,
|
|
48
|
-
importRepositoryFromUrl: (repository: RepositoryUrlImport) => mutate(repository),
|
|
49
|
-
importedRepository: data,
|
|
50
|
-
};
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const importRepository = (url: string, repository: RepositoryCreation, file: File, password?: string) => {
|
|
54
|
-
return apiClient
|
|
55
|
-
.postBinary(url, (formData) => {
|
|
56
|
-
formData.append("bundle", file, file?.name);
|
|
57
|
-
formData.append("repository", JSON.stringify({ ...repository, password }));
|
|
58
|
-
})
|
|
59
|
-
.then(fetchResourceFromLocationHeader)
|
|
60
|
-
.then(getResponseJson);
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
type ImportRepositoryFromBundleRequest = {
|
|
64
|
-
repository: RepositoryCreation;
|
|
65
|
-
file: File;
|
|
66
|
-
compressed?: boolean;
|
|
67
|
-
password?: string;
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
export const useImportRepositoryFromBundle = (repositoryType: RepositoryType) => {
|
|
71
|
-
const url = requiredLink(repositoryType, "import", "bundle");
|
|
72
|
-
const { isLoading, error, data, mutate } = useMutation<Repository, Error, ImportRepositoryFromBundleRequest>(
|
|
73
|
-
({ repository, file, compressed, password }) =>
|
|
74
|
-
importRepository(compressed ? url + "?compressed=true" : url, repository, file, password)
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
return {
|
|
78
|
-
isLoading,
|
|
79
|
-
error,
|
|
80
|
-
importRepositoryFromBundle: (repository: RepositoryCreation, file: File, compressed?: boolean, password?: string) =>
|
|
81
|
-
mutate({
|
|
82
|
-
repository,
|
|
83
|
-
file,
|
|
84
|
-
compressed,
|
|
85
|
-
password,
|
|
86
|
-
}),
|
|
87
|
-
importedRepository: data,
|
|
88
|
-
};
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
type ImportFullRepositoryRequest = {
|
|
92
|
-
repository: RepositoryCreation;
|
|
93
|
-
file: File;
|
|
94
|
-
password?: string;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
export const useImportFullRepository = (repositoryType: RepositoryType) => {
|
|
98
|
-
const { isLoading, error, data, mutate } = useMutation<Repository, Error, ImportFullRepositoryRequest>(
|
|
99
|
-
({ repository, file, password }) =>
|
|
100
|
-
importRepository(requiredLink(repositoryType, "import", "fullImport"), repository, file, password)
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
return {
|
|
104
|
-
isLoading,
|
|
105
|
-
error,
|
|
106
|
-
importFullRepository: (repository: RepositoryCreation, file: File, password?: string) =>
|
|
107
|
-
mutate({
|
|
108
|
-
repository,
|
|
109
|
-
file,
|
|
110
|
-
password,
|
|
111
|
-
}),
|
|
112
|
-
importedRepository: data,
|
|
113
|
-
};
|
|
114
|
-
};
|
|
31
|
+
return useQuery<string, Error>(["importLog", logId], () => apiClient.get(link).then(response => response.text()));
|
|
32
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -50,13 +50,6 @@ export * from "./import";
|
|
|
50
50
|
export * from "./diff";
|
|
51
51
|
export * from "./notifications";
|
|
52
52
|
export * from "./configLink";
|
|
53
|
-
export * from "./apiKeys";
|
|
54
|
-
export * from "./publicKeys";
|
|
55
|
-
export * from "./fileContent";
|
|
56
|
-
export * from "./history";
|
|
57
|
-
export * from "./contentType";
|
|
58
|
-
export * from "./annotations";
|
|
59
|
-
export * from "./search";
|
|
60
53
|
|
|
61
54
|
export { default as ApiProvider } from "./ApiProvider";
|
|
62
55
|
export * from "./ApiProvider";
|
package/src/links.test.ts
CHANGED
|
@@ -60,40 +60,4 @@ describe("requireLink tests", () => {
|
|
|
60
60
|
};
|
|
61
61
|
expect(() => requiredLink(object, "spaceship")).toThrowError();
|
|
62
62
|
});
|
|
63
|
-
|
|
64
|
-
it("should return sub-link if it exists", () => {
|
|
65
|
-
const object = {
|
|
66
|
-
_links: {
|
|
67
|
-
spaceship: [
|
|
68
|
-
{
|
|
69
|
-
name: "one",
|
|
70
|
-
href: "/v2/one"
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
name: "two",
|
|
74
|
-
href: "/v2/two"
|
|
75
|
-
}
|
|
76
|
-
]
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
expect(requiredLink(object, "spaceship", "one")).toBe("/v2/one");
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it("should throw error, if sub-link does not exist in link array", () => {
|
|
83
|
-
const object = {
|
|
84
|
-
_links: {
|
|
85
|
-
spaceship: [
|
|
86
|
-
{
|
|
87
|
-
name: "one",
|
|
88
|
-
href: "/v2/one"
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
name: "two",
|
|
92
|
-
href: "/v2/two"
|
|
93
|
-
}
|
|
94
|
-
]
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
expect(() => requiredLink(object, "spaceship", "three")).toThrowError();
|
|
98
|
-
});
|
|
99
63
|
});
|
package/src/links.ts
CHANGED
|
@@ -21,32 +21,14 @@
|
|
|
21
21
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
22
|
* SOFTWARE.
|
|
23
23
|
*/
|
|
24
|
-
import { HalRepresentation
|
|
24
|
+
import { HalRepresentation } from "@scm-manager/ui-types";
|
|
25
25
|
import { MissingLinkError } from "./errors";
|
|
26
26
|
|
|
27
|
-
export const requiredLink = (object: HalRepresentation, name: string
|
|
27
|
+
export const requiredLink = (object: HalRepresentation, name: string) => {
|
|
28
28
|
const link = object._links[name];
|
|
29
29
|
if (!link) {
|
|
30
30
|
throw new MissingLinkError(`could not find link with name ${name}`);
|
|
31
31
|
}
|
|
32
|
-
if (Array.isArray(link)) {
|
|
33
|
-
if (subName) {
|
|
34
|
-
const subLink = link.find((l: Link) => l.name === subName);
|
|
35
|
-
if (subLink) {
|
|
36
|
-
return subLink.href;
|
|
37
|
-
}
|
|
38
|
-
throw new Error(`could not return href, sub-link ${subName} in ${name} does not exist`);
|
|
39
|
-
}
|
|
40
|
-
throw new Error(`could not return href, link ${name} is a multi link`);
|
|
41
|
-
}
|
|
42
|
-
return link.href;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export const objectLink = (object: HalRepresentation, name: string) => {
|
|
46
|
-
const link = object._links[name];
|
|
47
|
-
if (!link) {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
32
|
if (Array.isArray(link)) {
|
|
51
33
|
throw new Error(`could not return href, link ${name} is a multi link`);
|
|
52
34
|
}
|
package/src/permissions.ts
CHANGED
|
@@ -21,21 +21,18 @@
|
|
|
21
21
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
22
|
* SOFTWARE.
|
|
23
23
|
*/
|
|
24
|
-
import { ApiResult, useIndexJsonResource
|
|
24
|
+
import { ApiResult, useIndexJsonResource } from "./base";
|
|
25
25
|
import { useMutation, useQuery, useQueryClient } from "react-query";
|
|
26
26
|
import {
|
|
27
|
-
GlobalPermissionsCollection,
|
|
28
|
-
Group,
|
|
29
27
|
Namespace,
|
|
30
28
|
Permission,
|
|
31
29
|
PermissionCollection,
|
|
32
30
|
PermissionCreateEntry,
|
|
33
31
|
Repository,
|
|
34
|
-
RepositoryVerbs
|
|
35
|
-
User,
|
|
32
|
+
RepositoryVerbs
|
|
36
33
|
} from "@scm-manager/ui-types";
|
|
37
34
|
import { apiClient } from "./apiclient";
|
|
38
|
-
import {
|
|
35
|
+
import { requiredLink } from "./links";
|
|
39
36
|
import { repoQueryKey } from "./keys";
|
|
40
37
|
import { useRepositoryRoles } from "./repository-roles";
|
|
41
38
|
|
|
@@ -43,9 +40,6 @@ export const useRepositoryVerbs = (): ApiResult<RepositoryVerbs> => {
|
|
|
43
40
|
return useIndexJsonResource<RepositoryVerbs>("repositoryVerbs");
|
|
44
41
|
};
|
|
45
42
|
|
|
46
|
-
/**
|
|
47
|
-
* *IMPORTANT NOTE:* These are actually *REPOSITORY* permissions.
|
|
48
|
-
*/
|
|
49
43
|
export const useAvailablePermissions = () => {
|
|
50
44
|
const roles = useRepositoryRoles();
|
|
51
45
|
const verbs = useRepositoryVerbs();
|
|
@@ -53,14 +47,14 @@ export const useAvailablePermissions = () => {
|
|
|
53
47
|
if (roles.data && verbs.data) {
|
|
54
48
|
data = {
|
|
55
49
|
repositoryVerbs: verbs.data.verbs,
|
|
56
|
-
repositoryRoles: roles.data._embedded.repositoryRoles
|
|
50
|
+
repositoryRoles: roles.data._embedded.repositoryRoles
|
|
57
51
|
};
|
|
58
52
|
}
|
|
59
53
|
|
|
60
54
|
return {
|
|
61
55
|
isLoading: roles.isLoading || verbs.isLoading,
|
|
62
56
|
error: roles.error || verbs.error,
|
|
63
|
-
data
|
|
57
|
+
data
|
|
64
58
|
};
|
|
65
59
|
};
|
|
66
60
|
|
|
@@ -79,21 +73,21 @@ const createQueryKey = (namespaceOrRepository: Namespace | Repository) => {
|
|
|
79
73
|
export const usePermissions = (namespaceOrRepository: Namespace | Repository): ApiResult<PermissionCollection> => {
|
|
80
74
|
const link = requiredLink(namespaceOrRepository, "permissions");
|
|
81
75
|
const queryKey = createQueryKey(namespaceOrRepository);
|
|
82
|
-
return useQuery<PermissionCollection, Error>(queryKey, () => apiClient.get(link).then(
|
|
76
|
+
return useQuery<PermissionCollection, Error>(queryKey, () => apiClient.get(link).then(response => response.json()));
|
|
83
77
|
};
|
|
84
78
|
|
|
85
79
|
const createPermission = (link: string) => {
|
|
86
80
|
return (permission: PermissionCreateEntry) => {
|
|
87
81
|
return apiClient
|
|
88
82
|
.post(link, permission, "application/vnd.scmm-repositoryPermission+json")
|
|
89
|
-
.then(
|
|
83
|
+
.then(response => {
|
|
90
84
|
const location = response.headers.get("Location");
|
|
91
85
|
if (!location) {
|
|
92
86
|
throw new Error("Server does not return required Location header");
|
|
93
87
|
}
|
|
94
88
|
return apiClient.get(location);
|
|
95
89
|
})
|
|
96
|
-
.then(
|
|
90
|
+
.then(response => response.json());
|
|
97
91
|
};
|
|
98
92
|
};
|
|
99
93
|
|
|
@@ -106,21 +100,21 @@ export const useCreatePermission = (namespaceOrRepository: Namespace | Repositor
|
|
|
106
100
|
onSuccess: () => {
|
|
107
101
|
const queryKey = createQueryKey(namespaceOrRepository);
|
|
108
102
|
return queryClient.invalidateQueries(queryKey);
|
|
109
|
-
}
|
|
103
|
+
}
|
|
110
104
|
}
|
|
111
105
|
);
|
|
112
106
|
return {
|
|
113
107
|
isLoading,
|
|
114
108
|
error,
|
|
115
109
|
create: (permission: PermissionCreateEntry) => mutate(permission),
|
|
116
|
-
permission: data
|
|
110
|
+
permission: data
|
|
117
111
|
};
|
|
118
112
|
};
|
|
119
113
|
|
|
120
114
|
export const useUpdatePermission = (namespaceOrRepository: Namespace | Repository) => {
|
|
121
115
|
const queryClient = useQueryClient();
|
|
122
116
|
const { isLoading, error, mutate, data } = useMutation<unknown, Error, Permission>(
|
|
123
|
-
|
|
117
|
+
permission => {
|
|
124
118
|
const link = requiredLink(permission, "update");
|
|
125
119
|
return apiClient.put(link, permission, "application/vnd.scmm-repositoryPermission+json");
|
|
126
120
|
},
|
|
@@ -128,21 +122,21 @@ export const useUpdatePermission = (namespaceOrRepository: Namespace | Repositor
|
|
|
128
122
|
onSuccess: () => {
|
|
129
123
|
const queryKey = createQueryKey(namespaceOrRepository);
|
|
130
124
|
return queryClient.invalidateQueries(queryKey);
|
|
131
|
-
}
|
|
125
|
+
}
|
|
132
126
|
}
|
|
133
127
|
);
|
|
134
128
|
return {
|
|
135
129
|
isLoading,
|
|
136
130
|
error,
|
|
137
131
|
update: (permission: Permission) => mutate(permission),
|
|
138
|
-
isUpdated: !!data
|
|
132
|
+
isUpdated: !!data
|
|
139
133
|
};
|
|
140
134
|
};
|
|
141
135
|
|
|
142
136
|
export const useDeletePermission = (namespaceOrRepository: Namespace | Repository) => {
|
|
143
137
|
const queryClient = useQueryClient();
|
|
144
138
|
const { isLoading, error, mutate, data } = useMutation<unknown, Error, Permission>(
|
|
145
|
-
|
|
139
|
+
permission => {
|
|
146
140
|
const link = requiredLink(permission, "delete");
|
|
147
141
|
return apiClient.delete(link);
|
|
148
142
|
},
|
|
@@ -150,53 +144,13 @@ export const useDeletePermission = (namespaceOrRepository: Namespace | Repositor
|
|
|
150
144
|
onSuccess: () => {
|
|
151
145
|
const queryKey = createQueryKey(namespaceOrRepository);
|
|
152
146
|
return queryClient.invalidateQueries(queryKey);
|
|
153
|
-
}
|
|
147
|
+
}
|
|
154
148
|
}
|
|
155
149
|
);
|
|
156
150
|
return {
|
|
157
151
|
isLoading,
|
|
158
152
|
error,
|
|
159
153
|
remove: (permission: Permission) => mutate(permission),
|
|
160
|
-
isDeleted: !!data
|
|
161
|
-
};
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
const userPermissionsKey = (user: User) => ["user", user.name, "permissions"];
|
|
165
|
-
const groupPermissionsKey = (group: Group) => ["group", group.name, "permissions"];
|
|
166
|
-
|
|
167
|
-
export const useGroupPermissions = (group: Group) =>
|
|
168
|
-
useJsonResource<GlobalPermissionsCollection>(group, "permissions", groupPermissionsKey(group));
|
|
169
|
-
export const useUserPermissions = (user: User) =>
|
|
170
|
-
useJsonResource<GlobalPermissionsCollection>(user, "permissions", userPermissionsKey(user));
|
|
171
|
-
export const useAvailableGlobalPermissions = () =>
|
|
172
|
-
useIndexJsonResource<Omit<GlobalPermissionsCollection, "_links">>("permissions");
|
|
173
|
-
|
|
174
|
-
const useSetEntityPermissions = (permissionCollection?: GlobalPermissionsCollection, key?: string[]) => {
|
|
175
|
-
const queryClient = useQueryClient();
|
|
176
|
-
const url = permissionCollection ? objectLink(permissionCollection, "overwrite") : null;
|
|
177
|
-
const { isLoading, error, mutate, data } = useMutation<unknown, Error, string[]>(
|
|
178
|
-
(permissions) =>
|
|
179
|
-
apiClient.put(
|
|
180
|
-
url!,
|
|
181
|
-
{
|
|
182
|
-
permissions,
|
|
183
|
-
},
|
|
184
|
-
"application/vnd.scmm-permissionCollection+json;v=2"
|
|
185
|
-
),
|
|
186
|
-
{
|
|
187
|
-
onSuccess: () => queryClient.invalidateQueries(key),
|
|
188
|
-
}
|
|
189
|
-
);
|
|
190
|
-
const setPermissions = (permissions: string[]) => mutate(permissions);
|
|
191
|
-
return {
|
|
192
|
-
isLoading,
|
|
193
|
-
error,
|
|
194
|
-
setPermissions: url ? setPermissions : undefined,
|
|
195
|
-
isUpdated: !!data,
|
|
154
|
+
isDeleted: !!data
|
|
196
155
|
};
|
|
197
156
|
};
|
|
198
|
-
|
|
199
|
-
export const useSetUserPermissions = (user: User, permissions?: GlobalPermissionsCollection) =>
|
|
200
|
-
useSetEntityPermissions(permissions, userPermissionsKey(user));
|
|
201
|
-
export const useSetGroupPermissions = (group: Group, permissions?: GlobalPermissionsCollection) =>
|
|
202
|
-
useSetEntityPermissions(permissions, groupPermissionsKey(group));
|