@gen3/core 0.10.44
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/dist/cjs/index.js +2243 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/dts/api.d.ts +8 -0
- package/dist/dts/constants.d.ts +32 -0
- package/dist/dts/dataAccess.d.ts +35 -0
- package/dist/dts/features/aiSearch/aiSearchSlice.d.ts +38 -0
- package/dist/dts/features/aiSearch/index.d.ts +2 -0
- package/dist/dts/features/app/store.d.ts +6 -0
- package/dist/dts/features/authz/authzMappingSlice.d.ts +141 -0
- package/dist/dts/features/authz/index.d.ts +3 -0
- package/dist/dts/features/authz/types.d.ts +5 -0
- package/dist/dts/features/cohort/cohortBuilderConfigSlice.d.ts +0 -0
- package/dist/dts/features/cohort/cohortSlice.d.ts +95 -0
- package/dist/dts/features/cohort/index.d.ts +2 -0
- package/dist/dts/features/cohort/types.d.ts +0 -0
- package/dist/dts/features/download/constants.d.ts +14 -0
- package/dist/dts/features/download/downloadStatusApi.d.ts +19 -0
- package/dist/dts/features/download/index.d.ts +2 -0
- package/dist/dts/features/download/types.d.ts +20 -0
- package/dist/dts/features/drsResolver/drsHostnameSlice.d.ts +5 -0
- package/dist/dts/features/drsResolver/index.d.ts +2 -0
- package/dist/dts/features/drsResolver/resolvers/cachedDRSResolver.d.ts +1 -0
- package/dist/dts/features/drsResolver/resolvers/dataGUIDSDotOrg.d.ts +6 -0
- package/dist/dts/features/drsResolver/resolvers/tests/dataGUIDSDotOrg.unit.test.d.ts +1 -0
- package/dist/dts/features/drsResolver/types.d.ts +0 -0
- package/dist/dts/features/drsResolver/utils.d.ts +6 -0
- package/dist/dts/features/fence/credentialsApi.d.ts +35 -0
- package/dist/dts/features/fence/fenceApi.d.ts +37 -0
- package/dist/dts/features/fence/index.d.ts +6 -0
- package/dist/dts/features/fence/jwtApi.d.ts +12 -0
- package/dist/dts/features/fence/types.d.ts +12 -0
- package/dist/dts/features/fence/utils.d.ts +12 -0
- package/dist/dts/features/filters/filters.d.ts +122 -0
- package/dist/dts/features/filters/index.d.ts +4 -0
- package/dist/dts/features/filters/tests/utils.unit.test.d.ts +1 -0
- package/dist/dts/features/filters/types.d.ts +113 -0
- package/dist/dts/features/filters/utils.d.ts +26 -0
- package/dist/dts/features/gen3/gen3Api.d.ts +11 -0
- package/dist/dts/features/gen3/index.d.ts +2 -0
- package/dist/dts/features/gen3Apps/Gen3App.d.ts +48 -0
- package/dist/dts/features/gen3Apps/gen3AppRegistry.d.ts +7 -0
- package/dist/dts/features/gen3Apps/gen3AppsSlice.d.ts +19 -0
- package/dist/dts/features/gen3Apps/index.d.ts +3 -0
- package/dist/dts/features/graphQL/graphQLSlice.d.ts +12 -0
- package/dist/dts/features/graphQL/index.d.ts +1 -0
- package/dist/dts/features/guppy/conversion.d.ts +23 -0
- package/dist/dts/features/guppy/guppyApi.d.ts +41 -0
- package/dist/dts/features/guppy/guppyDownloadSlice.d.ts +21 -0
- package/dist/dts/features/guppy/guppySlice.d.ts +120 -0
- package/dist/dts/features/guppy/index.d.ts +6 -0
- package/dist/dts/features/guppy/tests/downloadFromGuppy.unit.test.d.ts +1 -0
- package/dist/dts/features/guppy/types.d.ts +33 -0
- package/dist/dts/features/guppy/utils.d.ts +28 -0
- package/dist/dts/features/metadata/index.d.ts +3 -0
- package/dist/dts/features/metadata/metadataSlice.d.ts +56 -0
- package/dist/dts/features/metadata/types.d.ts +12 -0
- package/dist/dts/features/modals/index.d.ts +2 -0
- package/dist/dts/features/modals/modalsSlice.d.ts +26 -0
- package/dist/dts/features/submission/authMappingUtils.d.ts +15 -0
- package/dist/dts/features/submission/index.d.ts +3 -0
- package/dist/dts/features/submission/submissionApi.d.ts +43 -0
- package/dist/dts/features/submission/types.d.ts +46 -0
- package/dist/dts/features/user/externalLoginsSlice.d.ts +14 -0
- package/dist/dts/features/user/hooks.d.ts +18 -0
- package/dist/dts/features/user/index.d.ts +5 -0
- package/dist/dts/features/user/test/useGetExternalLoginsQuery.unit.test.d.ts +1 -0
- package/dist/dts/features/user/types.d.ts +50 -0
- package/dist/dts/features/user/userSlice.d.ts +53 -0
- package/dist/dts/features/user/userSliceRTK.d.ts +484 -0
- package/dist/dts/features/workspace/index.d.ts +2 -0
- package/dist/dts/features/workspace/types.d.ts +0 -0
- package/dist/dts/features/workspace/workspacesSlice.d.ts +6 -0
- package/dist/dts/hooks.d.ts +36 -0
- package/dist/dts/index.d.ts +23 -0
- package/dist/dts/provider.d.ts +2 -0
- package/dist/dts/reducers.d.ts +22 -0
- package/dist/dts/store.d.ts +53 -0
- package/dist/dts/store.unit.test.d.ts +2 -0
- package/dist/dts/types/index.d.ts +51 -0
- package/dist/dts/utils/extractvalues.d.ts +9 -0
- package/dist/dts/utils/index.d.ts +2 -0
- package/dist/dts/utils/ts-utils.d.ts +4 -0
- package/dist/esm/index.js +2083 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/index.d.ts +1604 -0
- package/package.json +70 -0
|
@@ -0,0 +1,2243 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var toolkit = require('@reduxjs/toolkit');
|
|
4
|
+
var react = require('@reduxjs/toolkit/query/react');
|
|
5
|
+
var React = require('react');
|
|
6
|
+
var reactRedux = require('react-redux');
|
|
7
|
+
var react$1 = require('@reduxjs/toolkit/dist/query/react');
|
|
8
|
+
var cookiesNext = require('cookies-next');
|
|
9
|
+
var query = require('@reduxjs/toolkit/query');
|
|
10
|
+
var lodash = require('lodash');
|
|
11
|
+
var Queue = require('queue');
|
|
12
|
+
var jsonpathPlus = require('jsonpath-plus');
|
|
13
|
+
var uuid = require('uuid');
|
|
14
|
+
require('redux-persist');
|
|
15
|
+
var reactCookie = require('react-cookie');
|
|
16
|
+
var useSWR = require('swr');
|
|
17
|
+
var flat = require('flat');
|
|
18
|
+
var Papa = require('papaparse');
|
|
19
|
+
|
|
20
|
+
const GEN3_COMMONS_NAME = process.env.GEN3_COMMONS_NAME || 'gen3';
|
|
21
|
+
const GEN3_API = process.env.NEXT_PUBLIC_GEN3_API || '';
|
|
22
|
+
const GEN3_DOMAIN = process.env.NEXT_PUBLIC_GEN3_DOMAIN || '';
|
|
23
|
+
/**
|
|
24
|
+
* Service Specific Constants
|
|
25
|
+
*/ const GEN3_GUPPY_API = process.env.NEXT_PUBLIC_GEN3_GUPPY_API || `${GEN3_API}/guppy`;
|
|
26
|
+
const GEN3_MDS_API = process.env.NEXT_PUBLIC_GEN3_MDS_API || `${GEN3_API}/mds`;
|
|
27
|
+
const GEN3_DOWNLOADS_ENDPOINT = process.env.NEXT_PUBLIC_GEN3_DOWNLOADS_ENDPOINT || 'downloads';
|
|
28
|
+
const GEN3_FENCE_API = process.env.NEXT_PUBLIC_GEN3_FENCE_API || GEN3_API;
|
|
29
|
+
const GEN3_AI_SEARCH_API = process.env.NEXT_PUBLIC_GEN3_AI_SEARCH_API || `${GEN3_API}/ai-search`;
|
|
30
|
+
const GEN3_AUTHZ_API = process.env.NEXT_PUBLIC_GEN3_AUTHZ_API || `${GEN3_API}/authz`;
|
|
31
|
+
const GEN3_REDIRECT_URL = process.env.NEXT_PUBLIC_GEN3_REDIRECT_URL || GEN3_API;
|
|
32
|
+
const GEN3_WORKSPACE_STATUS_API = process.env.NEXT_PUBLIC_GEN3_WORKSPACE_STATUS_API || `${GEN3_API}/lw-workspace`;
|
|
33
|
+
const GEN3_SUBMISSION_API = process.env.NEXT_PUBLIC_GEN3_SUBMISSION_API || `${GEN3_API}/api/v0/submission`;
|
|
34
|
+
const GEN3_WTS_API = process.env.NEXT_PUBLIC_GEN3_WTS_API || `${GEN3_API}/wts`;
|
|
35
|
+
const GEN3_CROSSWALK_API = process.env.NEXT_PUBLIC_GEN3_CROSSWALK_API || `${GEN3_API}/mds`;
|
|
36
|
+
exports.Accessibility = void 0;
|
|
37
|
+
(function(Accessibility) {
|
|
38
|
+
Accessibility["ACCESSIBLE"] = "accessible";
|
|
39
|
+
Accessibility["UNACCESSIBLE"] = "unaccessible";
|
|
40
|
+
Accessibility["ALL"] = "all";
|
|
41
|
+
})(exports.Accessibility || (exports.Accessibility = {}));
|
|
42
|
+
const FILE_DELIMITERS = {
|
|
43
|
+
tsv: '\t',
|
|
44
|
+
csv: ','
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// From here down is react-related code. If we wanted to create a UI-agnotic core,
|
|
48
|
+
// we could need to move the following code and the provider into a new workspace,
|
|
49
|
+
// such as core-react.
|
|
50
|
+
/**
|
|
51
|
+
* The initial context is never used in practice. A little casting voodoo to satisfy TS.
|
|
52
|
+
*
|
|
53
|
+
* Note: Should the action type be AnyAction (from redux) or PayloadAction (from redux-toolkit)?
|
|
54
|
+
* If we are creating all of our actions through RTK, then PayloadAction might be the
|
|
55
|
+
* correct opinionated type.
|
|
56
|
+
*/ const CoreContext = React.createContext(undefined);
|
|
57
|
+
const useCoreSelector = reactRedux.createSelectorHook(CoreContext);
|
|
58
|
+
const useCoreDispatch = reactRedux.createDispatchHook(CoreContext);
|
|
59
|
+
const useCoreStore = reactRedux.createStoreHook(CoreContext);
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Creates a custom Redux Toolkit core API
|
|
63
|
+
* See: https://redux-toolkit.js.org/rtk-query/usage/customizing-create-api
|
|
64
|
+
* @returns: created core API.
|
|
65
|
+
*/ const coreCreateApi = react.buildCreateApi(react.coreModule(), react.reactHooksModule({
|
|
66
|
+
useSelector: useCoreSelector,
|
|
67
|
+
useStore: useCoreStore,
|
|
68
|
+
useDispatch: useCoreDispatch
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Template for fence error response dict
|
|
73
|
+
* @returns: An error dict response from a RESTFUL API request
|
|
74
|
+
*/ const buildFetchError = async (res, request)=>{
|
|
75
|
+
return {
|
|
76
|
+
url: res.url,
|
|
77
|
+
status: res.status,
|
|
78
|
+
statusText: res.statusText,
|
|
79
|
+
text: await res.text(),
|
|
80
|
+
request: request
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Template for a standard fence request
|
|
85
|
+
* @returns: response data
|
|
86
|
+
*/ const fetchFence = async ({ endpoint, headers, body = {}, method = 'GET', isJSON = true })=>{
|
|
87
|
+
const res = await fetch(`${GEN3_FENCE_API}${endpoint}`, {
|
|
88
|
+
method: method,
|
|
89
|
+
headers: headers,
|
|
90
|
+
body: 'POST' === method ? JSON.stringify(body) : null
|
|
91
|
+
});
|
|
92
|
+
if (res.ok) return {
|
|
93
|
+
data: isJSON ? await res.json() : await res.text(),
|
|
94
|
+
status: res.status
|
|
95
|
+
};
|
|
96
|
+
throw await buildFetchError(res, {
|
|
97
|
+
endpoint,
|
|
98
|
+
method,
|
|
99
|
+
headers,
|
|
100
|
+
body
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const userAuthApi = coreCreateApi({
|
|
105
|
+
reducerPath: 'userAuthApi',
|
|
106
|
+
refetchOnMountOrArgChange: 1800,
|
|
107
|
+
refetchOnReconnect: true,
|
|
108
|
+
baseQuery: async ({ endpoint }, { getState })=>{
|
|
109
|
+
let results;
|
|
110
|
+
const csrfToken = selectCSRFToken(getState());
|
|
111
|
+
let accessToken = undefined;
|
|
112
|
+
if (process.env.NODE_ENV === 'development') {
|
|
113
|
+
accessToken = cookiesNext.getCookie('credentials_token');
|
|
114
|
+
}
|
|
115
|
+
const headers = {
|
|
116
|
+
Accept: 'application/json',
|
|
117
|
+
'Content-Type': 'application/json',
|
|
118
|
+
...csrfToken ? {
|
|
119
|
+
'X-CSRF-Token': csrfToken
|
|
120
|
+
} : {},
|
|
121
|
+
...accessToken ? {
|
|
122
|
+
Authorization: `Bearer ${accessToken}`
|
|
123
|
+
} : {},
|
|
124
|
+
credentials: 'include'
|
|
125
|
+
};
|
|
126
|
+
try {
|
|
127
|
+
results = await fetchFence({
|
|
128
|
+
endpoint,
|
|
129
|
+
headers
|
|
130
|
+
});
|
|
131
|
+
} catch (e) {
|
|
132
|
+
/*
|
|
133
|
+
Because an "error" response is valid for the auth requests we don't want to
|
|
134
|
+
put the request in an error state, or it will attempt the request over and over again
|
|
135
|
+
*/ return {
|
|
136
|
+
data: {}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
data: results
|
|
141
|
+
};
|
|
142
|
+
},
|
|
143
|
+
endpoints: (builder)=>({
|
|
144
|
+
fetchUserDetails: builder.query({
|
|
145
|
+
query: ()=>({
|
|
146
|
+
endpoint: '/user/user'
|
|
147
|
+
}),
|
|
148
|
+
transformResponse (response) {
|
|
149
|
+
return {
|
|
150
|
+
data: response.data,
|
|
151
|
+
// TODO: check if this is the correct status code
|
|
152
|
+
loginStatus: response.status === 200 && response.data?.username ? 'authenticated' : 'unauthenticated'
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}),
|
|
156
|
+
getCSRF: builder.query({
|
|
157
|
+
query: ()=>({
|
|
158
|
+
endpoint: '/_status'
|
|
159
|
+
}),
|
|
160
|
+
transformResponse: (response)=>{
|
|
161
|
+
return {
|
|
162
|
+
csrfToken: response?.data?.csrf ?? ''
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
})
|
|
167
|
+
});
|
|
168
|
+
const EMPTY_USER = {
|
|
169
|
+
username: undefined
|
|
170
|
+
};
|
|
171
|
+
const { useFetchUserDetailsQuery, useLazyFetchUserDetailsQuery, useGetCSRFQuery } = userAuthApi;
|
|
172
|
+
const userAuthApiMiddleware = userAuthApi.middleware;
|
|
173
|
+
const userAuthApiReducerPath = userAuthApi.reducerPath;
|
|
174
|
+
const userAuthApiReducer = userAuthApi.reducer;
|
|
175
|
+
const selectUserDetailsFromState = userAuthApi.endpoints.fetchUserDetails.select();
|
|
176
|
+
const selectUserDetails = toolkit.createSelector(selectUserDetailsFromState, (userDetails)=>userDetails?.data?.data ?? EMPTY_USER);
|
|
177
|
+
const selectUserAuthStatus = toolkit.createSelector(selectUserDetailsFromState, (userLoginState)=>userLoginState.status === query.QueryStatus.pending ? 'pending' : userLoginState.status === query.QueryStatus.uninitialized ? 'not present' : userLoginState?.data?.loginStatus ?? 'unauthenticated');
|
|
178
|
+
const selectCSRFTokenData = userAuthApi.endpoints.getCSRF.select();
|
|
179
|
+
const passThroughTheState = (state)=>state.gen3Services;
|
|
180
|
+
const selectCSRFToken = toolkit.createSelector([
|
|
181
|
+
selectCSRFTokenData,
|
|
182
|
+
passThroughTheState
|
|
183
|
+
], (state)=>state?.data?.csrfToken);
|
|
184
|
+
const selectHeadersWithCSRFToken = toolkit.createSelector([
|
|
185
|
+
selectCSRFToken,
|
|
186
|
+
passThroughTheState
|
|
187
|
+
], (csrfToken)=>({
|
|
188
|
+
Accept: 'application/json',
|
|
189
|
+
'Content-Type': 'application/json',
|
|
190
|
+
...csrfToken && {
|
|
191
|
+
'X-CSRF-Token': csrfToken
|
|
192
|
+
}
|
|
193
|
+
}));
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Creates a base class core API for building other API endpoints on top of.
|
|
197
|
+
* @param reducerPath - The root key name that the other slices will be derived from
|
|
198
|
+
* @param baseQuery: - The template query which the slices will addon to
|
|
199
|
+
* @param endpoints - Base API endpoints that should exist in every slice
|
|
200
|
+
* @returns: The generated base API
|
|
201
|
+
*/ const gen3Api = coreCreateApi({
|
|
202
|
+
reducerPath: 'gen3Services',
|
|
203
|
+
baseQuery: react$1.fetchBaseQuery({
|
|
204
|
+
baseUrl: `${GEN3_API}`,
|
|
205
|
+
prepareHeaders: (headers, { getState })=>{
|
|
206
|
+
const csrfToken = selectCSRFToken(getState());
|
|
207
|
+
headers.set('Content-Type', 'application/json');
|
|
208
|
+
let accessToken = undefined;
|
|
209
|
+
if (process.env.NODE_ENV === 'development') {
|
|
210
|
+
// NOTE: This cookie can only be accessed from the client side
|
|
211
|
+
// in development mode. Otherwise, the cookie is set as httpOnly
|
|
212
|
+
accessToken = cookiesNext.getCookie('credentials_token');
|
|
213
|
+
}
|
|
214
|
+
if (csrfToken) headers.set('X-CSRF-Token', csrfToken);
|
|
215
|
+
if (accessToken) headers.set('Authorization', `Bearer ${accessToken}`);
|
|
216
|
+
return headers;
|
|
217
|
+
}
|
|
218
|
+
}),
|
|
219
|
+
endpoints: ()=>({})
|
|
220
|
+
});
|
|
221
|
+
const gen3ServicesReducer = gen3Api.reducer;
|
|
222
|
+
const gen3ServicesReducerMiddleware = gen3Api.middleware;
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Creates a fence API endpoint for handling login processes
|
|
226
|
+
* @param endpoints - defined endpoint query for logging in
|
|
227
|
+
* @returns: The generated fence login API slice
|
|
228
|
+
*/ const loginProvidersApi = gen3Api.injectEndpoints({
|
|
229
|
+
endpoints: (builder)=>({
|
|
230
|
+
getLoginProviders: builder.query({
|
|
231
|
+
query: ()=>`${GEN3_FENCE_API}/user/login`
|
|
232
|
+
})
|
|
233
|
+
})
|
|
234
|
+
});
|
|
235
|
+
const { useGetLoginProvidersQuery } = loginProvidersApi;
|
|
236
|
+
/**
|
|
237
|
+
* Logout from fence
|
|
238
|
+
*/ const logoutFence = async (redirect = '/')=>await fetchFence({
|
|
239
|
+
endpoint: `${GEN3_FENCE_API}/user/logout?next=${GEN3_REDIRECT_URL}${redirect}`,
|
|
240
|
+
method: 'GET'
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// extending the gen3API to add a tag to the endpoints
|
|
244
|
+
const credentialsWithTags$1 = gen3Api.enhanceEndpoints({
|
|
245
|
+
addTagTypes: [
|
|
246
|
+
'Credentials'
|
|
247
|
+
]
|
|
248
|
+
});
|
|
249
|
+
/**
|
|
250
|
+
* Adds a credentialsApi into the base gen3Api
|
|
251
|
+
* @endpoints Includes get, add, and remove credential operations
|
|
252
|
+
* @see https://github.com/uc-cdis/fence/blob/master/openapis/swagger.yaml#L972-L1033
|
|
253
|
+
* @param getCredentials - List all the API keys for the current user
|
|
254
|
+
* @param addNewCredential - Get a new API key for the current user
|
|
255
|
+
* @param removeCredential - Delete API access key with given ID for current user
|
|
256
|
+
* @returns: A fence credential API for manipulating user credentials
|
|
257
|
+
*/ const credentialsApi = credentialsWithTags$1.injectEndpoints({
|
|
258
|
+
endpoints: (builder)=>({
|
|
259
|
+
getCredentials: builder.query({
|
|
260
|
+
query: ()=>`${GEN3_FENCE_API}/user/credentials/api`,
|
|
261
|
+
transformResponse: (response)=>response['jtis'],
|
|
262
|
+
// "jtis", which is an array of API keys
|
|
263
|
+
// no need to transform the response, since the API returns the correct format
|
|
264
|
+
providesTags: [
|
|
265
|
+
'Credentials'
|
|
266
|
+
]
|
|
267
|
+
}),
|
|
268
|
+
addNewCredential: builder.mutation({
|
|
269
|
+
query: (csrfToken)=>({
|
|
270
|
+
url: `${GEN3_FENCE_API}/user/credentials/api`,
|
|
271
|
+
method: 'POST',
|
|
272
|
+
headers: {
|
|
273
|
+
'Content-Type': 'application/json',
|
|
274
|
+
'x-csrf-token': csrfToken
|
|
275
|
+
},
|
|
276
|
+
body: {
|
|
277
|
+
scope: [
|
|
278
|
+
'user',
|
|
279
|
+
'data'
|
|
280
|
+
]
|
|
281
|
+
}
|
|
282
|
+
}),
|
|
283
|
+
invalidatesTags: [
|
|
284
|
+
'Credentials'
|
|
285
|
+
]
|
|
286
|
+
}),
|
|
287
|
+
removeCredential: builder.mutation({
|
|
288
|
+
query: ({ csrfToken, id })=>({
|
|
289
|
+
url: `${GEN3_FENCE_API}/user/credentials/api/${id}`,
|
|
290
|
+
method: 'DELETE',
|
|
291
|
+
headers: {
|
|
292
|
+
'Content-Type': 'application/json',
|
|
293
|
+
...csrfToken && {
|
|
294
|
+
'x-csrf-token': csrfToken
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}),
|
|
298
|
+
invalidatesTags: [
|
|
299
|
+
'Credentials'
|
|
300
|
+
]
|
|
301
|
+
}),
|
|
302
|
+
authorizeFromCredentials: builder.mutation({
|
|
303
|
+
query: (params)=>({
|
|
304
|
+
url: `${GEN3_FENCE_API}/user/credentials/api/access_token`,
|
|
305
|
+
method: 'POST',
|
|
306
|
+
headers: {
|
|
307
|
+
'Content-Type': 'application/json'
|
|
308
|
+
},
|
|
309
|
+
body: {
|
|
310
|
+
api_key: params.api_key,
|
|
311
|
+
key_id: params.key_id
|
|
312
|
+
}
|
|
313
|
+
})
|
|
314
|
+
})
|
|
315
|
+
})
|
|
316
|
+
});
|
|
317
|
+
const { useGetCredentialsQuery, useAddNewCredentialMutation, useRemoveCredentialMutation, useAuthorizeFromCredentialsMutation } = credentialsApi;
|
|
318
|
+
|
|
319
|
+
// extending the gen3API to add a tag to the endpoints
|
|
320
|
+
const credentialsWithTags = gen3Api.enhanceEndpoints({
|
|
321
|
+
addTagTypes: [
|
|
322
|
+
'fenceJWT'
|
|
323
|
+
]
|
|
324
|
+
});
|
|
325
|
+
/**
|
|
326
|
+
* A fence API for getting the public keys which can be used to validate
|
|
327
|
+
* JWTs issued and signed by fence
|
|
328
|
+
* @returns: Fence public keys
|
|
329
|
+
*/ const jwtApi = credentialsWithTags.injectEndpoints({
|
|
330
|
+
endpoints: (builder)=>({
|
|
331
|
+
getJWKKeys: builder.query({
|
|
332
|
+
query: ()=>'user/jwt/keys',
|
|
333
|
+
providesTags: [
|
|
334
|
+
'fenceJWT'
|
|
335
|
+
]
|
|
336
|
+
})
|
|
337
|
+
})
|
|
338
|
+
});
|
|
339
|
+
const { useGetJWKKeysQuery } = jwtApi;
|
|
340
|
+
|
|
341
|
+
const usePrevious = (value)=>{
|
|
342
|
+
const ref = React.useRef();
|
|
343
|
+
React.useEffect(()=>{
|
|
344
|
+
ref.current = value;
|
|
345
|
+
});
|
|
346
|
+
return ref.current;
|
|
347
|
+
};
|
|
348
|
+
const createUseCoreDataHook = (fetchDataActionCreator, dataSelector)=>{
|
|
349
|
+
return (...params)=>{
|
|
350
|
+
const coreDispatch = useCoreDispatch();
|
|
351
|
+
const { data, status, error } = useCoreSelector(dataSelector);
|
|
352
|
+
const action = fetchDataActionCreator(...params);
|
|
353
|
+
const prevParams = usePrevious(params);
|
|
354
|
+
React.useEffect(()=>{
|
|
355
|
+
if (status === 'uninitialized' || !lodash.isEqual(prevParams, params)) {
|
|
356
|
+
// createDispatchHook types forces the input to AnyAction, which is
|
|
357
|
+
// not compatible with thunk actions. hence, the `as any` cast. ;(
|
|
358
|
+
coreDispatch(action); // eslint-disable-line
|
|
359
|
+
}
|
|
360
|
+
}, [
|
|
361
|
+
status,
|
|
362
|
+
coreDispatch,
|
|
363
|
+
action,
|
|
364
|
+
params,
|
|
365
|
+
prevParams
|
|
366
|
+
]);
|
|
367
|
+
return {
|
|
368
|
+
data,
|
|
369
|
+
error,
|
|
370
|
+
isUninitialized: status === 'uninitialized',
|
|
371
|
+
isFetching: status === 'pending',
|
|
372
|
+
isSuccess: status === 'fulfilled',
|
|
373
|
+
isError: status === 'rejected'
|
|
374
|
+
};
|
|
375
|
+
};
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Creates an async thunk for fetching user permissions details from fence
|
|
380
|
+
* @see https://redux-toolkit.js.org/api/createAsyncThunk
|
|
381
|
+
* @returns: A fence response dict containing user details
|
|
382
|
+
*/ const fetchUserState = toolkit.createAsyncThunk('fence/user/user', async (_, meta)=>{
|
|
383
|
+
// Get an access token from a cookie if in development mode
|
|
384
|
+
const csrfToken = selectCSRFToken(meta.getState());
|
|
385
|
+
let accessToken = undefined;
|
|
386
|
+
if (process.env.NODE_ENV === 'development') {
|
|
387
|
+
accessToken = cookiesNext.getCookie('credentials_token');
|
|
388
|
+
}
|
|
389
|
+
return await fetchFence({
|
|
390
|
+
endpoint: '/user/user',
|
|
391
|
+
method: 'GET',
|
|
392
|
+
headers: {
|
|
393
|
+
Accept: 'application/json',
|
|
394
|
+
'Content-Type': 'application/json',
|
|
395
|
+
...csrfToken ? {
|
|
396
|
+
'X-CSRF-Token': csrfToken
|
|
397
|
+
} : {},
|
|
398
|
+
credentials: 'include',
|
|
399
|
+
...accessToken ? {
|
|
400
|
+
Authorization: `Bearer ${accessToken}`
|
|
401
|
+
} : {}
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
const isAuthenticated = (loginStatus)=>loginStatus === 'authenticated';
|
|
406
|
+
const isPending = (loginStatus)=>loginStatus === 'pending';
|
|
407
|
+
const initialState$3 = {
|
|
408
|
+
status: 'uninitialized',
|
|
409
|
+
loginStatus: 'unauthenticated',
|
|
410
|
+
error: undefined
|
|
411
|
+
};
|
|
412
|
+
/**
|
|
413
|
+
* Wraps a slice on top of fetchUserState async thunk to keep track of
|
|
414
|
+
* query state. authenticated/not-authenticated vs. ejected/fulfilled/pending
|
|
415
|
+
* @returns: status messages wrapped around fetchUserState response dict
|
|
416
|
+
*/ const slice$3 = toolkit.createSlice({
|
|
417
|
+
name: 'fence/user',
|
|
418
|
+
initialState: initialState$3,
|
|
419
|
+
reducers: {
|
|
420
|
+
resetUserState: ()=>initialState$3
|
|
421
|
+
},
|
|
422
|
+
extraReducers: (builder)=>{
|
|
423
|
+
builder.addCase(fetchUserState.fulfilled, (_, action)=>{
|
|
424
|
+
const response = action.payload;
|
|
425
|
+
if (response.status !== 200) {
|
|
426
|
+
return {
|
|
427
|
+
status: 'rejected',
|
|
428
|
+
loginStatus: 'unauthenticated'
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
return {
|
|
432
|
+
data: {
|
|
433
|
+
...response.data
|
|
434
|
+
},
|
|
435
|
+
status: 'fulfilled',
|
|
436
|
+
loginStatus: 'authenticated'
|
|
437
|
+
};
|
|
438
|
+
}).addCase(fetchUserState.pending, ()=>{
|
|
439
|
+
return {
|
|
440
|
+
status: 'pending',
|
|
441
|
+
loginStatus: 'unauthenticated'
|
|
442
|
+
};
|
|
443
|
+
}).addCase(fetchUserState.rejected, ()=>{
|
|
444
|
+
return {
|
|
445
|
+
status: 'rejected',
|
|
446
|
+
loginStatus: 'unauthenticated'
|
|
447
|
+
};
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
const userReducer = slice$3.reducer;
|
|
452
|
+
const { resetUserState } = slice$3.actions;
|
|
453
|
+
const selectUserData = (state)=>{
|
|
454
|
+
return state.user;
|
|
455
|
+
};
|
|
456
|
+
const selectUser = (state)=>state.user;
|
|
457
|
+
const selectUserLoginStatus = (state)=>state.user.loginStatus;
|
|
458
|
+
const useUser = createUseCoreDataHook(fetchUserState, selectUserData);
|
|
459
|
+
const useIsUserLoggedIn = ()=>{
|
|
460
|
+
return useCoreSelector((state)=>isAuthenticated(selectUserLoginStatus(state)));
|
|
461
|
+
};
|
|
462
|
+
/**
|
|
463
|
+
* Hook to return get the authenticated state of the user and if logged in,
|
|
464
|
+
* the user's profile and access api.
|
|
465
|
+
* Note that if fetchUserState gets called, the user's session is renewed.
|
|
466
|
+
*/ const useUserAuth = (renew = false)=>{
|
|
467
|
+
const coreDispatch = useCoreDispatch();
|
|
468
|
+
const { data, status, loginStatus, error } = useCoreSelector(selectUserData);
|
|
469
|
+
React.useEffect(()=>{
|
|
470
|
+
if (status === 'uninitialized' || renew) {
|
|
471
|
+
// TODO: need to determine what other states require dispatch
|
|
472
|
+
coreDispatch(fetchUserState());
|
|
473
|
+
}
|
|
474
|
+
}, [
|
|
475
|
+
status,
|
|
476
|
+
coreDispatch,
|
|
477
|
+
renew
|
|
478
|
+
]);
|
|
479
|
+
return {
|
|
480
|
+
data: data,
|
|
481
|
+
error,
|
|
482
|
+
loginStatus,
|
|
483
|
+
isUninitialized: status === 'uninitialized',
|
|
484
|
+
isFetching: status === 'pending',
|
|
485
|
+
isSuccess: status === 'fulfilled',
|
|
486
|
+
isError: status === 'rejected'
|
|
487
|
+
};
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* @description Creates a externalLoginApi for listing the configured identity providers
|
|
492
|
+
* in workspace token service. Includes user token expiration time.
|
|
493
|
+
* @see https://github.com/uc-cdis/workspace-token-service/tree/master
|
|
494
|
+
*/ const externalLoginApi = gen3Api.injectEndpoints({
|
|
495
|
+
endpoints: (builder)=>({
|
|
496
|
+
getExternalLogins: builder.query({
|
|
497
|
+
query: ()=>({
|
|
498
|
+
url: `${GEN3_WTS_API}/external_oidc/`
|
|
499
|
+
})
|
|
500
|
+
}),
|
|
501
|
+
isExternalConnected: builder.query({
|
|
502
|
+
query: (idp)=>({
|
|
503
|
+
url: `${GEN3_WTS_API}/oauth2/connected?idp=${idp}`
|
|
504
|
+
}),
|
|
505
|
+
transformResponse: ()=>{
|
|
506
|
+
return true; // if success then connected is true
|
|
507
|
+
}
|
|
508
|
+
})
|
|
509
|
+
})
|
|
510
|
+
});
|
|
511
|
+
const { useGetExternalLoginsQuery, useLazyGetExternalLoginsQuery, useLazyIsExternalConnectedQuery, useIsExternalConnectedQuery } = externalLoginApi;
|
|
512
|
+
|
|
513
|
+
// type guard functions
|
|
514
|
+
const isHistogramRangeData = (key)=>{
|
|
515
|
+
return Array.isArray(key) && key.length === 2 && key.every((item)=>typeof item === 'number');
|
|
516
|
+
};
|
|
517
|
+
const isJSONObject = (data)=>{
|
|
518
|
+
return typeof data === 'object' && data !== null && !Array.isArray(data);
|
|
519
|
+
};
|
|
520
|
+
const isJSONValue = (data)=>{
|
|
521
|
+
return typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean' || Array.isArray(data) && data.every(isJSONValue) || isJSONObject(data);
|
|
522
|
+
};
|
|
523
|
+
const isJSONValueArray = (data)=>{
|
|
524
|
+
return Array.isArray(data) && data.every(isJSONValue);
|
|
525
|
+
};
|
|
526
|
+
const isValidObject = (input)=>typeof input === 'object' && input !== null;
|
|
527
|
+
const isHistogramData = (data)=>{
|
|
528
|
+
return isValidObject(data) && 'key' in data && 'count' in data;
|
|
529
|
+
};
|
|
530
|
+
const isHistogramDataArray = (input)=>{
|
|
531
|
+
if (!isValidObject(input) || !Array.isArray(input.histogram)) {
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
return input.histogram.every(isHistogramData);
|
|
535
|
+
};
|
|
536
|
+
const isHistogramDataCollection = (obj)=>{
|
|
537
|
+
return isValidObject(obj) && 'histogram' in obj && isHistogramData(obj.histogram);
|
|
538
|
+
};
|
|
539
|
+
// Type guard function for GuppyAggregationData interface
|
|
540
|
+
const isGuppyAggregationData = (obj)=>{
|
|
541
|
+
if (!isValidObject(obj)) return false;
|
|
542
|
+
for(const key in obj){
|
|
543
|
+
if (!isHistogramDataCollection(obj[key])) {
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return true;
|
|
548
|
+
};
|
|
549
|
+
const isHistogramDataAnEnum = (data)=>{
|
|
550
|
+
return typeof data === 'object' && data !== null && 'key' in data && 'count' in data && typeof data.key === 'string' && typeof data.count === 'number';
|
|
551
|
+
};
|
|
552
|
+
const isHistogramDataAArray = (data)=>{
|
|
553
|
+
return Array.isArray(data) && data.every(isHistogramData);
|
|
554
|
+
};
|
|
555
|
+
const isHistogramDataArrayAnEnum = (data)=>{
|
|
556
|
+
return Array.isArray(data) && data.every(isHistogramDataAnEnum);
|
|
557
|
+
};
|
|
558
|
+
const isHistogramDataArrayARange = (data)=>{
|
|
559
|
+
return Array.isArray(data) && data.every((item)=>isHistogramRangeData(item.key));
|
|
560
|
+
};
|
|
561
|
+
/**
|
|
562
|
+
* Type predicate to narrow an unknown error to `FetchBaseQueryError`
|
|
563
|
+
*/ function isFetchBaseQueryError(error) {
|
|
564
|
+
return typeof error === 'object' && error != null && 'status' in error;
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Type predicate to narrow an unknown error to an object with a string 'message' property
|
|
568
|
+
*/ function isErrorWithMessage(error) {
|
|
569
|
+
return typeof error === 'object' && error != null && 'message' in error && typeof error.message === 'string';
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Type predicate to narrow an unknown error to an object with a string 'message' property
|
|
573
|
+
*/ function isFetchParseError(error) {
|
|
574
|
+
return typeof error === 'object' && error != null && 'originalStatus' in error && 'status' in error && error['status'] === 'PARSING_ERROR';
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* A registry for the Gen3 Apps.
|
|
579
|
+
*/ const REGISTRY = {};
|
|
580
|
+
const registerGen3App = (id, gen3App)=>{
|
|
581
|
+
REGISTRY[id] = gen3App;
|
|
582
|
+
};
|
|
583
|
+
const lookupGen3App = (id)=>{
|
|
584
|
+
return REGISTRY[id];
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
const initialState$2 = {
|
|
588
|
+
gen3Apps: {}
|
|
589
|
+
};
|
|
590
|
+
const slice$2 = toolkit.createSlice({
|
|
591
|
+
name: 'gen3Apps',
|
|
592
|
+
initialState: initialState$2,
|
|
593
|
+
reducers: {
|
|
594
|
+
addGen3AppMetadata: (state, action)=>{
|
|
595
|
+
const { id, requiredEntityTypes } = action.payload;
|
|
596
|
+
state.gen3Apps[id] = {
|
|
597
|
+
...action.payload,
|
|
598
|
+
// need to turn a ReadonlyArray into a mutable array for immer's WritableDraft
|
|
599
|
+
requiredEntityTypes: [
|
|
600
|
+
...requiredEntityTypes
|
|
601
|
+
]
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
const gen3AppReducer = slice$2.reducer;
|
|
607
|
+
const { addGen3AppMetadata } = slice$2.actions;
|
|
608
|
+
const selectGen3AppMetadataById = (state, appId)=>state.gen3Apps.gen3Apps[appId];
|
|
609
|
+
const selectGen3AppById = (appId)=>lookupGen3App(appId); // TODO: memoize this selector
|
|
610
|
+
|
|
611
|
+
const initialState$1 = {};
|
|
612
|
+
// TODO: document what this does
|
|
613
|
+
const slice$1 = toolkit.createSlice({
|
|
614
|
+
name: 'drsResolver',
|
|
615
|
+
initialState: initialState$1,
|
|
616
|
+
reducers: {
|
|
617
|
+
setDRSHostnames: (_state, action)=>{
|
|
618
|
+
return action.payload;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
const drsHostnamesReducer = slice$1.reducer;
|
|
623
|
+
const { setDRSHostnames } = slice$1.actions;
|
|
624
|
+
|
|
625
|
+
exports.Modals = void 0;
|
|
626
|
+
(function(Modals) {
|
|
627
|
+
Modals["FirstTimeModal"] = "FirstTimeModal";
|
|
628
|
+
Modals["SessionExpireModal"] = "SessionExpireModal";
|
|
629
|
+
Modals["NoAccessModal"] = "NoAccessModal";
|
|
630
|
+
Modals["CreateCredentialsAPIKeyModal"] = "CreateCredentialsAPIKeyModal";
|
|
631
|
+
Modals["GeneralErrorModal"] = "GeneralErrorModal";
|
|
632
|
+
})(exports.Modals || (exports.Modals = {}));
|
|
633
|
+
const initialState = {
|
|
634
|
+
currentModal: null
|
|
635
|
+
};
|
|
636
|
+
//Creates a modal slice for tracking showModal and hideModal state.
|
|
637
|
+
const slice = toolkit.createSlice({
|
|
638
|
+
name: 'modals',
|
|
639
|
+
initialState,
|
|
640
|
+
reducers: {
|
|
641
|
+
showModal: (state, action)=>{
|
|
642
|
+
state.currentModal = action.payload.modal;
|
|
643
|
+
state.message = action.payload.message;
|
|
644
|
+
return state;
|
|
645
|
+
},
|
|
646
|
+
hideModal: (state)=>{
|
|
647
|
+
state.currentModal = null;
|
|
648
|
+
return state;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
const modalReducer = slice.reducer;
|
|
653
|
+
const { showModal, hideModal } = slice.actions;
|
|
654
|
+
const selectCurrentModal = (state)=>state.modals.currentModal;
|
|
655
|
+
const selectCurrentMessage = (state)=>state.modals.message;
|
|
656
|
+
|
|
657
|
+
const initialCohortState = {
|
|
658
|
+
cohort: {
|
|
659
|
+
id: 'default',
|
|
660
|
+
name: 'Filters',
|
|
661
|
+
filters: {},
|
|
662
|
+
modified_datetime: new Date().toISOString()
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
// TODO: start using this adapter
|
|
666
|
+
/*
|
|
667
|
+
const cohortsAdapter = createEntityAdapter<Cohort>({
|
|
668
|
+
sortComparer: (a, b) => {
|
|
669
|
+
if (a.modified_datetime <= b.modified_datetime) return 1;
|
|
670
|
+
else return -1;
|
|
671
|
+
},
|
|
672
|
+
});
|
|
673
|
+
*/ /**
|
|
674
|
+
* Redux slice for cohort filters
|
|
675
|
+
*/ const cohortSlice = toolkit.createSlice({
|
|
676
|
+
name: 'cohort',
|
|
677
|
+
initialState: initialCohortState,
|
|
678
|
+
reducers: {
|
|
679
|
+
// adds a filter to the cohort filter set at the given index
|
|
680
|
+
updateCohortFilter: (state, action)=>{
|
|
681
|
+
const { index, field, filter } = action.payload;
|
|
682
|
+
return {
|
|
683
|
+
cohort: {
|
|
684
|
+
...state.cohort,
|
|
685
|
+
filters: {
|
|
686
|
+
...state.cohort.filters,
|
|
687
|
+
[index]: {
|
|
688
|
+
mode: state.cohort.filters?.[index]?.mode ?? 'and',
|
|
689
|
+
root: {
|
|
690
|
+
...state.cohort.filters?.[index]?.root ?? {},
|
|
691
|
+
[field]: filter
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
},
|
|
698
|
+
// removes a filter to the cohort filter set at the given index
|
|
699
|
+
removeCohortFilter: (state, action)=>{
|
|
700
|
+
const { index, field } = action.payload;
|
|
701
|
+
const filters = state.cohort?.filters?.[index]?.root;
|
|
702
|
+
if (!filters) {
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
const { [field]: _a, ...updated } = filters;
|
|
706
|
+
return {
|
|
707
|
+
cohort: {
|
|
708
|
+
...state.cohort,
|
|
709
|
+
filters: {
|
|
710
|
+
...state.cohort.filters,
|
|
711
|
+
[index]: {
|
|
712
|
+
mode: state.cohort.filters[index].mode,
|
|
713
|
+
root: updated
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
},
|
|
719
|
+
// removes all filters from the cohort filter set at the given index
|
|
720
|
+
clearCohortFilters: (state, action)=>{
|
|
721
|
+
const { index } = action.payload;
|
|
722
|
+
return {
|
|
723
|
+
cohort: {
|
|
724
|
+
...state.cohort,
|
|
725
|
+
filters: {
|
|
726
|
+
...state.cohort.filters,
|
|
727
|
+
[index]: {
|
|
728
|
+
// empty filter set
|
|
729
|
+
mode: 'and',
|
|
730
|
+
root: {}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
},
|
|
737
|
+
extraReducers: {}
|
|
738
|
+
});
|
|
739
|
+
// Filter actions: addFilter, removeFilter, updateFilter
|
|
740
|
+
const { updateCohortFilter, removeCohortFilter, clearCohortFilters } = cohortSlice.actions;
|
|
741
|
+
const selectCohortFilters = (state)=>state.cohorts.cohort.filters;
|
|
742
|
+
const selectCurrentCohortId = (state)=>state.cohorts.cohort.id;
|
|
743
|
+
const selectCurrentCohort = (state)=>state.cohorts.cohort;
|
|
744
|
+
const selectCurrentCohortName = (state)=>state.cohorts.cohort.name;
|
|
745
|
+
/**
|
|
746
|
+
* Select a filter by its name from the current cohort. If the filter is not found
|
|
747
|
+
* returns undefined.
|
|
748
|
+
* @param state - Core
|
|
749
|
+
* @param index which cohort index to select from
|
|
750
|
+
* @param name name of the filter to select
|
|
751
|
+
*/ const selectIndexedFilterByName = (state, index, name)=>{
|
|
752
|
+
return state.cohorts.cohort.filters[index]?.root[name];
|
|
753
|
+
};
|
|
754
|
+
const EmptyFilterSet = {
|
|
755
|
+
mode: 'and',
|
|
756
|
+
root: {}
|
|
757
|
+
};
|
|
758
|
+
/**
|
|
759
|
+
* Select a filter from the index.
|
|
760
|
+
* returns undefined.
|
|
761
|
+
* @param state - Core
|
|
762
|
+
* @param index which cohort index to select from
|
|
763
|
+
*/ const selectIndexFilters = (state, index)=>{
|
|
764
|
+
return state.cohorts.cohort.filters?.[index] ?? EmptyFilterSet; // TODO: check if this is undefined
|
|
765
|
+
};
|
|
766
|
+
const cohortReducer = cohortSlice.reducer;
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Creates a base class core API for guppy API calls.
|
|
770
|
+
* @returns: guppy core API with guppyAPIFetch base query
|
|
771
|
+
*/ const guppyApi = coreCreateApi({
|
|
772
|
+
reducerPath: 'guppy',
|
|
773
|
+
// TODO: refactor to use fetchBaseQuery
|
|
774
|
+
baseQuery: async (query, api)=>{
|
|
775
|
+
const csrfToken = selectCSRFToken(api.getState());
|
|
776
|
+
let accessToken = undefined;
|
|
777
|
+
if (process.env.NODE_ENV === 'development') {
|
|
778
|
+
// NOTE: This cookie can only be accessed from the client side
|
|
779
|
+
// in development mode. Otherwise, the cookie is set as httpOnly
|
|
780
|
+
accessToken = cookiesNext.getCookie('credentials_token');
|
|
781
|
+
}
|
|
782
|
+
const headers = {
|
|
783
|
+
Accept: 'application/json',
|
|
784
|
+
'Content-Type': 'application/json',
|
|
785
|
+
'Access-Control-Allow-Origin': '*',
|
|
786
|
+
...csrfToken && {
|
|
787
|
+
'X-CSRF-Token': csrfToken
|
|
788
|
+
},
|
|
789
|
+
...accessToken && {
|
|
790
|
+
Authorization: `Bearer ${accessToken}`
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
try {
|
|
794
|
+
const response = await fetch(`${GEN3_GUPPY_API}/graphql`, {
|
|
795
|
+
headers: headers,
|
|
796
|
+
method: 'POST',
|
|
797
|
+
body: JSON.stringify(query)
|
|
798
|
+
});
|
|
799
|
+
return {
|
|
800
|
+
data: await response.json()
|
|
801
|
+
};
|
|
802
|
+
} catch (e) {
|
|
803
|
+
if (e instanceof Error) return {
|
|
804
|
+
error: e.message
|
|
805
|
+
};
|
|
806
|
+
return {
|
|
807
|
+
error: e
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
},
|
|
811
|
+
endpoints: ()=>({})
|
|
812
|
+
});
|
|
813
|
+
const guppyAPISliceMiddleware = guppyApi.middleware;
|
|
814
|
+
const guppyApiSliceReducerPath = guppyApi.reducerPath;
|
|
815
|
+
const guppyApiReducer = guppyApi.reducer;
|
|
816
|
+
|
|
817
|
+
const rootReducer = toolkit.combineReducers({
|
|
818
|
+
gen3Services: gen3ServicesReducer,
|
|
819
|
+
user: userReducer,
|
|
820
|
+
gen3Apps: gen3AppReducer,
|
|
821
|
+
drsHostnames: drsHostnamesReducer,
|
|
822
|
+
modals: modalReducer,
|
|
823
|
+
cohorts: cohortReducer,
|
|
824
|
+
[guppyApiSliceReducerPath]: guppyApiReducer,
|
|
825
|
+
[userAuthApiReducerPath]: userAuthApiReducer
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
const coreStore = toolkit.configureStore({
|
|
829
|
+
reducer: rootReducer,
|
|
830
|
+
middleware: (getDefaultMiddleware)=>getDefaultMiddleware().concat(gen3ServicesReducerMiddleware, guppyAPISliceMiddleware, userAuthApiMiddleware)
|
|
831
|
+
});
|
|
832
|
+
query.setupListeners(coreStore.dispatch);
|
|
833
|
+
|
|
834
|
+
const CoreProvider = ({ children })=>{
|
|
835
|
+
return /*#__PURE__*/ React.createElement(reactRedux.Provider, {
|
|
836
|
+
store: coreStore,
|
|
837
|
+
context: CoreContext
|
|
838
|
+
}, children);
|
|
839
|
+
};
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* Creates the authzApi for checking arborist permissions for a selected user
|
|
843
|
+
* @see https://petstore.swagger.io/?url=https://raw.githubusercontent.com/uc-cdis/arborist/master/docs/openapi.yaml#/auth/get_auth_mapping
|
|
844
|
+
* @see https://github.com/uc-cdis/arborist/blob/master/docs/relationships.simplified.png
|
|
845
|
+
* @returns: An arborist response dict of user permissions {method, service} for each resource path.
|
|
846
|
+
*/ const authzApi = gen3Api.injectEndpoints({
|
|
847
|
+
endpoints: (builder)=>({
|
|
848
|
+
getAuthzMappings: builder.query({
|
|
849
|
+
query: ()=>`${GEN3_AUTHZ_API}/mapping`
|
|
850
|
+
})
|
|
851
|
+
})
|
|
852
|
+
});
|
|
853
|
+
const { useGetAuthzMappingsQuery } = authzApi;
|
|
854
|
+
const selectAuthzMapping = authzApi.endpoints.getAuthzMappings.select();
|
|
855
|
+
const selectAuthzMappingData = toolkit.createSelector(selectAuthzMapping, (authzMapping)=>authzMapping?.data ?? {
|
|
856
|
+
mappings: []
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
const HasEnoughData = (data, keys, limit)=>{
|
|
860
|
+
const numEmptyKeys = keys.filter((k)=>Object.hasOwn(data, k) && typeof data[k] === 'string' && data[k].trim() === '').length;
|
|
861
|
+
return numEmptyKeys < limit;
|
|
862
|
+
};
|
|
863
|
+
/**
|
|
864
|
+
* Defines metadataApi service using a base URL and expected endpoints. Derived from gen3Api core API.
|
|
865
|
+
*
|
|
866
|
+
* @param endpoints - Defines endpoints used in discovery page
|
|
867
|
+
* @param getAggMDS - Queries aggregate metadata service
|
|
868
|
+
* @see https://github.com/uc-cdis/metadata-service/blob/master/docs/agg_mds.md
|
|
869
|
+
* @see https://petstore.swagger.io/?url=https://raw.githubusercontent.com/uc-cdis/metadata-service/master/docs/openapi.yaml#/Aggregate/get_aggregate_metadata_aggregate_metadata_get
|
|
870
|
+
* @param getMDS - Queries normal metadata service
|
|
871
|
+
* @see https://petstore.swagger.io/?url=https://raw.githubusercontent.com/uc-cdis/metadata-service/master/docs/openapi.yaml#/Query/search_metadata_metadata_get
|
|
872
|
+
* @param getIndexAggMDS - queries the Aggregate Metadata service and returns all common passed in indexKeys
|
|
873
|
+
* @param getTags - Probably refering to Aggregate metadata service summary statistics query
|
|
874
|
+
* @see https://petstore.swagger.io/?url=https://raw.githubusercontent.com/uc-cdis/metadata-service/master/docs/openapi.yaml#/Aggregate/get_aggregate_tags_aggregate_tags_get
|
|
875
|
+
* @param getData - Looks like a duplicate of getMDS handler. unused in ./frontend package
|
|
876
|
+
* @param getCrosswalkData - Maps ids from one source to another
|
|
877
|
+
* @returns: A guppy download API for fetching bulk metadata
|
|
878
|
+
*/ const metadataApi = gen3Api.injectEndpoints({
|
|
879
|
+
endpoints: (builder)=>({
|
|
880
|
+
getAggMDS: builder.query({
|
|
881
|
+
query: ({ offset, pageSize })=>{
|
|
882
|
+
return `${GEN3_MDS_API}/aggregate/metadata?flatten=true&pagination=true&offset=${offset}&limit=${pageSize}`;
|
|
883
|
+
},
|
|
884
|
+
transformResponse: (response, _meta, params)=>{
|
|
885
|
+
return {
|
|
886
|
+
data: response.results.map((x)=>{
|
|
887
|
+
const objValues = Object.values(x);
|
|
888
|
+
const firstValue = objValues ? objValues.at(0) : undefined;
|
|
889
|
+
return firstValue ? firstValue[params.studyField] : undefined;
|
|
890
|
+
}),
|
|
891
|
+
hits: response.pagination.hits
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
}),
|
|
895
|
+
getIndexAggMDS: builder.query({
|
|
896
|
+
query: ({ pageSize })=>{
|
|
897
|
+
return `${GEN3_MDS_API}/aggregate/metadata?limit=${pageSize}`;
|
|
898
|
+
},
|
|
899
|
+
transformResponse: (response, _meta, params)=>{
|
|
900
|
+
const dataFromIndexes = params.indexKeys.reduce((acc, key)=>{
|
|
901
|
+
if (response[key]) {
|
|
902
|
+
acc.push(...response[key]);
|
|
903
|
+
}
|
|
904
|
+
return acc;
|
|
905
|
+
}, []);
|
|
906
|
+
return {
|
|
907
|
+
data: dataFromIndexes.map((x)=>{
|
|
908
|
+
const objValues = Object.values(x);
|
|
909
|
+
const objIds = Object.keys(x);
|
|
910
|
+
let firstValue = objValues ? objValues.at(0) : undefined;
|
|
911
|
+
if (params?.filterEmpty) {
|
|
912
|
+
// remove any data that has < limit if defined
|
|
913
|
+
if (firstValue && !HasEnoughData(firstValue[params.studyField], params.filterEmpty.keys, params.filterEmpty.limit)) firstValue = undefined;
|
|
914
|
+
}
|
|
915
|
+
return firstValue ? {
|
|
916
|
+
gen3MDSGUID: objIds.at(0),
|
|
917
|
+
...firstValue[params.studyField]
|
|
918
|
+
} : undefined;
|
|
919
|
+
}).filter((x)=>x !== undefined),
|
|
920
|
+
hits: dataFromIndexes.length
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
}),
|
|
924
|
+
getMDS: builder.query({
|
|
925
|
+
query: ({ guidType, offset, pageSize })=>{
|
|
926
|
+
return `${GEN3_MDS_API}/metadata?data=True&_guid_type=${guidType}&limit=${pageSize}&offset=${offset}`;
|
|
927
|
+
},
|
|
928
|
+
transformResponse: (response, _meta)=>{
|
|
929
|
+
return {
|
|
930
|
+
data: Object.keys(response).map((guid)=>response[guid]),
|
|
931
|
+
hits: Object.keys(response).length
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
}),
|
|
935
|
+
getTags: builder.query({
|
|
936
|
+
query: ()=>'tags'
|
|
937
|
+
}),
|
|
938
|
+
getData: builder.query({
|
|
939
|
+
query: (params)=>({
|
|
940
|
+
url: `metadata?${params}`
|
|
941
|
+
})
|
|
942
|
+
}),
|
|
943
|
+
// TODO: Move this to own slice
|
|
944
|
+
getCrosswalkData: builder.query({
|
|
945
|
+
queryFn: async (arg, _queryApi, _extraOptions, fetchWithBQ)=>{
|
|
946
|
+
const queryMultiple = async ()=>{
|
|
947
|
+
let result = [];
|
|
948
|
+
const queue = Queue({
|
|
949
|
+
concurrency: 15
|
|
950
|
+
});
|
|
951
|
+
for (const id of arg.ids){
|
|
952
|
+
queue.push(async (callback)=>{
|
|
953
|
+
const response = await fetchWithBQ({
|
|
954
|
+
url: `${GEN3_CROSSWALK_API}/metadata/${id}`
|
|
955
|
+
});
|
|
956
|
+
if (response.error) {
|
|
957
|
+
return {
|
|
958
|
+
error: response.error
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
const toData = arg.toPaths.reduce((acc, path)=>{
|
|
962
|
+
acc[path.id] = jsonpathPlus.JSONPath({
|
|
963
|
+
json: response.data,
|
|
964
|
+
path: `$.[${path.dataPath}]`,
|
|
965
|
+
resultType: 'value'
|
|
966
|
+
})?.[0] ?? 'n/a';
|
|
967
|
+
return acc;
|
|
968
|
+
}, {});
|
|
969
|
+
result = [
|
|
970
|
+
...result,
|
|
971
|
+
{
|
|
972
|
+
from: id,
|
|
973
|
+
to: toData
|
|
974
|
+
}
|
|
975
|
+
];
|
|
976
|
+
callback && callback();
|
|
977
|
+
return result;
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
return new Promise((resolve, reject)=>{
|
|
981
|
+
queue.start((err)=>{
|
|
982
|
+
if (err) {
|
|
983
|
+
reject(err);
|
|
984
|
+
} else {
|
|
985
|
+
resolve(result);
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
});
|
|
989
|
+
};
|
|
990
|
+
const result = await queryMultiple();
|
|
991
|
+
return {
|
|
992
|
+
data: result
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
})
|
|
996
|
+
})
|
|
997
|
+
});
|
|
998
|
+
const { useGetAggMDSQuery, useGetMDSQuery, useGetTagsQuery, useGetDataQuery, useGetCrosswalkDataQuery, useLazyGetCrosswalkDataQuery, useGetIndexAggMDSQuery } = metadataApi;
|
|
999
|
+
|
|
1000
|
+
// using a random uuid v4 as the namespace
|
|
1001
|
+
const GEN3_APP_NAMESPACE = '7bfaa818-c69c-457e-8d87-413cf60c25f0';
|
|
1002
|
+
const getGen3AppId = (name, version)=>{
|
|
1003
|
+
const nameVersion = `${name}::${version}`;
|
|
1004
|
+
return uuid.v5(nameVersion, GEN3_APP_NAMESPACE);
|
|
1005
|
+
};
|
|
1006
|
+
/**
|
|
1007
|
+
* TODO: can't tell what anything in this directory is doing.
|
|
1008
|
+
*/ const createGen3App = ({ App, name, version, requiredEntityTypes })=>{
|
|
1009
|
+
// create a stable id for this app
|
|
1010
|
+
const nameVersion = `${name}::${version}`;
|
|
1011
|
+
const id = uuid.v5(nameVersion, GEN3_APP_NAMESPACE);
|
|
1012
|
+
// need to create store and provider.
|
|
1013
|
+
// return a component representing this app
|
|
1014
|
+
// if component gets added to a list, then the list can be iterated in index.js and each provider component can be added
|
|
1015
|
+
// a route can be setup for the app
|
|
1016
|
+
// need to register its name, category, path, data requirements
|
|
1017
|
+
// this will be used to build page3
|
|
1018
|
+
// click app link
|
|
1019
|
+
const store = toolkit.configureStore({
|
|
1020
|
+
// TODO allow user to pass in a reducer in CreateGen3AppOptions?
|
|
1021
|
+
reducer: (state)=>state,
|
|
1022
|
+
devTools: {
|
|
1023
|
+
name: `${nameVersion}::${id}`
|
|
1024
|
+
}
|
|
1025
|
+
});
|
|
1026
|
+
const Gen3AppWrapper = ()=>{
|
|
1027
|
+
React.useEffect(()=>{
|
|
1028
|
+
document.title = `GEN3 - ${name}`;
|
|
1029
|
+
});
|
|
1030
|
+
return /*#__PURE__*/ React.createElement(reactRedux.Provider, {
|
|
1031
|
+
store: store
|
|
1032
|
+
}, /*#__PURE__*/ React.createElement(reactCookie.CookiesProvider, null, /*#__PURE__*/ React.createElement(App, null)));
|
|
1033
|
+
};
|
|
1034
|
+
// add the app to the store
|
|
1035
|
+
coreStore.dispatch(addGen3AppMetadata({
|
|
1036
|
+
id,
|
|
1037
|
+
name,
|
|
1038
|
+
version,
|
|
1039
|
+
requiredEntityTypes
|
|
1040
|
+
}));
|
|
1041
|
+
registerGen3App(id, Gen3AppWrapper);
|
|
1042
|
+
return Gen3AppWrapper;
|
|
1043
|
+
};
|
|
1044
|
+
|
|
1045
|
+
const graphQLWithTags = gen3Api.enhanceEndpoints({
|
|
1046
|
+
addTagTypes: [
|
|
1047
|
+
'graphQL'
|
|
1048
|
+
]
|
|
1049
|
+
});
|
|
1050
|
+
/**
|
|
1051
|
+
* Creates a graphQLAPI for graphql queries to elasticsearch indices via guppy
|
|
1052
|
+
* @see https://github.com/uc-cdis/guppy/blob/master/doc/queries.md
|
|
1053
|
+
* @param query - Resolver function which configures the graphql query with graphQLParams argument
|
|
1054
|
+
* @returns: A guppy search API for fetching metadata
|
|
1055
|
+
*/ const graphQLAPI = graphQLWithTags.injectEndpoints({
|
|
1056
|
+
endpoints: (builder)=>({
|
|
1057
|
+
graphQL: builder.query({
|
|
1058
|
+
query: (graphQLParams)=>({
|
|
1059
|
+
url: `${GEN3_GUPPY_API}/graphql`,
|
|
1060
|
+
method: 'POST',
|
|
1061
|
+
credentials: 'include',
|
|
1062
|
+
body: JSON.stringify(graphQLParams)
|
|
1063
|
+
})
|
|
1064
|
+
})
|
|
1065
|
+
})
|
|
1066
|
+
});
|
|
1067
|
+
const { useGraphQLQuery } = graphQLAPI;
|
|
1068
|
+
|
|
1069
|
+
const isOperationWithField = (operation)=>{
|
|
1070
|
+
return operation?.field !== undefined;
|
|
1071
|
+
};
|
|
1072
|
+
const extractFilterValue = (op)=>{
|
|
1073
|
+
const valueExtractorHandler = new ValueExtractorHandler();
|
|
1074
|
+
return handleOperation(valueExtractorHandler, op);
|
|
1075
|
+
};
|
|
1076
|
+
const extractEnumFilterValue = (op)=>{
|
|
1077
|
+
const enumValueExtractorHandler = new EnumValueExtractorHandler();
|
|
1078
|
+
const results = handleOperation(enumValueExtractorHandler, op);
|
|
1079
|
+
return results ?? [];
|
|
1080
|
+
};
|
|
1081
|
+
const assertNever = (x)=>{
|
|
1082
|
+
throw Error(`Exhaustive comparison did not handle: ${x}`);
|
|
1083
|
+
};
|
|
1084
|
+
const handleOperation = (handler, op)=>{
|
|
1085
|
+
switch(op.operator){
|
|
1086
|
+
case '=':
|
|
1087
|
+
return handler.handleEquals(op);
|
|
1088
|
+
case '!=':
|
|
1089
|
+
return handler.handleNotEquals(op);
|
|
1090
|
+
case '<':
|
|
1091
|
+
return handler.handleLessThan(op);
|
|
1092
|
+
case '<=':
|
|
1093
|
+
return handler.handleLessThanOrEquals(op);
|
|
1094
|
+
case '>':
|
|
1095
|
+
return handler.handleGreaterThan(op);
|
|
1096
|
+
case '>=':
|
|
1097
|
+
return handler.handleGreaterThanOrEquals(op);
|
|
1098
|
+
case 'and':
|
|
1099
|
+
return handler.handleIntersection(op);
|
|
1100
|
+
case 'or':
|
|
1101
|
+
return handler.handleUnion(op);
|
|
1102
|
+
case 'nested':
|
|
1103
|
+
return handler.handleNestedFilter(op);
|
|
1104
|
+
case 'in':
|
|
1105
|
+
return handler.handleIncludes(op);
|
|
1106
|
+
case 'excludeifany':
|
|
1107
|
+
return handler.handleExcludeIfAny(op);
|
|
1108
|
+
case 'excludes':
|
|
1109
|
+
return handler.handleExcludes(op);
|
|
1110
|
+
default:
|
|
1111
|
+
return assertNever(op);
|
|
1112
|
+
}
|
|
1113
|
+
};
|
|
1114
|
+
/**
|
|
1115
|
+
* Return true if a FilterSet's root value is an empty object
|
|
1116
|
+
* @param fs - FilterSet to test
|
|
1117
|
+
*/ const isFilterEmpty = (fs)=>lodash.isEqual({}, fs);
|
|
1118
|
+
class ToGqlHandler {
|
|
1119
|
+
constructor(){
|
|
1120
|
+
this.handleEquals = (op)=>({
|
|
1121
|
+
'=': {
|
|
1122
|
+
[op.field]: op.operand
|
|
1123
|
+
}
|
|
1124
|
+
});
|
|
1125
|
+
this.handleNotEquals = (op)=>({
|
|
1126
|
+
'!=': {
|
|
1127
|
+
[op.field]: op.operand
|
|
1128
|
+
}
|
|
1129
|
+
});
|
|
1130
|
+
this.handleLessThan = (op)=>({
|
|
1131
|
+
'<': {
|
|
1132
|
+
[op.field]: op.operand
|
|
1133
|
+
}
|
|
1134
|
+
});
|
|
1135
|
+
this.handleLessThanOrEquals = (op)=>({
|
|
1136
|
+
'<=': {
|
|
1137
|
+
[op.field]: op.operand
|
|
1138
|
+
}
|
|
1139
|
+
});
|
|
1140
|
+
this.handleGreaterThan = (op)=>({
|
|
1141
|
+
'>': {
|
|
1142
|
+
[op.field]: op.operand
|
|
1143
|
+
}
|
|
1144
|
+
});
|
|
1145
|
+
this.handleGreaterThanOrEquals = (op)=>({
|
|
1146
|
+
'>=': {
|
|
1147
|
+
[op.field]: op.operand
|
|
1148
|
+
}
|
|
1149
|
+
});
|
|
1150
|
+
this.handleIncludes = (op)=>({
|
|
1151
|
+
in: {
|
|
1152
|
+
[op.field]: op.operands
|
|
1153
|
+
}
|
|
1154
|
+
});
|
|
1155
|
+
this.handleExcludes = (op)=>({
|
|
1156
|
+
exclude: {
|
|
1157
|
+
[op.field]: op.operands
|
|
1158
|
+
}
|
|
1159
|
+
});
|
|
1160
|
+
this.handleExcludeIfAny = (op)=>({
|
|
1161
|
+
excludeifany: {
|
|
1162
|
+
[op.field]: op.operands
|
|
1163
|
+
}
|
|
1164
|
+
});
|
|
1165
|
+
this.handleIntersection = (op)=>({
|
|
1166
|
+
and: op.operands.map((x)=>convertFilterToGqlFilter(x))
|
|
1167
|
+
});
|
|
1168
|
+
this.handleUnion = (op)=>({
|
|
1169
|
+
or: op.operands.map((x)=>convertFilterToGqlFilter(x))
|
|
1170
|
+
});
|
|
1171
|
+
this.handleNestedFilter = (op)=>{
|
|
1172
|
+
const child = convertFilterToGqlFilter(op.operand);
|
|
1173
|
+
return {
|
|
1174
|
+
nested: {
|
|
1175
|
+
path: op.path,
|
|
1176
|
+
...child
|
|
1177
|
+
}
|
|
1178
|
+
};
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
const convertFilterToGqlFilter = (filter)=>{
|
|
1183
|
+
const handler = new ToGqlHandler();
|
|
1184
|
+
return handleOperation(handler, filter);
|
|
1185
|
+
};
|
|
1186
|
+
const convertFilterSetToGqlFilter = (fs, toplevelOp = 'and')=>{
|
|
1187
|
+
const fsKeys = Object.keys(fs.root);
|
|
1188
|
+
// if no keys return undefined
|
|
1189
|
+
if (fsKeys.length === 0) return {
|
|
1190
|
+
and: []
|
|
1191
|
+
};
|
|
1192
|
+
return toplevelOp === 'and' ? {
|
|
1193
|
+
and: fsKeys.map((key)=>convertFilterToGqlFilter(fs.root[key]))
|
|
1194
|
+
} : {
|
|
1195
|
+
or: fsKeys.map((key)=>convertFilterToGqlFilter(fs.root[key]))
|
|
1196
|
+
};
|
|
1197
|
+
};
|
|
1198
|
+
/**
|
|
1199
|
+
* Extract the operand values, if operands themselves have values, otherwise undefined.
|
|
1200
|
+
*/ class ValueExtractorHandler {
|
|
1201
|
+
constructor(){
|
|
1202
|
+
this.handleEquals = (op)=>op.operand;
|
|
1203
|
+
this.handleNotEquals = (op)=>op.operand;
|
|
1204
|
+
this.handleIncludes = (op)=>op.operands;
|
|
1205
|
+
this.handleExcludes = (op)=>op.operands;
|
|
1206
|
+
this.handleExcludeIfAny = (op)=>op.operands;
|
|
1207
|
+
this.handleGreaterThanOrEquals = (op)=>op.operand;
|
|
1208
|
+
this.handleGreaterThan = (op)=>op.operand;
|
|
1209
|
+
this.handleLessThan = (op)=>op.operand;
|
|
1210
|
+
this.handleLessThanOrEquals = (op)=>op.operand;
|
|
1211
|
+
this.handleIntersection = (_)=>undefined;
|
|
1212
|
+
this.handleUnion = (_)=>undefined;
|
|
1213
|
+
this.handleNestedFilter = (_)=>undefined;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
/**
|
|
1217
|
+
* Extract the operand values, if operands themselves have values, otherwise undefined.
|
|
1218
|
+
*/ class EnumValueExtractorHandler {
|
|
1219
|
+
constructor(){
|
|
1220
|
+
this.handleEquals = (_)=>undefined;
|
|
1221
|
+
this.handleNotEquals = (_)=>undefined;
|
|
1222
|
+
this.handleIncludes = (op)=>op.operands;
|
|
1223
|
+
this.handleExcludes = (op)=>op.operands;
|
|
1224
|
+
this.handleExcludeIfAny = (op)=>op.operands;
|
|
1225
|
+
this.handleGreaterThanOrEquals = (_)=>undefined;
|
|
1226
|
+
this.handleGreaterThan = (_)=>undefined;
|
|
1227
|
+
this.handleLessThan = (_)=>undefined;
|
|
1228
|
+
this.handleLessThanOrEquals = (_)=>undefined;
|
|
1229
|
+
this.handleIntersection = (_)=>undefined;
|
|
1230
|
+
this.handleUnion = (_)=>undefined;
|
|
1231
|
+
this.handleNestedFilter = (op)=>{
|
|
1232
|
+
return extractEnumFilterValue(op.operand);
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
const FieldNameOverrides = {};
|
|
1238
|
+
const COMMON_PREPOSITIONS = [
|
|
1239
|
+
'a',
|
|
1240
|
+
'an',
|
|
1241
|
+
'and',
|
|
1242
|
+
'at',
|
|
1243
|
+
'but',
|
|
1244
|
+
'by',
|
|
1245
|
+
'for',
|
|
1246
|
+
'in',
|
|
1247
|
+
'is',
|
|
1248
|
+
'nor',
|
|
1249
|
+
'of',
|
|
1250
|
+
'on',
|
|
1251
|
+
'or',
|
|
1252
|
+
'out',
|
|
1253
|
+
'so',
|
|
1254
|
+
'the',
|
|
1255
|
+
'to',
|
|
1256
|
+
'up',
|
|
1257
|
+
'yet'
|
|
1258
|
+
];
|
|
1259
|
+
const capitalize = (s)=>s.length > 0 ? s[0].toUpperCase() + s.slice(1) : '';
|
|
1260
|
+
const trimFirstFieldNameToTitle = (fieldName, trim = false)=>{
|
|
1261
|
+
if (trim) {
|
|
1262
|
+
const source = fieldName.slice(fieldName.indexOf('.') + 1);
|
|
1263
|
+
return fieldNameToTitle(source ? source : fieldName, 0);
|
|
1264
|
+
}
|
|
1265
|
+
return fieldNameToTitle(fieldName);
|
|
1266
|
+
};
|
|
1267
|
+
/**
|
|
1268
|
+
* Converts a filter name to a title,
|
|
1269
|
+
* For example files.input.experimental_strategy will get converted to Experimental Strategy
|
|
1270
|
+
* if sections == 2 then the output would be Input Experimental Strategy
|
|
1271
|
+
* @param fieldName input filter expected to be: string.firstpart_secondpart
|
|
1272
|
+
* @param sections number of "sections" string.string.string to got back from the end of the field
|
|
1273
|
+
*/ const fieldNameToTitle = (fieldName, sections = 1)=>{
|
|
1274
|
+
if (fieldName in FieldNameOverrides) {
|
|
1275
|
+
return FieldNameOverrides[fieldName];
|
|
1276
|
+
}
|
|
1277
|
+
if (fieldName === undefined) return 'No Title';
|
|
1278
|
+
return fieldName.split('.').slice(-sections).map((s)=>s.split('_')).flat().map((word)=>COMMON_PREPOSITIONS.includes(word) ? word : capitalize(word)).join(' ');
|
|
1279
|
+
};
|
|
1280
|
+
/**
|
|
1281
|
+
* Extracts the index name from the field name
|
|
1282
|
+
* @param fieldName
|
|
1283
|
+
*/ const extractIndexFromFullFieldName = (fieldName)=>fieldName.split('.')[0];
|
|
1284
|
+
/**
|
|
1285
|
+
* prepend the index name to the field name
|
|
1286
|
+
*/ const prependIndexToFieldName = (fieldName, index)=>`${index}.${fieldName}`;
|
|
1287
|
+
/**
|
|
1288
|
+
* extract the field name from the index.field name
|
|
1289
|
+
*/ const extractFieldNameFromFullFieldName = (fieldName)=>fieldName.split('.').slice(1).join('.');
|
|
1290
|
+
/**
|
|
1291
|
+
* extract the field name and the index from the index.field name returning as a tuple
|
|
1292
|
+
*/ const extractIndexAndFieldNameFromFullFieldName = (fieldName)=>{
|
|
1293
|
+
const [index, ...rest] = fieldName.split('.');
|
|
1294
|
+
return [
|
|
1295
|
+
index,
|
|
1296
|
+
rest.join('.')
|
|
1297
|
+
];
|
|
1298
|
+
};
|
|
1299
|
+
|
|
1300
|
+
const statusEndpoint = '/_status';
|
|
1301
|
+
const processHistogramResponse = (data)=>{
|
|
1302
|
+
const pathData = jsonpathPlus.JSONPath({
|
|
1303
|
+
json: data,
|
|
1304
|
+
path: '$..histogram',
|
|
1305
|
+
resultType: 'all'
|
|
1306
|
+
});
|
|
1307
|
+
const results = pathData.reduce((acc, element)=>{
|
|
1308
|
+
const key = element.pointer.slice(1).replace(/\/histogram/g, '').replace(/\//g, '.');
|
|
1309
|
+
return {
|
|
1310
|
+
...acc,
|
|
1311
|
+
[key]: element.value
|
|
1312
|
+
};
|
|
1313
|
+
}, {});
|
|
1314
|
+
return results;
|
|
1315
|
+
};
|
|
1316
|
+
const fetchJson = async (url)=>{
|
|
1317
|
+
const res = await fetch(url, {
|
|
1318
|
+
method: 'GET',
|
|
1319
|
+
headers: {
|
|
1320
|
+
'Content-type': 'application/json'
|
|
1321
|
+
}
|
|
1322
|
+
});
|
|
1323
|
+
if (!res.ok) throw new Error('An error occurred while fetching the data.');
|
|
1324
|
+
return await res.json();
|
|
1325
|
+
};
|
|
1326
|
+
const useGetStatus = ()=>{
|
|
1327
|
+
const fetcher = ()=>fetchJson(`${GEN3_GUPPY_API}${statusEndpoint}`);
|
|
1328
|
+
return useSWR('explorerStatus', fetcher);
|
|
1329
|
+
};
|
|
1330
|
+
/**
|
|
1331
|
+
* The main endpoint used in templating Exploration page queries.
|
|
1332
|
+
* Includes table, filter and aggregation query types and leverages guppyApi defined in ./gupplApi.ts
|
|
1333
|
+
* Query templates support filters where applicable
|
|
1334
|
+
*
|
|
1335
|
+
* @param endpoints - Defines endpoints used in Exploration page:
|
|
1336
|
+
* @param getAllFieldsForType - A mapping query that returns all property key names vertex types specified.
|
|
1337
|
+
* @see https://github.com/uc-cdis/guppy/blob/master/doc/queries.md#mapping-query
|
|
1338
|
+
* @param getAccessibleData - An aggregation histogram counts query that filters based on access type
|
|
1339
|
+
* @see https://github.com/uc-cdis/guppy/blob/master/doc/queries.md#accessibility-argument-for-regular-tier-access-level
|
|
1340
|
+
* @param getRawDataAndTotalCounts - Queries both _totalCount for selected vertex types and
|
|
1341
|
+
* tabular results containing the raw data in the rows of selected vertex types
|
|
1342
|
+
* @see https://github.com/uc-cdis/guppy/blob/master/doc/queries.md#1-total-count-aggregation
|
|
1343
|
+
* @param getAggs - An aggregated histogram counts query which outputs vertex property frequencies
|
|
1344
|
+
* @param getSubAggs - TODO: not sure what this one does. Looks like nested aggregation
|
|
1345
|
+
* @param getCounts - Returns total counts of a vertex type
|
|
1346
|
+
* @returns: A guppy API endpoint for templating queriable data displayed on the exploration page
|
|
1347
|
+
*/ const explorerApi = guppyApi.injectEndpoints({
|
|
1348
|
+
endpoints: (builder)=>({
|
|
1349
|
+
getAllFieldsForType: builder.query({
|
|
1350
|
+
query: (type)=>({
|
|
1351
|
+
query: `{ _mapping ${type} } }`
|
|
1352
|
+
}),
|
|
1353
|
+
transformResponse: (response, _meta, params)=>{
|
|
1354
|
+
return response[params.type];
|
|
1355
|
+
}
|
|
1356
|
+
}),
|
|
1357
|
+
getAccessibleData: builder.query({
|
|
1358
|
+
query: ({ type, fields, accessType })=>{
|
|
1359
|
+
const fieldParts = fields.map((field)=>`${field} { histogram { key count } }`);
|
|
1360
|
+
return {
|
|
1361
|
+
query: `_aggregation {
|
|
1362
|
+
${type} (accessibility: ${accessType}) {
|
|
1363
|
+
${fieldParts.join(',')}
|
|
1364
|
+
}
|
|
1365
|
+
}`
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
}),
|
|
1369
|
+
getRawDataAndTotalCounts: builder.query({
|
|
1370
|
+
query: ({ type, fields, filters, sort, offset = 0, size = 20, accessibility = exports.Accessibility.ALL, format = undefined })=>{
|
|
1371
|
+
const gqlFilter = convertFilterSetToGqlFilter(filters);
|
|
1372
|
+
const params = [
|
|
1373
|
+
...sort ? [
|
|
1374
|
+
'$sort: JSON'
|
|
1375
|
+
] : [],
|
|
1376
|
+
...gqlFilter ? [
|
|
1377
|
+
'$filter: JSON'
|
|
1378
|
+
] : [],
|
|
1379
|
+
...format ? [
|
|
1380
|
+
'$format: Format'
|
|
1381
|
+
] : []
|
|
1382
|
+
].join(',');
|
|
1383
|
+
const queryLine = `query getRawDataAndTotalCounts (${params}) {`;
|
|
1384
|
+
const dataParams = [
|
|
1385
|
+
...format ? [
|
|
1386
|
+
'format: $format'
|
|
1387
|
+
] : [],
|
|
1388
|
+
...sort ? [
|
|
1389
|
+
'sort: $sort'
|
|
1390
|
+
] : [],
|
|
1391
|
+
...gqlFilter ? [
|
|
1392
|
+
'filter: $filter'
|
|
1393
|
+
] : []
|
|
1394
|
+
].join(',');
|
|
1395
|
+
const dataTypeLine = `${type} (accessibility: ${accessibility}, offset: ${offset}, first: ${size},
|
|
1396
|
+
${dataParams}) {`;
|
|
1397
|
+
const typeAggsLine = `${type} (${gqlFilter && 'filter: $filter,'} accessibility: ${accessibility}) {`;
|
|
1398
|
+
const processedFields = fields.map((field)=>rawDataQueryStrForEachField(field));
|
|
1399
|
+
const query = `${queryLine}
|
|
1400
|
+
${dataTypeLine}
|
|
1401
|
+
${processedFields.join(' ')}
|
|
1402
|
+
}
|
|
1403
|
+
_aggregation {
|
|
1404
|
+
${typeAggsLine}
|
|
1405
|
+
_totalCount
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
}`;
|
|
1409
|
+
const variables = {
|
|
1410
|
+
...sort && {
|
|
1411
|
+
sort
|
|
1412
|
+
},
|
|
1413
|
+
...gqlFilter && {
|
|
1414
|
+
filter: gqlFilter
|
|
1415
|
+
},
|
|
1416
|
+
...format && {
|
|
1417
|
+
format
|
|
1418
|
+
}
|
|
1419
|
+
};
|
|
1420
|
+
return {
|
|
1421
|
+
query,
|
|
1422
|
+
variables
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
}),
|
|
1426
|
+
getAggs: builder.query({
|
|
1427
|
+
query: ({ type, fields, filters, accessibility = exports.Accessibility.ALL })=>{
|
|
1428
|
+
const queryStart = isFilterEmpty(filters) ? `
|
|
1429
|
+
query getAggs {
|
|
1430
|
+
_aggregation {
|
|
1431
|
+
${type} (accessibility: ${accessibility}) {` : `query getAggs ($filter: JSON) {
|
|
1432
|
+
_aggregation {
|
|
1433
|
+
${type} (filter: $filter, filterSelf: false, accessibility: ${accessibility}) {`;
|
|
1434
|
+
const query = `${queryStart}
|
|
1435
|
+
${fields.map((field)=>histogramQueryStrForEachField(field))}
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
}`;
|
|
1439
|
+
const queryBody = {
|
|
1440
|
+
query: query,
|
|
1441
|
+
variables: {
|
|
1442
|
+
filter: convertFilterSetToGqlFilter(filters)
|
|
1443
|
+
}
|
|
1444
|
+
};
|
|
1445
|
+
return queryBody;
|
|
1446
|
+
},
|
|
1447
|
+
transformResponse: (response, _meta, args)=>{
|
|
1448
|
+
return processHistogramResponse(response.data._aggregation[args.type]);
|
|
1449
|
+
}
|
|
1450
|
+
}),
|
|
1451
|
+
getSubAggs: builder.query({
|
|
1452
|
+
query: ({ type, mainField, termsFields = undefined, missingFields = undefined, numericAggAsText = false, gqlFilter = undefined, accessibility = exports.Accessibility.ALL })=>{
|
|
1453
|
+
const nestedAggFields = {
|
|
1454
|
+
termsFields: termsFields,
|
|
1455
|
+
missingFields: missingFields
|
|
1456
|
+
};
|
|
1457
|
+
const query = `query getSubAggs ( ${gqlFilter ?? '$filter: JSON,'} $nestedAggFields: JSON) {
|
|
1458
|
+
_aggregation {
|
|
1459
|
+
${type} ( ${gqlFilter ?? 'filter: $filter, filterSelf: false,'} nestedAggFields: $nestedAggFields, accessibility: ${accessibility}) {
|
|
1460
|
+
${nestedHistogramQueryStrForEachField(mainField, numericAggAsText)}
|
|
1461
|
+
}`;
|
|
1462
|
+
return {
|
|
1463
|
+
query: query,
|
|
1464
|
+
variables: {
|
|
1465
|
+
...gqlFilter && {
|
|
1466
|
+
filter: convertFilterSetToGqlFilter(gqlFilter)
|
|
1467
|
+
},
|
|
1468
|
+
nestedAggFields: nestedAggFields
|
|
1469
|
+
}
|
|
1470
|
+
};
|
|
1471
|
+
},
|
|
1472
|
+
transformResponse: (response, _meta, args)=>{
|
|
1473
|
+
return processHistogramResponse(response.data._aggregation[args.type]);
|
|
1474
|
+
}
|
|
1475
|
+
}),
|
|
1476
|
+
getCounts: builder.query({
|
|
1477
|
+
query: ({ type, filters, accessibility = exports.Accessibility.ALL })=>{
|
|
1478
|
+
const gqlFilters = convertFilterSetToGqlFilter(filters);
|
|
1479
|
+
const queryLine = `query totalCounts ${gqlFilters ? '($filter: JSON)' : ''}{`;
|
|
1480
|
+
const typeAggsLine = `${type} ${gqlFilters ? '(filter: $filter, ' : '('} accessibility: ${accessibility}) {`;
|
|
1481
|
+
const query = `${queryLine} _aggregation {
|
|
1482
|
+
${typeAggsLine}
|
|
1483
|
+
_totalCount
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
}`;
|
|
1487
|
+
return {
|
|
1488
|
+
query: query,
|
|
1489
|
+
variables: {
|
|
1490
|
+
...gqlFilters && {
|
|
1491
|
+
filter: gqlFilters
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
};
|
|
1495
|
+
},
|
|
1496
|
+
transformResponse: (response, _meta, args)=>{
|
|
1497
|
+
return response.data._aggregation[args.type]._totalCount;
|
|
1498
|
+
}
|
|
1499
|
+
}),
|
|
1500
|
+
getFieldCountSummary: builder.query({
|
|
1501
|
+
query: ({ type, field, filters })=>{
|
|
1502
|
+
const gqlFilters = convertFilterSetToGqlFilter(filters);
|
|
1503
|
+
const query = `query ($filter: JSON) {
|
|
1504
|
+
_aggregation {
|
|
1505
|
+
${type} (filter: $filter) {
|
|
1506
|
+
${field} {
|
|
1507
|
+
histogram {
|
|
1508
|
+
sum
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
}`;
|
|
1514
|
+
return {
|
|
1515
|
+
query: query,
|
|
1516
|
+
variables: {
|
|
1517
|
+
...gqlFilters && {
|
|
1518
|
+
filter: gqlFilters
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
};
|
|
1522
|
+
}
|
|
1523
|
+
}),
|
|
1524
|
+
getFieldsForIndex: builder.query({
|
|
1525
|
+
query: (index)=>{
|
|
1526
|
+
return {
|
|
1527
|
+
query: `{
|
|
1528
|
+
_mapping ${index}
|
|
1529
|
+
}`
|
|
1530
|
+
};
|
|
1531
|
+
},
|
|
1532
|
+
transformResponse: (response)=>{
|
|
1533
|
+
return response['_mapping'];
|
|
1534
|
+
}
|
|
1535
|
+
}),
|
|
1536
|
+
generalGQL: builder.query({
|
|
1537
|
+
query: ({ query, variables })=>{
|
|
1538
|
+
return {
|
|
1539
|
+
query: query,
|
|
1540
|
+
variables: variables
|
|
1541
|
+
};
|
|
1542
|
+
}
|
|
1543
|
+
})
|
|
1544
|
+
})
|
|
1545
|
+
});
|
|
1546
|
+
// query for aggregate data
|
|
1547
|
+
// convert the function below to typescript
|
|
1548
|
+
const histogramQueryStrForEachField = (field)=>{
|
|
1549
|
+
const splittedFieldArray = field.split('.');
|
|
1550
|
+
const splittedField = splittedFieldArray.shift();
|
|
1551
|
+
if (splittedFieldArray.length === 0) {
|
|
1552
|
+
return `
|
|
1553
|
+
${splittedField} {
|
|
1554
|
+
histogram {
|
|
1555
|
+
key
|
|
1556
|
+
count
|
|
1557
|
+
}
|
|
1558
|
+
}`;
|
|
1559
|
+
}
|
|
1560
|
+
return `
|
|
1561
|
+
${splittedField} {
|
|
1562
|
+
${histogramQueryStrForEachField(splittedFieldArray.join('.'))}
|
|
1563
|
+
}`;
|
|
1564
|
+
};
|
|
1565
|
+
const nestedHistogramQueryStrForEachField = (mainField, numericAggAsText)=>`
|
|
1566
|
+
${mainField} {
|
|
1567
|
+
${numericAggAsText ? 'asTextHistogram' : 'histogram'} {
|
|
1568
|
+
key
|
|
1569
|
+
count
|
|
1570
|
+
missingFields {
|
|
1571
|
+
field
|
|
1572
|
+
count
|
|
1573
|
+
}
|
|
1574
|
+
termsFields {
|
|
1575
|
+
field
|
|
1576
|
+
count
|
|
1577
|
+
terms {
|
|
1578
|
+
key
|
|
1579
|
+
count
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
}`;
|
|
1584
|
+
const rawDataQueryStrForEachField = (field)=>{
|
|
1585
|
+
const splitFieldArray = field.split('.');
|
|
1586
|
+
const splitField = splitFieldArray.shift();
|
|
1587
|
+
if (splitFieldArray.length === 0) {
|
|
1588
|
+
return `
|
|
1589
|
+
${splitField}
|
|
1590
|
+
`;
|
|
1591
|
+
}
|
|
1592
|
+
return `
|
|
1593
|
+
${splitField} {
|
|
1594
|
+
${rawDataQueryStrForEachField(splitFieldArray.join('.'))}
|
|
1595
|
+
}`;
|
|
1596
|
+
};
|
|
1597
|
+
const useGetArrayTypes = ()=>{
|
|
1598
|
+
{
|
|
1599
|
+
const { data, error } = useGetStatus();
|
|
1600
|
+
if (error) {
|
|
1601
|
+
return {};
|
|
1602
|
+
}
|
|
1603
|
+
return data ? data['indices'] : {};
|
|
1604
|
+
}
|
|
1605
|
+
};
|
|
1606
|
+
const { useGetRawDataAndTotalCountsQuery, useGetAccessibleDataQuery, useGetAllFieldsForTypeQuery, useGetAggsQuery, useGetSubAggsQuery, useGetCountsQuery, useGetFieldCountSummaryQuery, useGetFieldsForIndexQuery, useGeneralGQLQuery, useLazyGeneralGQLQuery } = explorerApi;
|
|
1607
|
+
const EmptyAggData = {};
|
|
1608
|
+
const selectAggDataForIndex = (state, index, field)=>{
|
|
1609
|
+
const data = state.guppyApi.aggs[index]?.[field]?.histogram;
|
|
1610
|
+
return data ?? EmptyAggData;
|
|
1611
|
+
};
|
|
1612
|
+
|
|
1613
|
+
/**
|
|
1614
|
+
* Flattens a deep nested JSON object skipping
|
|
1615
|
+
* the first level to avoid potentially flattening
|
|
1616
|
+
* non-nested data.
|
|
1617
|
+
* @param {JSON} json
|
|
1618
|
+
*/ function flattenJson(json) {
|
|
1619
|
+
const flattenedJson = [];
|
|
1620
|
+
Object.keys(json).forEach((key)=>{
|
|
1621
|
+
flattenedJson.push(flat.flatten(json[key], {
|
|
1622
|
+
delimiter: '_'
|
|
1623
|
+
}));
|
|
1624
|
+
});
|
|
1625
|
+
return flattenedJson;
|
|
1626
|
+
}
|
|
1627
|
+
/**
|
|
1628
|
+
* Converts JSON based on a config.
|
|
1629
|
+
* @param {JSON} json
|
|
1630
|
+
* @param {Object} config
|
|
1631
|
+
*/ async function conversion(json, config) {
|
|
1632
|
+
return Papa.unparse(json, config);
|
|
1633
|
+
}
|
|
1634
|
+
/**
|
|
1635
|
+
* Converts JSON to a specified file format.
|
|
1636
|
+
* Defaults to JSON if file format is not supported.
|
|
1637
|
+
* @param {JSON} json
|
|
1638
|
+
* @param {string} format
|
|
1639
|
+
*/ async function jsonToFormat(json, format) {
|
|
1640
|
+
if (Object.keys(FILE_DELIMITERS).includes(format)) {
|
|
1641
|
+
const flatJson = await flattenJson(json);
|
|
1642
|
+
const data = await conversion(flatJson, {
|
|
1643
|
+
delimiter: FILE_DELIMITERS[format]
|
|
1644
|
+
});
|
|
1645
|
+
return data;
|
|
1646
|
+
}
|
|
1647
|
+
return json;
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
/**
|
|
1651
|
+
* Prepares a URL for downloading by appending '/download' to the provided apiUrl.
|
|
1652
|
+
*
|
|
1653
|
+
* @param {string} apiUrl - The base URL to be used for preparing the download URL.
|
|
1654
|
+
* @returns {URL} - The prepared download URL as a URL object.
|
|
1655
|
+
*/ const prepareUrl = (apiUrl)=>new URL(apiUrl + '/download');
|
|
1656
|
+
/**
|
|
1657
|
+
* Prepares a fetch configuration object for downloading files from Guppy.
|
|
1658
|
+
*
|
|
1659
|
+
* @param {GuppyFileDownloadRequestParams} parameters - The parameters to include in the request body.
|
|
1660
|
+
* @param {string} csrfToken - The CSRF token to include in the request headers.
|
|
1661
|
+
* @returns {FetchConfig} - The prepared fetch configuration object.
|
|
1662
|
+
*/ const prepareFetchConfig = (parameters, csrfToken)=>{
|
|
1663
|
+
return {
|
|
1664
|
+
method: 'POST',
|
|
1665
|
+
headers: {
|
|
1666
|
+
'Content-Type': 'application/json',
|
|
1667
|
+
...csrfToken !== undefined && {
|
|
1668
|
+
'X-CSRF-Token': csrfToken
|
|
1669
|
+
}
|
|
1670
|
+
},
|
|
1671
|
+
body: JSON.stringify({
|
|
1672
|
+
type: parameters.type,
|
|
1673
|
+
filter: convertFilterSetToGqlFilter(parameters.filter),
|
|
1674
|
+
accessibility: parameters.accessibility,
|
|
1675
|
+
fields: parameters?.fields,
|
|
1676
|
+
sort: parameters?.sort
|
|
1677
|
+
})
|
|
1678
|
+
};
|
|
1679
|
+
};
|
|
1680
|
+
/**
|
|
1681
|
+
* Downloads a file from Guppy using the provided parameters.
|
|
1682
|
+
* It will optionally convert the data to the specified format.
|
|
1683
|
+
*
|
|
1684
|
+
* @param {DownloadFromGuppyParams} parameters - The parameters to use for the download request.
|
|
1685
|
+
* @param onStart - The function to call when the download starts.
|
|
1686
|
+
* @param onDone - The function to call when the download is done.
|
|
1687
|
+
* @param onError - The function to call when the download fails.
|
|
1688
|
+
* @param onAbort - The function to call when the download is aborted.
|
|
1689
|
+
* @param signal - AbortSignal to use for the request.
|
|
1690
|
+
*/ const downloadFromGuppyToBlob = async ({ parameters, onStart = ()=>null, onDone = (_)=>null, onError = (_)=>null, onAbort = ()=>null, signal = undefined })=>{
|
|
1691
|
+
const csrfToken = selectCSRFToken(coreStore.getState());
|
|
1692
|
+
onStart?.();
|
|
1693
|
+
const url = prepareUrl(GEN3_GUPPY_API);
|
|
1694
|
+
const fetchConfig = prepareFetchConfig(parameters, csrfToken);
|
|
1695
|
+
fetch(url.toString(), {
|
|
1696
|
+
...fetchConfig,
|
|
1697
|
+
...signal ? {
|
|
1698
|
+
signal: signal
|
|
1699
|
+
} : {}
|
|
1700
|
+
}).then(async (response)=>{
|
|
1701
|
+
if (!response.ok) {
|
|
1702
|
+
throw new Error(response.statusText);
|
|
1703
|
+
}
|
|
1704
|
+
let jsonData = await response.json();
|
|
1705
|
+
if (parameters?.rootPath && parameters.rootPath) {
|
|
1706
|
+
// if rootPath is provided, extract the data from the rootPath
|
|
1707
|
+
jsonData = jsonpathPlus.JSONPath({
|
|
1708
|
+
json: jsonData,
|
|
1709
|
+
path: `$.[${parameters.rootPath}]`,
|
|
1710
|
+
resultType: 'value'
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
// convert the data to the specified format and return a Blob
|
|
1714
|
+
let str = '';
|
|
1715
|
+
if (parameters.format === 'json') {
|
|
1716
|
+
str = JSON.stringify(jsonData);
|
|
1717
|
+
} else {
|
|
1718
|
+
const convertedData = await jsonToFormat(jsonData, parameters.format);
|
|
1719
|
+
if (isJSONObject(convertedData)) {
|
|
1720
|
+
str = JSON.stringify(convertedData, null, 2);
|
|
1721
|
+
} else {
|
|
1722
|
+
str = convertedData;
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
return new Blob([
|
|
1726
|
+
str
|
|
1727
|
+
], {
|
|
1728
|
+
type: 'application/json'
|
|
1729
|
+
});
|
|
1730
|
+
}).then((blob)=>onDone?.(blob)).catch((error)=>{
|
|
1731
|
+
// Abort is handle as an exception
|
|
1732
|
+
if (error.name == 'AbortError') {
|
|
1733
|
+
// handle abort()
|
|
1734
|
+
onAbort?.();
|
|
1735
|
+
}
|
|
1736
|
+
onError?.(error);
|
|
1737
|
+
});
|
|
1738
|
+
};
|
|
1739
|
+
const downloadJSONDataFromGuppy = async ({ parameters, onAbort = ()=>null, signal = undefined })=>{
|
|
1740
|
+
const csrfToken = selectCSRFToken(coreStore.getState());
|
|
1741
|
+
const url = prepareUrl(GEN3_GUPPY_API);
|
|
1742
|
+
const fetchConfig = prepareFetchConfig(parameters, csrfToken);
|
|
1743
|
+
try {
|
|
1744
|
+
const response = await fetch(url.toString(), {
|
|
1745
|
+
...fetchConfig,
|
|
1746
|
+
...signal ? {
|
|
1747
|
+
signal: signal
|
|
1748
|
+
} : {}
|
|
1749
|
+
});
|
|
1750
|
+
let jsonData = await response.json();
|
|
1751
|
+
if (parameters?.rootPath && parameters.rootPath) {
|
|
1752
|
+
// if rootPath is provided, extract the data from the rootPath
|
|
1753
|
+
jsonData = jsonpathPlus.JSONPath({
|
|
1754
|
+
json: jsonData,
|
|
1755
|
+
path: `$.[${parameters.rootPath}]`,
|
|
1756
|
+
resultType: 'value'
|
|
1757
|
+
});
|
|
1758
|
+
}
|
|
1759
|
+
// convert the data to the specified format and return a Blob
|
|
1760
|
+
return jsonData;
|
|
1761
|
+
} catch (error) {
|
|
1762
|
+
// Abort is handle as an exception
|
|
1763
|
+
if (error.name == 'AbortError') {
|
|
1764
|
+
// handle abort()
|
|
1765
|
+
onAbort?.();
|
|
1766
|
+
}
|
|
1767
|
+
throw new Error(error);
|
|
1768
|
+
}
|
|
1769
|
+
};
|
|
1770
|
+
const useGetIndexFields = (index)=>{
|
|
1771
|
+
const { data } = useGetFieldsForIndexQuery(index);
|
|
1772
|
+
return data ?? [];
|
|
1773
|
+
};
|
|
1774
|
+
|
|
1775
|
+
/**
|
|
1776
|
+
* Creates a Guppy API for fetching bulk (> 10K rows) elasticsearch data
|
|
1777
|
+
* @see https://github.com/uc-cdis/guppy/blob/master/doc/download.md
|
|
1778
|
+
* @param endpoints - Resolver function which configures the query with
|
|
1779
|
+
* type, filter, accessibility, fields, and sort arguments
|
|
1780
|
+
* @returns: A guppy download API for fetching bulk metadata
|
|
1781
|
+
*/ const downloadRequestApi = gen3Api.injectEndpoints({
|
|
1782
|
+
endpoints: (builder)=>({
|
|
1783
|
+
downloadFromGuppy: builder.mutation({
|
|
1784
|
+
query: ({ type, filter, accessibility, fields, sort })=>{
|
|
1785
|
+
const queryBody = {
|
|
1786
|
+
filter: convertFilterSetToGqlFilter(filter),
|
|
1787
|
+
...{
|
|
1788
|
+
type,
|
|
1789
|
+
accessibility,
|
|
1790
|
+
fields,
|
|
1791
|
+
sort
|
|
1792
|
+
}
|
|
1793
|
+
};
|
|
1794
|
+
return {
|
|
1795
|
+
url: `${GEN3_GUPPY_API}/download`,
|
|
1796
|
+
method: 'POST',
|
|
1797
|
+
queryBody,
|
|
1798
|
+
cache: 'no-cache'
|
|
1799
|
+
};
|
|
1800
|
+
},
|
|
1801
|
+
transformResponse: (response)=>{
|
|
1802
|
+
return response;
|
|
1803
|
+
}
|
|
1804
|
+
})
|
|
1805
|
+
})
|
|
1806
|
+
});
|
|
1807
|
+
const { useDownloadFromGuppyMutation } = downloadRequestApi;
|
|
1808
|
+
|
|
1809
|
+
/**
|
|
1810
|
+
* returns a response from the AI search service
|
|
1811
|
+
* @param searchParams - the parameters for the AI search service
|
|
1812
|
+
* @returns the response from the AI search service
|
|
1813
|
+
*/ const aiSearchApi = gen3Api.injectEndpoints({
|
|
1814
|
+
endpoints: (builder)=>({
|
|
1815
|
+
askQuestion: builder.mutation({
|
|
1816
|
+
query: (searchParams)=>({
|
|
1817
|
+
url: `${GEN3_AI_SEARCH_API}/ask`,
|
|
1818
|
+
method: 'POST',
|
|
1819
|
+
credentials: 'include',
|
|
1820
|
+
headers: {
|
|
1821
|
+
Accept: 'application/json',
|
|
1822
|
+
'Content-Type': 'application/json'
|
|
1823
|
+
},
|
|
1824
|
+
body: JSON.stringify({
|
|
1825
|
+
query: searchParams.query,
|
|
1826
|
+
...searchParams.topic ? {
|
|
1827
|
+
topic: searchParams.topic
|
|
1828
|
+
} : {},
|
|
1829
|
+
...searchParams.conversationId ? {
|
|
1830
|
+
conversation_id: searchParams.conversationId
|
|
1831
|
+
} : {}
|
|
1832
|
+
})
|
|
1833
|
+
}),
|
|
1834
|
+
transformResponse: (data, _, arg)=>{
|
|
1835
|
+
return {
|
|
1836
|
+
query: arg.query,
|
|
1837
|
+
response: data.response,
|
|
1838
|
+
topic: data.topic,
|
|
1839
|
+
conversationId: data.conversation_id,
|
|
1840
|
+
documents: data.documents
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
}),
|
|
1844
|
+
/**
|
|
1845
|
+
* returns the status of the AI search service
|
|
1846
|
+
* @returns {
|
|
1847
|
+
* status: string
|
|
1848
|
+
* timestamp: string
|
|
1849
|
+
* }
|
|
1850
|
+
*/ getAISearchStatus: builder.query({
|
|
1851
|
+
query: ()=>`${GEN3_AI_SEARCH_API}/_status`
|
|
1852
|
+
}),
|
|
1853
|
+
getAISearchVersion: builder.query({
|
|
1854
|
+
query: ()=>`${GEN3_AI_SEARCH_API}/_version`
|
|
1855
|
+
})
|
|
1856
|
+
})
|
|
1857
|
+
});
|
|
1858
|
+
// Add more endpoints here
|
|
1859
|
+
const { useAskQuestionMutation, useGetAISearchStatusQuery, useGetAISearchVersionQuery } = aiSearchApi;
|
|
1860
|
+
|
|
1861
|
+
const workspacesApi = gen3Api.injectEndpoints({
|
|
1862
|
+
endpoints: (builder)=>({
|
|
1863
|
+
getWorkspaceOptions: builder.query({
|
|
1864
|
+
query: ()=>`${GEN3_WORKSPACE_STATUS_API}/options`
|
|
1865
|
+
}),
|
|
1866
|
+
getWorkspacePayModels: builder.query({
|
|
1867
|
+
query: ()=>`${GEN3_WORKSPACE_STATUS_API}/allpaymodels`
|
|
1868
|
+
}),
|
|
1869
|
+
getWorkspaceStatus: builder.query({
|
|
1870
|
+
query: ()=>`${GEN3_WORKSPACE_STATUS_API}/status`
|
|
1871
|
+
})
|
|
1872
|
+
})
|
|
1873
|
+
});
|
|
1874
|
+
const { useGetWorkspaceOptionsQuery, useGetWorkspacePayModelsQuery, useGetWorkspaceStatusQuery } = workspacesApi;
|
|
1875
|
+
|
|
1876
|
+
const resourcePathFromProjectID = (projectID)=>{
|
|
1877
|
+
const split = projectID.split('-');
|
|
1878
|
+
const program = split[0];
|
|
1879
|
+
const project = split.slice(1).join('-');
|
|
1880
|
+
const resourcePath = [
|
|
1881
|
+
'/programs',
|
|
1882
|
+
program,
|
|
1883
|
+
'projects',
|
|
1884
|
+
project
|
|
1885
|
+
].join('/');
|
|
1886
|
+
return resourcePath;
|
|
1887
|
+
};
|
|
1888
|
+
const isRootUrl = (urlFragment)=>urlFragment === '_root';
|
|
1889
|
+
const isProgramUrl = (urlFragment)=>urlFragment !== '_root' && !urlFragment.includes('-');
|
|
1890
|
+
const userHasSheepdogProgramAdmin = (userAuthMapping = {})=>userAuthMapping['/services/sheepdog/submission/program'] !== undefined;
|
|
1891
|
+
const userHasSheepdogProjectAdmin = (userAuthMapping = {})=>userAuthMapping['/services/sheepdog/submission/project'] !== undefined;
|
|
1892
|
+
const projectCodeFromResourcePath = (resourcePath)=>{
|
|
1893
|
+
const split = resourcePath.split('/');
|
|
1894
|
+
return split.length < 5 || split[1] !== 'programs' || split[3] !== 'projects' ? '' : split[4];
|
|
1895
|
+
};
|
|
1896
|
+
const listifyMethodsFromMapping = (actions)=>{
|
|
1897
|
+
const reducer = (accumulator, currval)=>accumulator.concat([
|
|
1898
|
+
currval.method
|
|
1899
|
+
]);
|
|
1900
|
+
return actions.reduce(reducer, []);
|
|
1901
|
+
};
|
|
1902
|
+
const userHasDataUpload = (userAuthMapping = {})=>{
|
|
1903
|
+
const actionIsFileUpload = (x)=>x.method === 'file_upload';
|
|
1904
|
+
const resource = userAuthMapping['/data_file'];
|
|
1905
|
+
return resource !== undefined && resource.some(actionIsFileUpload);
|
|
1906
|
+
};
|
|
1907
|
+
const userHasMethodForServiceOnResource = (method, service, resourcePath, userAuthMapping = {})=>{
|
|
1908
|
+
const actions = userAuthMapping[resourcePath];
|
|
1909
|
+
return actions !== undefined && actions.some((x)=>(x.service === service || x.service === '*') && (x.method === method || x.method === '*'));
|
|
1910
|
+
};
|
|
1911
|
+
const userHasMethodForServiceOnProject = (method, service, projectID, userAuthMapping = {})=>{
|
|
1912
|
+
const resourcePath = resourcePathFromProjectID(projectID);
|
|
1913
|
+
return userHasMethodForServiceOnResource(method, service, resourcePath, userAuthMapping);
|
|
1914
|
+
};
|
|
1915
|
+
const userHasMethodOnAnyProject = (method, userAuthMapping = {})=>{
|
|
1916
|
+
const actionHasMethod = (x)=>x.method === method;
|
|
1917
|
+
const actionArrays = Object.values(userAuthMapping);
|
|
1918
|
+
const hasMethod = actionArrays.some((x)=>x.some(actionHasMethod));
|
|
1919
|
+
return hasMethod;
|
|
1920
|
+
};
|
|
1921
|
+
const userHasCreateOrUpdateOnAnyProject = (userAuthMapping = {})=>userHasMethodOnAnyProject('create', userAuthMapping) || userHasMethodOnAnyProject('update', userAuthMapping);
|
|
1922
|
+
|
|
1923
|
+
const extractValuesFromObject = (jsonPathMappings, obj)=>{
|
|
1924
|
+
const result = {};
|
|
1925
|
+
const extractObjectValue = (jsonPath, obj)=>{
|
|
1926
|
+
const extractedValues = jsonpathPlus.JSONPath({
|
|
1927
|
+
path: jsonPath,
|
|
1928
|
+
json: obj
|
|
1929
|
+
});
|
|
1930
|
+
return extractedValues.length > 0 ? extractedValues[0] : undefined;
|
|
1931
|
+
};
|
|
1932
|
+
for(const key in jsonPathMappings){
|
|
1933
|
+
if (key in Object.keys(jsonPathMappings)) {
|
|
1934
|
+
// Extract value from object and store it in the result.
|
|
1935
|
+
result[key] = extractObjectValue(jsonPathMappings[key], obj);
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
return result;
|
|
1939
|
+
};
|
|
1940
|
+
|
|
1941
|
+
const SubmissionGraphqlQuery = `query transactionList {
|
|
1942
|
+
transactionList: transaction_log(last: 20) {
|
|
1943
|
+
id
|
|
1944
|
+
submitter
|
|
1945
|
+
project_id
|
|
1946
|
+
created_datetime
|
|
1947
|
+
documents {
|
|
1948
|
+
doc_size
|
|
1949
|
+
doc
|
|
1950
|
+
id
|
|
1951
|
+
}
|
|
1952
|
+
state
|
|
1953
|
+
}
|
|
1954
|
+
}`;
|
|
1955
|
+
/**
|
|
1956
|
+
* Defines submissionApi service using a base URL and expected endpoints. Derived from gen3Api core API.
|
|
1957
|
+
* @param endpoints - Defines endpoints used in submission page
|
|
1958
|
+
* @param getProjects - Queries the list of projects
|
|
1959
|
+
* @param getProjectsDetails - Queries the list of projects and their details
|
|
1960
|
+
* @param getSubmissionGraphQL - Queries the submission graphql with a query and variables
|
|
1961
|
+
* @returns: A submission API for fetching project details
|
|
1962
|
+
*/ const submissionApi = gen3Api.injectEndpoints({
|
|
1963
|
+
endpoints: (builder)=>({
|
|
1964
|
+
getProjects: builder.query({
|
|
1965
|
+
query: (graphQLParams)=>({
|
|
1966
|
+
url: `${GEN3_SUBMISSION_API}/graphql`,
|
|
1967
|
+
method: 'POST',
|
|
1968
|
+
credentials: 'include',
|
|
1969
|
+
body: JSON.stringify(graphQLParams)
|
|
1970
|
+
}),
|
|
1971
|
+
transformResponse: (response, _meta)=>{
|
|
1972
|
+
return {
|
|
1973
|
+
projects: response.data.project
|
|
1974
|
+
};
|
|
1975
|
+
}
|
|
1976
|
+
}),
|
|
1977
|
+
getSubmissions: builder.query({
|
|
1978
|
+
query: ()=>({
|
|
1979
|
+
url: `${GEN3_SUBMISSION_API}/graphql`,
|
|
1980
|
+
method: 'POST',
|
|
1981
|
+
credentials: 'include',
|
|
1982
|
+
body: JSON.stringify({
|
|
1983
|
+
query: SubmissionGraphqlQuery
|
|
1984
|
+
})
|
|
1985
|
+
}),
|
|
1986
|
+
transformResponse: (response, _meta)=>{
|
|
1987
|
+
return {
|
|
1988
|
+
transactionList: response.data.transactionList
|
|
1989
|
+
};
|
|
1990
|
+
}
|
|
1991
|
+
}),
|
|
1992
|
+
getProjectsDetails: builder.query({
|
|
1993
|
+
async queryFn (args, _queryApi, _extraOptions, fetchWithBQ) {
|
|
1994
|
+
// get the list of projects
|
|
1995
|
+
const projectsResponse = await fetchWithBQ({
|
|
1996
|
+
url: `${GEN3_SUBMISSION_API}/graphql`,
|
|
1997
|
+
method: 'POST',
|
|
1998
|
+
credentials: 'include',
|
|
1999
|
+
body: JSON.stringify(args.projectQuery)
|
|
2000
|
+
});
|
|
2001
|
+
if (projectsResponse.error) return {
|
|
2002
|
+
error: projectsResponse.error
|
|
2003
|
+
};
|
|
2004
|
+
const projects = projectsResponse?.data.data.project;
|
|
2005
|
+
const projectIds = projects.map((p)=>p.project_id);
|
|
2006
|
+
// given the list of projects, get all of them by executing the projectDetailsQuery for each project
|
|
2007
|
+
const projectDetails = await Promise.all(projectIds.map(async (projectId)=>{
|
|
2008
|
+
const result = await fetchWithBQ({
|
|
2009
|
+
url: `${GEN3_SUBMISSION_API}/graphql`,
|
|
2010
|
+
method: 'POST',
|
|
2011
|
+
credentials: 'include',
|
|
2012
|
+
body: JSON.stringify({
|
|
2013
|
+
...args.projectDetailsQuery,
|
|
2014
|
+
variables: {
|
|
2015
|
+
name: projectId
|
|
2016
|
+
}
|
|
2017
|
+
})
|
|
2018
|
+
});
|
|
2019
|
+
const { project, ...values } = result.data.data;
|
|
2020
|
+
return result.data ? {
|
|
2021
|
+
data: args.mapping ? {
|
|
2022
|
+
...extractValuesFromObject(args.mapping, values),
|
|
2023
|
+
project: project[0]
|
|
2024
|
+
} : {
|
|
2025
|
+
...values,
|
|
2026
|
+
project: project[0]
|
|
2027
|
+
}
|
|
2028
|
+
} : {
|
|
2029
|
+
error: result.error
|
|
2030
|
+
};
|
|
2031
|
+
}));
|
|
2032
|
+
// if any of the projectDetails has an error, return the error
|
|
2033
|
+
if (projectDetails.some((detail)=>'error' in detail)) {
|
|
2034
|
+
return {
|
|
2035
|
+
error: projectDetails.find((detail)=>'error' in detail)
|
|
2036
|
+
};
|
|
2037
|
+
} else return {
|
|
2038
|
+
data: {
|
|
2039
|
+
details: projectDetails.reduce((acc, detail)=>{
|
|
2040
|
+
acc.push(detail.data ?? {
|
|
2041
|
+
project: {
|
|
2042
|
+
name: '',
|
|
2043
|
+
id: '',
|
|
2044
|
+
code: ''
|
|
2045
|
+
}
|
|
2046
|
+
});
|
|
2047
|
+
return acc;
|
|
2048
|
+
}, []),
|
|
2049
|
+
hits: projectIds.length
|
|
2050
|
+
}
|
|
2051
|
+
};
|
|
2052
|
+
}
|
|
2053
|
+
}),
|
|
2054
|
+
getSubmissionGraphQL: builder.query({
|
|
2055
|
+
query: (graphQLParams)=>({
|
|
2056
|
+
url: `${GEN3_SUBMISSION_API}/graphql`,
|
|
2057
|
+
method: 'POST',
|
|
2058
|
+
credentials: 'include',
|
|
2059
|
+
body: JSON.stringify({
|
|
2060
|
+
query: graphQLParams.query,
|
|
2061
|
+
...graphQLParams.variables ? {
|
|
2062
|
+
variables: graphQLParams.variables
|
|
2063
|
+
} : {}
|
|
2064
|
+
})
|
|
2065
|
+
}),
|
|
2066
|
+
transformResponse: (response, _meta, params)=>{
|
|
2067
|
+
if (params.mapping) {
|
|
2068
|
+
return {
|
|
2069
|
+
data: extractValuesFromObject(params.mapping, response.data)
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2072
|
+
return response.data;
|
|
2073
|
+
}
|
|
2074
|
+
}),
|
|
2075
|
+
getDictionary: builder.query({
|
|
2076
|
+
query: ()=>({
|
|
2077
|
+
url: `${GEN3_SUBMISSION_API}/_dictionary/_all/`
|
|
2078
|
+
})
|
|
2079
|
+
})
|
|
2080
|
+
})
|
|
2081
|
+
});
|
|
2082
|
+
const { useGetProjectsQuery, useGetSubmissionGraphQLQuery, useGetProjectsDetailsQuery, useLazyGetProjectsQuery, useLazyGetSubmissionGraphQLQuery, useGetSubmissionsQuery, useGetDictionaryQuery } = submissionApi;
|
|
2083
|
+
|
|
2084
|
+
exports.CoreContext = CoreContext;
|
|
2085
|
+
exports.CoreProvider = CoreProvider;
|
|
2086
|
+
exports.GEN3_API = GEN3_API;
|
|
2087
|
+
exports.GEN3_AUTHZ_API = GEN3_AUTHZ_API;
|
|
2088
|
+
exports.GEN3_COMMONS_NAME = GEN3_COMMONS_NAME;
|
|
2089
|
+
exports.GEN3_CROSSWALK_API = GEN3_CROSSWALK_API;
|
|
2090
|
+
exports.GEN3_DOMAIN = GEN3_DOMAIN;
|
|
2091
|
+
exports.GEN3_DOWNLOADS_ENDPOINT = GEN3_DOWNLOADS_ENDPOINT;
|
|
2092
|
+
exports.GEN3_FENCE_API = GEN3_FENCE_API;
|
|
2093
|
+
exports.GEN3_GUPPY_API = GEN3_GUPPY_API;
|
|
2094
|
+
exports.GEN3_MDS_API = GEN3_MDS_API;
|
|
2095
|
+
exports.GEN3_REDIRECT_URL = GEN3_REDIRECT_URL;
|
|
2096
|
+
exports.GEN3_SUBMISSION_API = GEN3_SUBMISSION_API;
|
|
2097
|
+
exports.GEN3_WORKSPACE_STATUS_API = GEN3_WORKSPACE_STATUS_API;
|
|
2098
|
+
exports.clearCohortFilters = clearCohortFilters;
|
|
2099
|
+
exports.cohortReducer = cohortReducer;
|
|
2100
|
+
exports.convertFilterSetToGqlFilter = convertFilterSetToGqlFilter;
|
|
2101
|
+
exports.coreStore = coreStore;
|
|
2102
|
+
exports.createGen3App = createGen3App;
|
|
2103
|
+
exports.createUseCoreDataHook = createUseCoreDataHook;
|
|
2104
|
+
exports.downloadFromGuppyToBlob = downloadFromGuppyToBlob;
|
|
2105
|
+
exports.downloadJSONDataFromGuppy = downloadJSONDataFromGuppy;
|
|
2106
|
+
exports.drsHostnamesReducer = drsHostnamesReducer;
|
|
2107
|
+
exports.extractEnumFilterValue = extractEnumFilterValue;
|
|
2108
|
+
exports.extractFieldNameFromFullFieldName = extractFieldNameFromFullFieldName;
|
|
2109
|
+
exports.extractFilterValue = extractFilterValue;
|
|
2110
|
+
exports.extractIndexAndFieldNameFromFullFieldName = extractIndexAndFieldNameFromFullFieldName;
|
|
2111
|
+
exports.extractIndexFromFullFieldName = extractIndexFromFullFieldName;
|
|
2112
|
+
exports.fetchFence = fetchFence;
|
|
2113
|
+
exports.fetchJson = fetchJson;
|
|
2114
|
+
exports.fetchUserState = fetchUserState;
|
|
2115
|
+
exports.fieldNameToTitle = fieldNameToTitle;
|
|
2116
|
+
exports.gen3Api = gen3Api;
|
|
2117
|
+
exports.getGen3AppId = getGen3AppId;
|
|
2118
|
+
exports.graphQLAPI = graphQLAPI;
|
|
2119
|
+
exports.graphQLWithTags = graphQLWithTags;
|
|
2120
|
+
exports.guppyAPISliceMiddleware = guppyAPISliceMiddleware;
|
|
2121
|
+
exports.guppyApi = guppyApi;
|
|
2122
|
+
exports.guppyApiReducer = guppyApiReducer;
|
|
2123
|
+
exports.guppyApiSliceReducerPath = guppyApiSliceReducerPath;
|
|
2124
|
+
exports.handleOperation = handleOperation;
|
|
2125
|
+
exports.hideModal = hideModal;
|
|
2126
|
+
exports.isAuthenticated = isAuthenticated;
|
|
2127
|
+
exports.isErrorWithMessage = isErrorWithMessage;
|
|
2128
|
+
exports.isFetchBaseQueryError = isFetchBaseQueryError;
|
|
2129
|
+
exports.isFetchParseError = isFetchParseError;
|
|
2130
|
+
exports.isFilterEmpty = isFilterEmpty;
|
|
2131
|
+
exports.isGuppyAggregationData = isGuppyAggregationData;
|
|
2132
|
+
exports.isHistogramData = isHistogramData;
|
|
2133
|
+
exports.isHistogramDataAArray = isHistogramDataAArray;
|
|
2134
|
+
exports.isHistogramDataAnEnum = isHistogramDataAnEnum;
|
|
2135
|
+
exports.isHistogramDataArray = isHistogramDataArray;
|
|
2136
|
+
exports.isHistogramDataArrayARange = isHistogramDataArrayARange;
|
|
2137
|
+
exports.isHistogramDataArrayAnEnum = isHistogramDataArrayAnEnum;
|
|
2138
|
+
exports.isHistogramDataCollection = isHistogramDataCollection;
|
|
2139
|
+
exports.isHistogramRangeData = isHistogramRangeData;
|
|
2140
|
+
exports.isJSONObject = isJSONObject;
|
|
2141
|
+
exports.isJSONValue = isJSONValue;
|
|
2142
|
+
exports.isJSONValueArray = isJSONValueArray;
|
|
2143
|
+
exports.isOperationWithField = isOperationWithField;
|
|
2144
|
+
exports.isPending = isPending;
|
|
2145
|
+
exports.isProgramUrl = isProgramUrl;
|
|
2146
|
+
exports.isRootUrl = isRootUrl;
|
|
2147
|
+
exports.listifyMethodsFromMapping = listifyMethodsFromMapping;
|
|
2148
|
+
exports.logoutFence = logoutFence;
|
|
2149
|
+
exports.prependIndexToFieldName = prependIndexToFieldName;
|
|
2150
|
+
exports.projectCodeFromResourcePath = projectCodeFromResourcePath;
|
|
2151
|
+
exports.rawDataQueryStrForEachField = rawDataQueryStrForEachField;
|
|
2152
|
+
exports.removeCohortFilter = removeCohortFilter;
|
|
2153
|
+
exports.resetUserState = resetUserState;
|
|
2154
|
+
exports.resourcePathFromProjectID = resourcePathFromProjectID;
|
|
2155
|
+
exports.selectAggDataForIndex = selectAggDataForIndex;
|
|
2156
|
+
exports.selectAuthzMappingData = selectAuthzMappingData;
|
|
2157
|
+
exports.selectCSRFToken = selectCSRFToken;
|
|
2158
|
+
exports.selectCSRFTokenData = selectCSRFTokenData;
|
|
2159
|
+
exports.selectCohortFilters = selectCohortFilters;
|
|
2160
|
+
exports.selectCurrentCohort = selectCurrentCohort;
|
|
2161
|
+
exports.selectCurrentCohortId = selectCurrentCohortId;
|
|
2162
|
+
exports.selectCurrentCohortName = selectCurrentCohortName;
|
|
2163
|
+
exports.selectCurrentMessage = selectCurrentMessage;
|
|
2164
|
+
exports.selectCurrentModal = selectCurrentModal;
|
|
2165
|
+
exports.selectGen3AppById = selectGen3AppById;
|
|
2166
|
+
exports.selectGen3AppMetadataById = selectGen3AppMetadataById;
|
|
2167
|
+
exports.selectHeadersWithCSRFToken = selectHeadersWithCSRFToken;
|
|
2168
|
+
exports.selectIndexFilters = selectIndexFilters;
|
|
2169
|
+
exports.selectIndexedFilterByName = selectIndexedFilterByName;
|
|
2170
|
+
exports.selectUser = selectUser;
|
|
2171
|
+
exports.selectUserAuthStatus = selectUserAuthStatus;
|
|
2172
|
+
exports.selectUserData = selectUserData;
|
|
2173
|
+
exports.selectUserDetails = selectUserDetails;
|
|
2174
|
+
exports.selectUserLoginStatus = selectUserLoginStatus;
|
|
2175
|
+
exports.setDRSHostnames = setDRSHostnames;
|
|
2176
|
+
exports.showModal = showModal;
|
|
2177
|
+
exports.submissionApi = submissionApi;
|
|
2178
|
+
exports.trimFirstFieldNameToTitle = trimFirstFieldNameToTitle;
|
|
2179
|
+
exports.updateCohortFilter = updateCohortFilter;
|
|
2180
|
+
exports.useAddNewCredentialMutation = useAddNewCredentialMutation;
|
|
2181
|
+
exports.useAskQuestionMutation = useAskQuestionMutation;
|
|
2182
|
+
exports.useAuthorizeFromCredentialsMutation = useAuthorizeFromCredentialsMutation;
|
|
2183
|
+
exports.useCoreDispatch = useCoreDispatch;
|
|
2184
|
+
exports.useCoreSelector = useCoreSelector;
|
|
2185
|
+
exports.useCoreStore = useCoreStore;
|
|
2186
|
+
exports.useDownloadFromGuppyMutation = useDownloadFromGuppyMutation;
|
|
2187
|
+
exports.useFetchUserDetailsQuery = useFetchUserDetailsQuery;
|
|
2188
|
+
exports.useGeneralGQLQuery = useGeneralGQLQuery;
|
|
2189
|
+
exports.useGetAISearchStatusQuery = useGetAISearchStatusQuery;
|
|
2190
|
+
exports.useGetAISearchVersionQuery = useGetAISearchVersionQuery;
|
|
2191
|
+
exports.useGetAccessibleDataQuery = useGetAccessibleDataQuery;
|
|
2192
|
+
exports.useGetAggMDSQuery = useGetAggMDSQuery;
|
|
2193
|
+
exports.useGetAggsQuery = useGetAggsQuery;
|
|
2194
|
+
exports.useGetAllFieldsForTypeQuery = useGetAllFieldsForTypeQuery;
|
|
2195
|
+
exports.useGetArrayTypes = useGetArrayTypes;
|
|
2196
|
+
exports.useGetAuthzMappingsQuery = useGetAuthzMappingsQuery;
|
|
2197
|
+
exports.useGetCSRFQuery = useGetCSRFQuery;
|
|
2198
|
+
exports.useGetCountsQuery = useGetCountsQuery;
|
|
2199
|
+
exports.useGetCredentialsQuery = useGetCredentialsQuery;
|
|
2200
|
+
exports.useGetCrosswalkDataQuery = useGetCrosswalkDataQuery;
|
|
2201
|
+
exports.useGetDataQuery = useGetDataQuery;
|
|
2202
|
+
exports.useGetDictionaryQuery = useGetDictionaryQuery;
|
|
2203
|
+
exports.useGetExternalLoginsQuery = useGetExternalLoginsQuery;
|
|
2204
|
+
exports.useGetFieldCountSummaryQuery = useGetFieldCountSummaryQuery;
|
|
2205
|
+
exports.useGetFieldsForIndexQuery = useGetFieldsForIndexQuery;
|
|
2206
|
+
exports.useGetIndexAggMDSQuery = useGetIndexAggMDSQuery;
|
|
2207
|
+
exports.useGetIndexFields = useGetIndexFields;
|
|
2208
|
+
exports.useGetJWKKeysQuery = useGetJWKKeysQuery;
|
|
2209
|
+
exports.useGetLoginProvidersQuery = useGetLoginProvidersQuery;
|
|
2210
|
+
exports.useGetMDSQuery = useGetMDSQuery;
|
|
2211
|
+
exports.useGetProjectsDetailsQuery = useGetProjectsDetailsQuery;
|
|
2212
|
+
exports.useGetProjectsQuery = useGetProjectsQuery;
|
|
2213
|
+
exports.useGetRawDataAndTotalCountsQuery = useGetRawDataAndTotalCountsQuery;
|
|
2214
|
+
exports.useGetStatus = useGetStatus;
|
|
2215
|
+
exports.useGetSubAggsQuery = useGetSubAggsQuery;
|
|
2216
|
+
exports.useGetSubmissionGraphQLQuery = useGetSubmissionGraphQLQuery;
|
|
2217
|
+
exports.useGetSubmissionsQuery = useGetSubmissionsQuery;
|
|
2218
|
+
exports.useGetTagsQuery = useGetTagsQuery;
|
|
2219
|
+
exports.useGetWorkspaceOptionsQuery = useGetWorkspaceOptionsQuery;
|
|
2220
|
+
exports.useGetWorkspacePayModelsQuery = useGetWorkspacePayModelsQuery;
|
|
2221
|
+
exports.useGetWorkspaceStatusQuery = useGetWorkspaceStatusQuery;
|
|
2222
|
+
exports.useGraphQLQuery = useGraphQLQuery;
|
|
2223
|
+
exports.useIsExternalConnectedQuery = useIsExternalConnectedQuery;
|
|
2224
|
+
exports.useIsUserLoggedIn = useIsUserLoggedIn;
|
|
2225
|
+
exports.useLazyFetchUserDetailsQuery = useLazyFetchUserDetailsQuery;
|
|
2226
|
+
exports.useLazyGeneralGQLQuery = useLazyGeneralGQLQuery;
|
|
2227
|
+
exports.useLazyGetCrosswalkDataQuery = useLazyGetCrosswalkDataQuery;
|
|
2228
|
+
exports.useLazyGetExternalLoginsQuery = useLazyGetExternalLoginsQuery;
|
|
2229
|
+
exports.useLazyGetProjectsQuery = useLazyGetProjectsQuery;
|
|
2230
|
+
exports.useLazyGetSubmissionGraphQLQuery = useLazyGetSubmissionGraphQLQuery;
|
|
2231
|
+
exports.useLazyIsExternalConnectedQuery = useLazyIsExternalConnectedQuery;
|
|
2232
|
+
exports.usePrevious = usePrevious;
|
|
2233
|
+
exports.useRemoveCredentialMutation = useRemoveCredentialMutation;
|
|
2234
|
+
exports.useUser = useUser;
|
|
2235
|
+
exports.useUserAuth = useUserAuth;
|
|
2236
|
+
exports.userHasCreateOrUpdateOnAnyProject = userHasCreateOrUpdateOnAnyProject;
|
|
2237
|
+
exports.userHasDataUpload = userHasDataUpload;
|
|
2238
|
+
exports.userHasMethodForServiceOnProject = userHasMethodForServiceOnProject;
|
|
2239
|
+
exports.userHasMethodForServiceOnResource = userHasMethodForServiceOnResource;
|
|
2240
|
+
exports.userHasMethodOnAnyProject = userHasMethodOnAnyProject;
|
|
2241
|
+
exports.userHasSheepdogProgramAdmin = userHasSheepdogProgramAdmin;
|
|
2242
|
+
exports.userHasSheepdogProjectAdmin = userHasSheepdogProjectAdmin;
|
|
2243
|
+
//# sourceMappingURL=index.js.map
|