@scm-manager/ui-api 2.27.3 → 2.27.5-20211121-150210

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scm-manager/ui-api",
3
- "version": "2.27.3",
3
+ "version": "2.27.5-20211121-150210",
4
4
  "description": "React hook api for the SCM-Manager backend",
5
5
  "main": "src/index.ts",
6
6
  "files": [
@@ -25,10 +25,10 @@
25
25
  "react-test-renderer": "^17.0.1"
26
26
  },
27
27
  "dependencies": {
28
- "@scm-manager/ui-types": "^2.27.3",
28
+ "@scm-manager/ui-types": "^2.27.5-20211121-150210",
29
29
  "fetch-mock-jest": "^1.5.1",
30
30
  "gitdiff-parser": "^0.1.2",
31
- "query-string": "5",
31
+ "query-string": "6.14.1",
32
32
  "react": "^17.0.1",
33
33
  "react-query": "^3.25.1"
34
34
  },
@@ -36,36 +36,38 @@ 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 = {
45
45
  name: "develop",
46
46
  revision: "42",
47
+ lastCommitter: { name: "trillian" },
47
48
  _links: {
48
49
  delete: {
49
- href: "/hog/branches/develop",
50
- },
51
- },
50
+ href: "/hog/branches/develop"
51
+ }
52
+ }
52
53
  };
53
54
 
54
55
  const feature: Branch = {
55
56
  name: "feature/something-special",
56
57
  revision: "42",
58
+ lastCommitter: { name: "trillian" },
57
59
  _links: {
58
60
  delete: {
59
- href: "/hog/branches/feature%2Fsomething-special",
60
- },
61
- },
61
+ href: "/hog/branches/feature%2Fsomething-special"
62
+ }
63
+ }
62
64
  };
63
65
 
64
66
  const branches: BranchCollection = {
65
67
  _embedded: {
66
- branches: [develop],
68
+ branches: [develop]
67
69
  },
68
- _links: {},
70
+ _links: {}
69
71
  };
70
72
 
71
73
  const queryClient = createInfiniteCachingClient();
@@ -83,7 +85,7 @@ describe("Test branches hooks", () => {
83
85
  fetchMock.getOnce("/api/v2/hog/branches", branches);
84
86
 
85
87
  const { result, waitFor } = renderHook(() => useBranches(repository), {
86
- wrapper: createWrapper(undefined, queryClient),
88
+ wrapper: createWrapper(undefined, queryClient)
87
89
  });
88
90
  await waitFor(() => {
89
91
  return !!result.current.data;
@@ -104,7 +106,7 @@ describe("Test branches hooks", () => {
104
106
  "repository",
105
107
  "hitchhiker",
106
108
  "heart-of-gold",
107
- "branches",
109
+ "branches"
108
110
  ]);
109
111
  expect(data).toEqual(branches);
110
112
  });
@@ -115,7 +117,7 @@ describe("Test branches hooks", () => {
115
117
  fetchMock.getOnce("/api/v2/hog/branches/" + encodeURIComponent(name), branch);
116
118
 
117
119
  const { result, waitFor } = renderHook(() => useBranch(repository, name), {
118
- wrapper: createWrapper(undefined, queryClient),
120
+ wrapper: createWrapper(undefined, queryClient)
119
121
  });
120
122
 
121
123
  expect(result.error).toBeUndefined();
@@ -143,14 +145,14 @@ describe("Test branches hooks", () => {
143
145
  fetchMock.postOnce("/api/v2/hog/branches", {
144
146
  status: 201,
145
147
  headers: {
146
- Location: "/hog/branches/develop",
147
- },
148
+ Location: "/hog/branches/develop"
149
+ }
148
150
  });
149
151
 
150
152
  fetchMock.getOnce("/api/v2/hog/branches/develop", develop);
151
153
 
152
154
  const { result, waitForNextUpdate } = renderHook(() => useCreateBranch(repository), {
153
- wrapper: createWrapper(undefined, queryClient),
155
+ wrapper: createWrapper(undefined, queryClient)
154
156
  });
155
157
 
156
158
  await act(() => {
@@ -175,7 +177,7 @@ describe("Test branches hooks", () => {
175
177
  "hitchhiker",
176
178
  "heart-of-gold",
177
179
  "branch",
178
- "develop",
180
+ "develop"
179
181
  ]);
180
182
  expect(branch).toEqual(develop);
181
183
  });
@@ -192,11 +194,11 @@ describe("Test branches hooks", () => {
192
194
  describe("useDeleteBranch tests", () => {
193
195
  const deleteBranch = async () => {
194
196
  fetchMock.deleteOnce("/api/v2/hog/branches/develop", {
195
- status: 204,
197
+ status: 204
196
198
  });
197
199
 
198
200
  const { result, waitForNextUpdate } = renderHook(() => useDeleteBranch(repository), {
199
- wrapper: createWrapper(undefined, queryClient),
201
+ wrapper: createWrapper(undefined, queryClient)
200
202
  });
201
203
 
202
204
  await act(() => {
package/src/branches.ts CHANGED
@@ -21,19 +21,35 @@
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 { Branch, BranchCollection, BranchCreation, Link, Repository } from "@scm-manager/ui-types";
24
+ import {
25
+ Branch,
26
+ BranchCollection,
27
+ BranchCreation,
28
+ BranchDetails,
29
+ BranchDetailsCollection,
30
+ Link,
31
+ NamespaceAndName,
32
+ Repository
33
+ } from "@scm-manager/ui-types";
25
34
  import { requiredLink } from "./links";
26
- import { useMutation, useQuery, useQueryClient } from "react-query";
35
+ import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from "react-query";
27
36
  import { ApiResult, ApiResultWithFetching } from "./base";
28
37
  import { branchQueryKey, repoQueryKey } from "./keys";
29
38
  import { apiClient } from "./apiclient";
30
39
  import { concat } from "./urls";
40
+ import { useEffect } from "react";
31
41
 
32
42
  export const useBranches = (repository: Repository): ApiResult<BranchCollection> => {
43
+ const queryClient = useQueryClient();
33
44
  const link = requiredLink(repository, "branches");
34
45
  return useQuery<BranchCollection, Error>(
35
46
  repoQueryKey(repository, "branches"),
36
- () => apiClient.get(link).then((response) => response.json())
47
+ () => apiClient.get(link).then(response => response.json()),
48
+ {
49
+ onSuccess: () => {
50
+ return queryClient.invalidateQueries(branchQueryKey(repository, "details"));
51
+ }
52
+ }
37
53
  // we do not populate the cache for a single branch,
38
54
  // because we have no pagination for branches and if we have a lot of them
39
55
  // the population slows us down
@@ -43,22 +59,99 @@ export const useBranches = (repository: Repository): ApiResult<BranchCollection>
43
59
  export const useBranch = (repository: Repository, name: string): ApiResultWithFetching<Branch> => {
44
60
  const link = requiredLink(repository, "branches");
45
61
  return useQuery<Branch, Error>(branchQueryKey(repository, name), () =>
46
- apiClient.get(concat(link, encodeURIComponent(name))).then((response) => response.json())
62
+ apiClient.get(concat(link, encodeURIComponent(name))).then(response => response.json())
63
+ );
64
+ };
65
+
66
+ function chunkBranches(branches: Branch[]) {
67
+ const chunks: Branch[][] = [];
68
+ const chunkSize = 5;
69
+ let chunkIndex = 0;
70
+ for (const branch of branches) {
71
+ if (!chunks[chunkIndex]) {
72
+ chunks[chunkIndex] = [];
73
+ }
74
+ chunks[chunkIndex].push(branch);
75
+ if (chunks[chunkIndex].length >= chunkSize) {
76
+ chunkIndex = chunkIndex + 1;
77
+ }
78
+ }
79
+ return chunks;
80
+ }
81
+
82
+ const branchDetailsQueryKey = (
83
+ repository: NamespaceAndName,
84
+ branch: string | undefined = undefined
85
+ ) => {
86
+ let branchName;
87
+ if (!branch) {
88
+ branchName = "_";
89
+ } else {
90
+ branchName = branch;
91
+ }
92
+ return [...repoQueryKey(repository), "branch-details", branchName];
93
+ };
94
+
95
+ export const useBranchDetailsCollection = (repository: Repository, branches: Branch[]) => {
96
+ const link = requiredLink(repository, "branchDetailsCollection");
97
+ const chunks = chunkBranches(branches);
98
+ const queryClient = useQueryClient();
99
+
100
+ const { data, isLoading, error, fetchNextPage } = useInfiniteQuery<
101
+ BranchDetailsCollection,
102
+ Error,
103
+ BranchDetailsCollection
104
+ >(
105
+ branchDetailsQueryKey(repository),
106
+ ({ pageParam = 0 }) => {
107
+ const encodedBranches = chunks[pageParam]?.map(b => encodeURIComponent(b.name)).join("&branches=");
108
+ return apiClient.get(concat(link, `?branches=${encodedBranches}`)).then(response => response.json());
109
+ },
110
+ {
111
+ getNextPageParam: (lastPage, allPages) => {
112
+ if (allPages.length >= chunks.length) {
113
+ return undefined;
114
+ }
115
+ return allPages.length;
116
+ },
117
+ onSuccess: newData => {
118
+ newData.pages
119
+ .flatMap(d => d._embedded?.branchDetails)
120
+ .filter(d => !!d)
121
+ .forEach(d => queryClient.setQueryData(branchDetailsQueryKey(repository, d!.branchName), () => d));
122
+ }
123
+ }
47
124
  );
125
+
126
+ useEffect(() => {
127
+ fetchNextPage();
128
+ }, [data, fetchNextPage]);
129
+
130
+ return {
131
+ data: data?.pages?.map(d => d._embedded?.branchDetails).flat(1),
132
+ isLoading,
133
+ error
134
+ };
135
+ };
136
+
137
+ export const useBranchDetails = (repository: Repository, branch: Branch) => {
138
+ const link = (branch._links.details as Link).href;
139
+ const queryKey = branchDetailsQueryKey(repository, branch.name);
140
+ return useQuery<BranchDetails, Error>(queryKey, () => apiClient.get(link).then(response => response.json()));
48
141
  };
49
142
 
50
143
  const createBranch = (link: string) => {
51
144
  return (branch: BranchCreation) => {
52
145
  return apiClient
53
146
  .post(link, branch, "application/vnd.scmm-branchRequest+json;v=2")
54
- .then((response) => {
147
+ .then(response => {
55
148
  const location = response.headers.get("Location");
56
149
  if (!location) {
57
150
  throw new Error("Server does not return required Location header");
58
151
  }
59
152
  return apiClient.get(location);
60
153
  })
61
- .then((response) => response.json());
154
+ .then(response => response.json());
62
155
  };
63
156
  };
64
157
 
@@ -66,23 +159,23 @@ export const useCreateBranch = (repository: Repository) => {
66
159
  const queryClient = useQueryClient();
67
160
  const link = requiredLink(repository, "branches");
68
161
  const { mutate, isLoading, error, data } = useMutation<Branch, Error, BranchCreation>(createBranch(link), {
69
- onSuccess: async (branch) => {
162
+ onSuccess: async branch => {
70
163
  queryClient.setQueryData(branchQueryKey(repository, branch), branch);
71
164
  await queryClient.invalidateQueries(repoQueryKey(repository, "branches"));
72
- },
165
+ }
73
166
  });
74
167
  return {
75
168
  create: (branch: BranchCreation) => mutate(branch),
76
169
  isLoading,
77
170
  error,
78
- branch: data,
171
+ branch: data
79
172
  };
80
173
  };
81
174
 
82
175
  export const useDeleteBranch = (repository: Repository) => {
83
176
  const queryClient = useQueryClient();
84
177
  const { mutate, isLoading, error, data } = useMutation<unknown, Error, Branch>(
85
- (branch) => {
178
+ branch => {
86
179
  const deleteUrl = (branch._links.delete as Link).href;
87
180
  return apiClient.delete(deleteUrl);
88
181
  },
@@ -90,14 +183,14 @@ export const useDeleteBranch = (repository: Repository) => {
90
183
  onSuccess: async (_, branch) => {
91
184
  queryClient.removeQueries(branchQueryKey(repository, branch));
92
185
  await queryClient.invalidateQueries(repoQueryKey(repository, "branches"));
93
- },
186
+ }
94
187
  }
95
188
  );
96
189
  return {
97
190
  remove: (branch: Branch) => mutate(branch),
98
191
  isLoading,
99
192
  error,
100
- isDeleted: !!data,
193
+ isDeleted: !!data
101
194
  };
102
195
  };
103
196
 
@@ -106,6 +199,6 @@ type DefaultBranch = { defaultBranch: string };
106
199
  export const useDefaultBranch = (repository: Repository): ApiResult<DefaultBranch> => {
107
200
  const link = requiredLink(repository, "defaultBranch");
108
201
  return useQuery<DefaultBranch, Error>(branchQueryKey(repository, "__default-branch"), () =>
109
- apiClient.get(link).then((response) => response.json())
202
+ apiClient.get(link).then(response => response.json())
110
203
  );
111
204
  };
@@ -35,19 +35,20 @@ describe("Test changeset hooks", () => {
35
35
  type: "hg",
36
36
  _links: {
37
37
  changesets: {
38
- href: "/r/c",
39
- },
40
- },
38
+ href: "/r/c"
39
+ }
40
+ }
41
41
  };
42
42
 
43
43
  const develop: Branch = {
44
44
  name: "develop",
45
45
  revision: "42",
46
+ lastCommitter: { name: "trillian" },
46
47
  _links: {
47
48
  history: {
48
- href: "/r/b/c",
49
- },
50
- },
49
+ href: "/r/b/c"
50
+ }
51
+ }
51
52
  };
52
53
 
53
54
  const changeset: Changeset = {
@@ -55,19 +56,19 @@ describe("Test changeset hooks", () => {
55
56
  description: "Awesome change",
56
57
  date: new Date(),
57
58
  author: {
58
- name: "Arthur Dent",
59
+ name: "Arthur Dent"
59
60
  },
60
61
  _embedded: {},
61
- _links: {},
62
+ _links: {}
62
63
  };
63
64
 
64
65
  const changesets: ChangesetCollection = {
65
66
  page: 1,
66
67
  pageTotal: 1,
67
68
  _embedded: {
68
- changesets: [changeset],
69
+ changesets: [changeset]
69
70
  },
70
- _links: {},
71
+ _links: {}
71
72
  };
72
73
 
73
74
  const expectChangesetCollection = (result?: ChangesetCollection) => {
@@ -85,7 +86,7 @@ describe("Test changeset hooks", () => {
85
86
  const queryClient = createInfiniteCachingClient();
86
87
 
87
88
  const { result, waitFor } = renderHook(() => useChangesets(repository), {
88
- wrapper: createWrapper(undefined, queryClient),
89
+ wrapper: createWrapper(undefined, queryClient)
89
90
  });
90
91
 
91
92
  await waitFor(() => {
@@ -98,14 +99,14 @@ describe("Test changeset hooks", () => {
98
99
  it("should return changesets for page", async () => {
99
100
  fetchMock.getOnce("/api/v2/r/c", changesets, {
100
101
  query: {
101
- page: 42,
102
- },
102
+ page: 42
103
+ }
103
104
  });
104
105
 
105
106
  const queryClient = createInfiniteCachingClient();
106
107
 
107
108
  const { result, waitFor } = renderHook(() => useChangesets(repository, { page: 42 }), {
108
- wrapper: createWrapper(undefined, queryClient),
109
+ wrapper: createWrapper(undefined, queryClient)
109
110
  });
110
111
 
111
112
  await waitFor(() => {
@@ -121,7 +122,7 @@ describe("Test changeset hooks", () => {
121
122
  const queryClient = createInfiniteCachingClient();
122
123
 
123
124
  const { result, waitFor } = renderHook(() => useChangesets(repository, { branch: develop }), {
124
- wrapper: createWrapper(undefined, queryClient),
125
+ wrapper: createWrapper(undefined, queryClient)
125
126
  });
126
127
 
127
128
  await waitFor(() => {
@@ -137,7 +138,7 @@ describe("Test changeset hooks", () => {
137
138
  const queryClient = createInfiniteCachingClient();
138
139
 
139
140
  const { result, waitFor } = renderHook(() => useChangesets(repository), {
140
- wrapper: createWrapper(undefined, queryClient),
141
+ wrapper: createWrapper(undefined, queryClient)
141
142
  });
142
143
 
143
144
  await waitFor(() => {
@@ -149,7 +150,7 @@ describe("Test changeset hooks", () => {
149
150
  "hitchhiker",
150
151
  "heart-of-gold",
151
152
  "changeset",
152
- "42",
153
+ "42"
153
154
  ]);
154
155
 
155
156
  expect(changeset?.id).toBe("42");
@@ -163,7 +164,7 @@ describe("Test changeset hooks", () => {
163
164
  const queryClient = createInfiniteCachingClient();
164
165
 
165
166
  const { result, waitFor } = renderHook(() => useChangeset(repository, "42"), {
166
- wrapper: createWrapper(undefined, queryClient),
167
+ wrapper: createWrapper(undefined, queryClient)
167
168
  });
168
169
 
169
170
  await waitFor(() => {
@@ -50,6 +50,7 @@ describe("Test config hooks", () => {
50
50
  namespaceStrategy: "",
51
51
  emergencyContacts: [],
52
52
  pluginUrl: "",
53
+ pluginAuthUrl: "",
53
54
  proxyExcludes: [],
54
55
  proxyPassword: null,
55
56
  proxyPort: 0,
@@ -28,6 +28,9 @@ import { ApiResultWithFetching } from "./base";
28
28
  export type ContentType = {
29
29
  type: string;
30
30
  language?: string;
31
+ aceMode?: string;
32
+ codemirrorMode?: string;
33
+ prismMode?: string;
31
34
  };
32
35
 
33
36
  function getContentType(url: string): Promise<ContentType> {
@@ -35,6 +38,9 @@ function getContentType(url: string): Promise<ContentType> {
35
38
  return {
36
39
  type: response.headers.get("Content-Type") || "application/octet-stream",
37
40
  language: response.headers.get("X-Programming-Language") || undefined,
41
+ aceMode: response.headers.get("X-Syntax-Mode-Ace") || undefined,
42
+ codemirrorMode: response.headers.get("X-Syntax-Mode-Codemirror") || undefined,
43
+ prismMode: response.headers.get("X-Syntax-Mode-Prism") || undefined,
38
44
  };
39
45
  });
40
46
  }
package/src/import.ts CHANGED
@@ -30,12 +30,12 @@ import { requiredLink } from "./links";
30
30
 
31
31
  export const useImportLog = (logId: string): ApiResult<string> => {
32
32
  const link = useRequiredIndexLink("importLog").replace("{logId}", logId);
33
- return useQuery<string, Error>(["importLog", logId], () => apiClient.get(link).then((response) => response.text()));
33
+ return useQuery<string, Error>(["importLog", logId], () => apiClient.get(link).then(response => response.text()));
34
34
  };
35
35
 
36
36
  export const useImportRepositoryFromUrl = (repositoryType: RepositoryType) => {
37
37
  const url = requiredLink(repositoryType, "import", "url");
38
- const { isLoading, error, data, mutate } = useMutation<Repository, Error, RepositoryUrlImport>((repo) =>
38
+ const { isLoading, error, data, mutate } = useMutation<Repository, Error, RepositoryUrlImport>(repo =>
39
39
  apiClient
40
40
  .post(url, repo, "application/vnd.scmm-repository+json;v=2")
41
41
  .then(fetchResourceFromLocationHeader)
@@ -46,14 +46,14 @@ export const useImportRepositoryFromUrl = (repositoryType: RepositoryType) => {
46
46
  isLoading,
47
47
  error,
48
48
  importRepositoryFromUrl: (repository: RepositoryUrlImport) => mutate(repository),
49
- importedRepository: data,
49
+ importedRepository: data
50
50
  };
51
51
  };
52
52
 
53
53
  const importRepository = (url: string, repository: RepositoryCreation, file: File, password?: string) => {
54
54
  return apiClient
55
- .postBinary(url, (formData) => {
56
- formData.append("bundle", file, file?.name);
55
+ .postBinary(url, formData => {
56
+ formData.append("bundle", file, file.name);
57
57
  formData.append("repository", JSON.stringify({ ...repository, password }));
58
58
  })
59
59
  .then(fetchResourceFromLocationHeader)
@@ -82,9 +82,9 @@ export const useImportRepositoryFromBundle = (repositoryType: RepositoryType) =>
82
82
  repository,
83
83
  file,
84
84
  compressed,
85
- password,
85
+ password
86
86
  }),
87
- importedRepository: data,
87
+ importedRepository: data
88
88
  };
89
89
  };
90
90
 
@@ -107,8 +107,8 @@ export const useImportFullRepository = (repositoryType: RepositoryType) => {
107
107
  mutate({
108
108
  repository,
109
109
  file,
110
- password,
110
+ password
111
111
  }),
112
- importedRepository: data,
112
+ importedRepository: data
113
113
  };
114
114
  };
package/src/index.ts CHANGED
@@ -58,6 +58,8 @@ export * from "./history";
58
58
  export * from "./contentType";
59
59
  export * from "./annotations";
60
60
  export * from "./search";
61
+ export * from "./loginInfo";
62
+ export * from "./usePluginCenterAuthInfo";
61
63
 
62
64
  export { default as ApiProvider } from "./ApiProvider";
63
65
  export * from "./ApiProvider";
@@ -0,0 +1,43 @@
1
+ /*
2
+ * MIT License
3
+ *
4
+ * Copyright (c) 2020-present Cloudogu GmbH and Contributors
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+ import { ApiResult, useIndexLink } from "./base";
25
+ import { useQuery } from "react-query";
26
+ import { LoginInfo } from "@scm-manager/ui-types";
27
+
28
+ export const useLoginInfo = (disabled = false): ApiResult<LoginInfo> => {
29
+ const loginInfoLink = useIndexLink("loginInfo");
30
+ const { error, isLoading, data } = useQuery<LoginInfo, Error>(
31
+ ["loginInfo"],
32
+ () => fetch(loginInfoLink!).then(response => response.json()),
33
+ {
34
+ enabled: !disabled && !!loginInfoLink,
35
+ refetchOnWindowFocus: false
36
+ }
37
+ );
38
+ return {
39
+ data,
40
+ error,
41
+ isLoading
42
+ };
43
+ };
@@ -37,7 +37,7 @@ import {
37
37
  useRepository,
38
38
  useRepositoryTypes,
39
39
  useUnarchiveRepository,
40
- useUpdateRepository,
40
+ useUpdateRepository
41
41
  } from "./repositories";
42
42
  import { Repository } from "@scm-manager/ui-types";
43
43
  import { QueryClient } from "react-query";
@@ -50,25 +50,25 @@ describe("Test repository hooks", () => {
50
50
  type: "git",
51
51
  _links: {
52
52
  delete: {
53
- href: "/r/spaceships/heartOfGold",
53
+ href: "/r/spaceships/heartOfGold"
54
54
  },
55
55
  update: {
56
- href: "/r/spaceships/heartOfGold",
56
+ href: "/r/spaceships/heartOfGold"
57
57
  },
58
58
  archive: {
59
- href: "/r/spaceships/heartOfGold/archive",
59
+ href: "/r/spaceships/heartOfGold/archive"
60
60
  },
61
61
  unarchive: {
62
- href: "/r/spaceships/heartOfGold/unarchive",
63
- },
64
- },
62
+ href: "/r/spaceships/heartOfGold/unarchive"
63
+ }
64
+ }
65
65
  };
66
66
 
67
67
  const repositoryCollection = {
68
68
  _embedded: {
69
- repositories: [heartOfGold],
69
+ repositories: [heartOfGold]
70
70
  },
71
- _links: {},
71
+ _links: {}
72
72
  };
73
73
 
74
74
  afterEach(() => {
@@ -78,7 +78,7 @@ describe("Test repository hooks", () => {
78
78
  describe("useRepositories tests", () => {
79
79
  const expectCollection = async (queryClient: QueryClient, request?: UseRepositoriesRequest) => {
80
80
  const { result, waitFor } = renderHook(() => useRepositories(request), {
81
- wrapper: createWrapper(undefined, queryClient),
81
+ wrapper: createWrapper(undefined, queryClient)
82
82
  });
83
83
  await waitFor(() => {
84
84
  return !!result.current.data;
@@ -91,8 +91,8 @@ describe("Test repository hooks", () => {
91
91
  setIndexLink(queryClient, "repositories", "/repos");
92
92
  fetchMock.get("/api/v2/repos", repositoryCollection, {
93
93
  query: {
94
- sortBy: "namespaceAndName",
95
- },
94
+ sortBy: "namespaceAndName"
95
+ }
96
96
  });
97
97
 
98
98
  await expectCollection(queryClient);
@@ -104,12 +104,12 @@ describe("Test repository hooks", () => {
104
104
  fetchMock.get("/api/v2/repos", repositoryCollection, {
105
105
  query: {
106
106
  sortBy: "namespaceAndName",
107
- page: "42",
108
- },
107
+ page: "42"
108
+ }
109
109
  });
110
110
 
111
111
  await expectCollection(queryClient, {
112
- page: 42,
112
+ page: 42
113
113
  });
114
114
  });
115
115
 
@@ -118,8 +118,8 @@ describe("Test repository hooks", () => {
118
118
  setIndexLink(queryClient, "repositories", "/repos");
119
119
  fetchMock.get("/api/v2/spaceships", repositoryCollection, {
120
120
  query: {
121
- sortBy: "namespaceAndName",
122
- },
121
+ sortBy: "namespaceAndName"
122
+ }
123
123
  });
124
124
 
125
125
  await expectCollection(queryClient, {
@@ -127,10 +127,10 @@ describe("Test repository hooks", () => {
127
127
  namespace: "spaceships",
128
128
  _links: {
129
129
  repositories: {
130
- href: "/spaceships",
131
- },
132
- },
133
- },
130
+ href: "/spaceships"
131
+ }
132
+ }
133
+ }
134
134
  });
135
135
  });
136
136
 
@@ -140,12 +140,12 @@ describe("Test repository hooks", () => {
140
140
  fetchMock.get("/api/v2/repos", repositoryCollection, {
141
141
  query: {
142
142
  sortBy: "namespaceAndName",
143
- q: "heart",
144
- },
143
+ q: "heart"
144
+ }
145
145
  });
146
146
 
147
147
  await expectCollection(queryClient, {
148
- search: "heart",
148
+ search: "heart"
149
149
  });
150
150
  });
151
151
 
@@ -154,8 +154,8 @@ describe("Test repository hooks", () => {
154
154
  setIndexLink(queryClient, "repositories", "/repos");
155
155
  fetchMock.get("/api/v2/repos", repositoryCollection, {
156
156
  query: {
157
- sortBy: "namespaceAndName",
158
- },
157
+ sortBy: "namespaceAndName"
158
+ }
159
159
  });
160
160
 
161
161
  await expectCollection(queryClient);
@@ -168,7 +168,7 @@ describe("Test repository hooks", () => {
168
168
  const queryClient = createInfiniteCachingClient();
169
169
  setIndexLink(queryClient, "repositories", "/repos");
170
170
  const { result } = renderHook(() => useRepositories({ disabled: true }), {
171
- wrapper: createWrapper(undefined, queryClient),
171
+ wrapper: createWrapper(undefined, queryClient)
172
172
  });
173
173
 
174
174
  expect(result.current.isLoading).toBe(false);
@@ -185,19 +185,18 @@ describe("Test repository hooks", () => {
185
185
  fetchMock.postOnce("/api/v2/r", {
186
186
  status: 201,
187
187
  headers: {
188
- Location: "/r/spaceships/heartOfGold",
189
- },
188
+ Location: "/r/spaceships/heartOfGold"
189
+ }
190
190
  });
191
191
 
192
192
  fetchMock.getOnce("/api/v2/r/spaceships/heartOfGold", heartOfGold);
193
193
 
194
194
  const { result, waitForNextUpdate } = renderHook(() => useCreateRepository(), {
195
- wrapper: createWrapper(undefined, queryClient),
195
+ wrapper: createWrapper(undefined, queryClient)
196
196
  });
197
197
 
198
198
  const repository = {
199
- ...heartOfGold,
200
- contextEntries: [],
199
+ ...heartOfGold
201
200
  };
202
201
 
203
202
  await act(() => {
@@ -216,19 +215,18 @@ describe("Test repository hooks", () => {
216
215
  fetchMock.postOnce("/api/v2/r?initialize=true", {
217
216
  status: 201,
218
217
  headers: {
219
- Location: "/r/spaceships/heartOfGold",
220
- },
218
+ Location: "/r/spaceships/heartOfGold"
219
+ }
221
220
  });
222
221
 
223
222
  fetchMock.getOnce("/api/v2/r/spaceships/heartOfGold", heartOfGold);
224
223
 
225
224
  const { result, waitForNextUpdate } = renderHook(() => useCreateRepository(), {
226
- wrapper: createWrapper(undefined, queryClient),
225
+ wrapper: createWrapper(undefined, queryClient)
227
226
  });
228
227
 
229
228
  const repository = {
230
- ...heartOfGold,
231
- contextEntries: [],
229
+ ...heartOfGold
232
230
  };
233
231
 
234
232
  await act(() => {
@@ -245,16 +243,15 @@ describe("Test repository hooks", () => {
245
243
  setIndexLink(queryClient, "repositories", "/r");
246
244
 
247
245
  fetchMock.postOnce("/api/v2/r", {
248
- status: 201,
246
+ status: 201
249
247
  });
250
248
 
251
249
  const { result, waitForNextUpdate } = renderHook(() => useCreateRepository(), {
252
- wrapper: createWrapper(undefined, queryClient),
250
+ wrapper: createWrapper(undefined, queryClient)
253
251
  });
254
252
 
255
253
  const repository = {
256
- ...heartOfGold,
257
- contextEntries: [],
254
+ ...heartOfGold
258
255
  };
259
256
 
260
257
  await act(() => {
@@ -274,7 +271,7 @@ describe("Test repository hooks", () => {
274
271
  fetchMock.get("/api/v2/r/spaceships/heartOfGold", heartOfGold);
275
272
 
276
273
  const { result, waitFor } = renderHook(() => useRepository("spaceships", "heartOfGold"), {
277
- wrapper: createWrapper(undefined, queryClient),
274
+ wrapper: createWrapper(undefined, queryClient)
278
275
  });
279
276
  await waitFor(() => {
280
277
  return !!result.current.data;
@@ -293,15 +290,15 @@ describe("Test repository hooks", () => {
293
290
  {
294
291
  name: "git",
295
292
  displayName: "Git",
296
- _links: {},
297
- },
298
- ],
293
+ _links: {}
294
+ }
295
+ ]
299
296
  },
300
- _links: {},
297
+ _links: {}
301
298
  });
302
299
 
303
300
  const { result, waitFor } = renderHook(() => useRepositoryTypes(), {
304
- wrapper: createWrapper(undefined, queryClient),
301
+ wrapper: createWrapper(undefined, queryClient)
305
302
  });
306
303
  await waitFor(() => {
307
304
  return !!result.current.data;
@@ -322,11 +319,11 @@ describe("Test repository hooks", () => {
322
319
 
323
320
  const deleteRepository = async (options?: UseDeleteRepositoryOptions) => {
324
321
  fetchMock.deleteOnce("/api/v2/r/spaceships/heartOfGold", {
325
- status: 204,
322
+ status: 204
326
323
  });
327
324
 
328
325
  const { result, waitForNextUpdate } = renderHook(() => useDeleteRepository(options), {
329
- wrapper: createWrapper(undefined, queryClient),
326
+ wrapper: createWrapper(undefined, queryClient)
330
327
  });
331
328
 
332
329
  await act(() => {
@@ -371,9 +368,9 @@ describe("Test repository hooks", () => {
371
368
  it("should call onSuccess callback", async () => {
372
369
  let repo;
373
370
  await deleteRepository({
374
- onSuccess: (repository) => {
371
+ onSuccess: repository => {
375
372
  repo = repository;
376
- },
373
+ }
377
374
  });
378
375
  expect(repo).toEqual(heartOfGold);
379
376
  });
@@ -388,11 +385,11 @@ describe("Test repository hooks", () => {
388
385
 
389
386
  const updateRepository = async () => {
390
387
  fetchMock.putOnce("/api/v2/r/spaceships/heartOfGold", {
391
- status: 204,
388
+ status: 204
392
389
  });
393
390
 
394
391
  const { result, waitForNextUpdate } = renderHook(() => useUpdateRepository(), {
395
- wrapper: createWrapper(undefined, queryClient),
392
+ wrapper: createWrapper(undefined, queryClient)
396
393
  });
397
394
 
398
395
  await act(() => {
@@ -436,11 +433,11 @@ describe("Test repository hooks", () => {
436
433
 
437
434
  const archiveRepository = async () => {
438
435
  fetchMock.postOnce("/api/v2/r/spaceships/heartOfGold/archive", {
439
- status: 204,
436
+ status: 204
440
437
  });
441
438
 
442
439
  const { result, waitForNextUpdate } = renderHook(() => useArchiveRepository(), {
443
- wrapper: createWrapper(undefined, queryClient),
440
+ wrapper: createWrapper(undefined, queryClient)
444
441
  });
445
442
 
446
443
  await act(() => {
@@ -484,11 +481,11 @@ describe("Test repository hooks", () => {
484
481
 
485
482
  const unarchiveRepository = async () => {
486
483
  fetchMock.postOnce("/api/v2/r/spaceships/heartOfGold/unarchive", {
487
- status: 204,
484
+ status: 204
488
485
  });
489
486
 
490
487
  const { result, waitForNextUpdate } = renderHook(() => useUnarchiveRepository(), {
491
- wrapper: createWrapper(undefined, queryClient),
488
+ wrapper: createWrapper(undefined, queryClient)
492
489
  });
493
490
 
494
491
  await act(() => {
package/src/urls.ts CHANGED
@@ -84,8 +84,13 @@ function parsePageNumber(pageAsString: string) {
84
84
  return page;
85
85
  }
86
86
 
87
- export function getQueryStringFromLocation(location: any) {
88
- return location.search ? queryString.parse(location.search).q : undefined;
87
+ export function getQueryStringFromLocation(location: { search?: string }): string | undefined {
88
+ if (location.search) {
89
+ const query = queryString.parse(location.search).q;
90
+ if (query && !Array.isArray(query)) {
91
+ return query;
92
+ }
93
+ }
89
94
  }
90
95
 
91
96
  export function stripEndingSlash(url: string) {
@@ -0,0 +1,78 @@
1
+ /*
2
+ * MIT License
3
+ *
4
+ * Copyright (c) 2020-present Cloudogu GmbH and Contributors
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ import { ApiResult, useIndexLink } from "./base";
26
+ import { Link, PluginCenterAuthenticationInfo } from "@scm-manager/ui-types";
27
+ import { useMutation, useQuery, useQueryClient } from "react-query";
28
+ import { apiClient } from "./apiclient";
29
+ import { useLocation } from "react-router-dom";
30
+
31
+ export const usePluginCenterAuthInfo = (): ApiResult<PluginCenterAuthenticationInfo> => {
32
+ const link = useIndexLink("pluginCenterAuth");
33
+ const location = useLocation();
34
+ return useQuery<PluginCenterAuthenticationInfo, Error>(
35
+ ["pluginCenterAuth"],
36
+ () => {
37
+ if (!link) {
38
+ throw new Error("no such plugin center auth link");
39
+ }
40
+ return apiClient
41
+ .get(link)
42
+ .then(response => response.json())
43
+ .then((result: PluginCenterAuthenticationInfo) => {
44
+ if (result._links?.login) {
45
+ (result._links.login as Link).href += `?source=${location.pathname}`;
46
+ }
47
+ return result;
48
+ });
49
+ },
50
+ {
51
+ enabled: !!link
52
+ }
53
+ );
54
+ };
55
+
56
+ export const usePluginCenterLogout = (authenticationInfo: PluginCenterAuthenticationInfo) => {
57
+ const queryClient = useQueryClient();
58
+ const { mutate, isLoading, error } = useMutation<unknown, Error>(
59
+ () => {
60
+ if (!authenticationInfo._links.logout) {
61
+ throw new Error("authenticationInfo has no logout link");
62
+ }
63
+ const logout = authenticationInfo._links.logout as Link;
64
+ return apiClient.delete(logout.href);
65
+ },
66
+ {
67
+ onSuccess: () => queryClient.invalidateQueries("pluginCenterAuth")
68
+ }
69
+ );
70
+
71
+ return {
72
+ logout: () => {
73
+ mutate();
74
+ },
75
+ isLoading,
76
+ error
77
+ };
78
+ };