@gen3/core 0.10.54 → 0.10.55

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.
Files changed (40) hide show
  1. package/dist/cjs/index.js +1866 -1057
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/dts/constants.d.ts +1 -0
  4. package/dist/dts/features/aiSearch/aiSearchSlice.d.ts +10 -10
  5. package/dist/dts/features/authz/authzMappingSlice.d.ts +16 -16
  6. package/dist/dts/features/dataLibrary/dataLibraryApi.d.ts +1085 -0
  7. package/dist/dts/features/dataLibrary/dataLibraryIndexDB.d.ts +34 -0
  8. package/dist/dts/features/dataLibrary/dataLibrarySelectionSlice.d.ts +30 -0
  9. package/dist/dts/features/dataLibrary/index.d.ts +6 -0
  10. package/dist/dts/features/dataLibrary/types.d.ts +86 -0
  11. package/dist/dts/features/dataLibrary/useDataLibrary.d.ts +13 -0
  12. package/dist/dts/features/dataLibrary/utils.d.ts +13 -0
  13. package/dist/dts/features/download/downloadStatusApi.d.ts +11 -11
  14. package/dist/dts/features/fence/credentialsApi.d.ts +30 -30
  15. package/dist/dts/features/fence/fenceApi.d.ts +263 -1
  16. package/dist/dts/features/fence/index.d.ts +2 -2
  17. package/dist/dts/features/filters/types.d.ts +1 -0
  18. package/dist/dts/features/gen3Apps/Gen3AppRTKQ.d.ts +1 -1
  19. package/dist/dts/features/graphQL/graphQLSlice.d.ts +1 -1
  20. package/dist/dts/features/guppy/guppyDownloadSlice.d.ts +10 -10
  21. package/dist/dts/features/guppy/guppySlice.d.ts +1 -0
  22. package/dist/dts/features/guppy/tests/guppySlice.unit.test.d.ts +1 -0
  23. package/dist/dts/features/guppy/tests/jsonpath.unit.test.d.ts +1 -0
  24. package/dist/dts/features/sower/sowerApi.d.ts +10 -10
  25. package/dist/dts/features/submission/submissionApi.d.ts +1 -1
  26. package/dist/dts/features/user/hooks.d.ts +1 -1
  27. package/dist/dts/features/user/userSliceRTK.d.ts +47 -28
  28. package/dist/dts/features/user/utils.d.ts +9 -0
  29. package/dist/dts/features/workspace/workspaceApi.d.ts +58 -58
  30. package/dist/dts/hooks.d.ts +2 -0
  31. package/dist/dts/index.d.ts +2 -0
  32. package/dist/dts/reducers.d.ts +2 -0
  33. package/dist/dts/store.d.ts +2 -0
  34. package/dist/dts/utils/fetch.d.ts +17 -0
  35. package/dist/dts/utils/index.d.ts +3 -1
  36. package/dist/dts/utils/url.d.ts +7 -0
  37. package/dist/esm/index.js +1844 -1061
  38. package/dist/esm/index.js.map +1 -1
  39. package/dist/index.d.ts +1325 -156
  40. package/package.json +2 -3
package/dist/cjs/index.js CHANGED
@@ -7,14 +7,15 @@ var query = require('@reduxjs/toolkit/query');
7
7
  var React = require('react');
8
8
  var reactRedux = require('react-redux');
9
9
  var lodash = require('lodash');
10
- var Queue = require('queue');
10
+ var idb = require('idb');
11
+ var useSWR = require('swr');
11
12
  var jsonpathPlus = require('jsonpath-plus');
13
+ var flat = require('flat');
14
+ var Papa = require('papaparse');
15
+ var Queue = require('queue');
12
16
  var uuid = require('uuid');
13
17
  var reduxPersist = require('redux-persist');
14
18
  var reactCookie = require('react-cookie');
15
- var useSWR = require('swr');
16
- var flat = require('flat');
17
- var Papa = require('papaparse');
18
19
 
19
20
  function _interopNamespaceDefault(e) {
20
21
  var n = Object.create(null);
@@ -50,6 +51,7 @@ const GEN3_REDIRECT_URL = process.env.NEXT_PUBLIC_GEN3_REDIRECT_URL || GEN3_API;
50
51
  const GEN3_WORKSPACE_API = process.env.NEXT_PUBLIC_GEN3_WORKSPACE_STATUS_API || `${GEN3_API}/lw-workspace`;
51
52
  const GEN3_SUBMISSION_API = process.env.NEXT_PUBLIC_GEN3_SUBMISSION_API || `${GEN3_API}/api/v0/submission`;
52
53
  const GEN3_WTS_API = process.env.NEXT_PUBLIC_GEN3_WTS_API || `${GEN3_API}/wts`;
54
+ const GEN3_DATA_LIBRARY_API = process.env.NEXT_PUBLIC_GEN3_DATA_LIBRARY_API || `${GEN3_API}/library/lists`;
53
55
  const GEN3_CROSSWALK_API = process.env.NEXT_PUBLIC_GEN3_CROSSWALK_API || `${GEN3_API}/mds`;
54
56
  const GEN3_SOWER_API = process.env.NEXT_PUBLIC_GEN3_SOWER_API || `${GEN3_API}/jobs`;
55
57
  exports.Accessibility = void 0;
@@ -63,6 +65,10 @@ const FILE_DELIMITERS = {
63
65
  csv: ','
64
66
  };
65
67
 
68
+ // import { selectCSRFToken } from '../user/userSliceRTK';
69
+ // import { coreStore } from '../../store';
70
+ // import { prepareUrl } from '../../utils';
71
+ // import { getCookie } from 'cookies-next';
66
72
  /**
67
73
  * Template for fence error response dict
68
74
  * @returns: An error dict response from a RESTFUL API request
@@ -94,7 +100,59 @@ const FILE_DELIMITERS = {
94
100
  headers,
95
101
  body
96
102
  });
103
+ }; /*
104
+ interface DownloadFromFenceParameters {
105
+ guid: string;
106
+ method?: 'GET' | 'POST';
107
+ onStart?: () => void; // function to call when the download starts
108
+ onDone?: (blob: Blob) => void; // function to call when the download is done
109
+ onError?: (error: Error) => void; // function to call when the download fails
110
+ onAbort?: () => void; // function to call when the download is aborted
111
+ signal?: AbortSignal; // AbortSignal to use for the request
112
+ }
113
+
114
+
115
+ export const fetchFencePresignedURL = async ({
116
+ guid,
117
+ method = 'GET',
118
+ onAbort = () => null,
119
+ signal = undefined,
120
+ }: DownloadFromFenceParameters) => {
121
+ const csrfToken = selectCSRFToken(coreStore.getState());
122
+
123
+ const headers = new Headers();
124
+ headers.set('Content-Type', 'application/json');
125
+ if (csrfToken) headers.set('X-CSRF-Token', csrfToken);
126
+ let accessToken = undefined;
127
+ if (process.env.NODE_ENV === 'development') {
128
+ // NOTE: This cookie can only be accessed from the client side
129
+ // in development mode. Otherwise, the cookie is set as httpOnly
130
+ accessToken = getCookie('credentials_token');
131
+ }
132
+ if (csrfToken) headers.set('X-CSRF-Token', csrfToken);
133
+ if (accessToken) headers.set('Authorization', `Bearer ${accessToken}`);
134
+ const url = prepareUrl(`${GEN3_FENCE_API}/user/download/${guid}`);
135
+
136
+ try {
137
+ const response = await fetch(url.toString(), {
138
+ method: method,
139
+ headers: headers,
140
+ ...(signal ? { signal: signal } : {}),
141
+ } as RequestInit);
142
+
143
+ const jsonData = await response.json();
144
+ // convert the data to the specified format and return a Blob
145
+ return jsonData;
146
+ } catch (error: any) {
147
+ // Abort is handle as an exception
148
+ if (error.name == 'AbortError') {
149
+ // handle abort()
150
+ onAbort?.();
151
+ }
152
+ throw new Error(error);
153
+ }
97
154
  };
155
+ */
98
156
 
99
157
  const userAuthApi = react.createApi({
100
158
  reducerPath: 'userAuthApi',
@@ -224,10 +282,13 @@ const gen3ServicesReducerMiddleware = gen3Api.middleware;
224
282
  endpoints: (builder)=>({
225
283
  getLoginProviders: builder.query({
226
284
  query: ()=>`${GEN3_FENCE_API}/user/login`
285
+ }),
286
+ getDownload: builder.query({
287
+ query: (guid)=>`${GEN3_FENCE_API}/user/data/download/${guid}`
227
288
  })
228
289
  })
229
290
  });
230
- const { useGetLoginProvidersQuery } = loginProvidersApi;
291
+ const { useGetLoginProvidersQuery, useGetDownloadQuery, useLazyGetDownloadQuery } = loginProvidersApi;
231
292
  /**
232
293
  * Logout from fence
233
294
  */ const logoutFence = async (redirect = '/')=>await fetchFence({
@@ -411,7 +472,7 @@ const createUseCoreDataHook = (fetchDataActionCreator, dataSelector)=>{
411
472
  });
412
473
  const isAuthenticated = (loginStatus)=>loginStatus === 'authenticated';
413
474
  const isPending = (loginStatus)=>loginStatus === 'pending';
414
- const initialState$4 = {
475
+ const initialState$5 = {
415
476
  status: 'uninitialized',
416
477
  loginStatus: 'unauthenticated',
417
478
  error: undefined
@@ -422,9 +483,9 @@ const initialState$4 = {
422
483
  * @returns: status messages wrapped around fetchUserState response dict
423
484
  */ const slice$4 = toolkit.createSlice({
424
485
  name: 'fence/user',
425
- initialState: initialState$4,
486
+ initialState: initialState$5,
426
487
  reducers: {
427
- resetUserState: ()=>initialState$4
488
+ resetUserState: ()=>initialState$5
428
489
  },
429
490
  extraReducers: (builder)=>{
430
491
  builder.addCase(fetchUserState.fulfilled, (_, action)=>{
@@ -594,12 +655,12 @@ const lookupGen3App = (id)=>{
594
655
  return REGISTRY[id];
595
656
  };
596
657
 
597
- const initialState$3 = {
658
+ const initialState$4 = {
598
659
  gen3Apps: {}
599
660
  };
600
661
  const slice$3 = toolkit.createSlice({
601
662
  name: 'gen3Apps',
602
- initialState: initialState$3,
663
+ initialState: initialState$4,
603
664
  reducers: {
604
665
  addGen3AppMetadata: (state, action)=>{
605
666
  const { name, requiredEntityTypes } = action.payload;
@@ -618,11 +679,11 @@ const { addGen3AppMetadata } = slice$3.actions;
618
679
  const selectGen3AppMetadataByName = (state, appName)=>state.gen3Apps.gen3Apps[appName];
619
680
  const selectGen3AppByName = (appName)=>lookupGen3App(appName); // TODO: memoize this selector
620
681
 
621
- const initialState$2 = {};
682
+ const initialState$3 = {};
622
683
  // TODO: document what this does
623
684
  const slice$2 = toolkit.createSlice({
624
685
  name: 'drsResolver',
625
- initialState: initialState$2,
686
+ initialState: initialState$3,
626
687
  reducers: {
627
688
  setDRSHostnames: (_state, action)=>{
628
689
  return action.payload;
@@ -640,13 +701,13 @@ exports.Modals = void 0;
640
701
  Modals["CreateCredentialsAPIKeyModal"] = "CreateCredentialsAPIKeyModal";
641
702
  Modals["GeneralErrorModal"] = "GeneralErrorModal";
642
703
  })(exports.Modals || (exports.Modals = {}));
643
- const initialState$1 = {
704
+ const initialState$2 = {
644
705
  currentModal: null
645
706
  };
646
707
  //Creates a modal slice for tracking showModal and hideModal state.
647
708
  const slice$1 = toolkit.createSlice({
648
709
  name: 'modals',
649
- initialState: initialState$1,
710
+ initialState: initialState$2,
650
711
  reducers: {
651
712
  showModal: (state, action)=>{
652
713
  state.currentModal = action.payload.modal;
@@ -786,6 +847,587 @@ const EmptyFilterSet = {
786
847
  };
787
848
  const cohortReducer = cohortSlice.reducer;
788
849
 
850
+ const isFileItem = (item)=>{
851
+ return item && 'guid' in item;
852
+ };
853
+ const isAdditionalDataItem = (item)=>{
854
+ return item.itemType === 'AdditionalData'; // TODO resolve this with type from the api
855
+ };
856
+ // Type guard for CohortItem
857
+ const isCohortItem = (item)=>{
858
+ return item && 'data' in item && 'schemaVersion' in item && item.itemType === 'Gen3GraphQL';
859
+ };
860
+
861
+ const processItem = (id, data)=>{
862
+ if (data?.type === 'AdditionalData') {
863
+ return {
864
+ name: data.name,
865
+ itemType: 'AdditionalData',
866
+ description: data?.description,
867
+ documentationUrl: data?.documentationUrl,
868
+ url: data?.url
869
+ };
870
+ }
871
+ return {
872
+ ...data,
873
+ itemType: 'Data',
874
+ guid: data.id,
875
+ id: id
876
+ };
877
+ };
878
+ const BuildList = (listId, listData)=>{
879
+ if (!Object.keys(listData).includes('items')) return undefined;
880
+ const items = Object.entries(listData?.items).reduce((acc, [id, data])=>{
881
+ if (data?.type === 'Gen3GraphQL') {
882
+ acc.items[id] = {
883
+ itemType: 'Gen3GraphQL',
884
+ id: data.guid,
885
+ schemaVersion: data.schema_version,
886
+ data: data.data,
887
+ name: data.name,
888
+ index: data.index
889
+ };
890
+ } else {
891
+ if (!(data.dataset_guid in acc.items)) {
892
+ acc.items[data.dataset_guid] = {
893
+ id: data.dataset_guid,
894
+ name: '',
895
+ items: {
896
+ [id]: processItem(id, data)
897
+ }
898
+ };
899
+ } else {
900
+ acc.items[data.dataset_guid].items[id] = processItem(id, data);
901
+ }
902
+ }
903
+ return acc;
904
+ }, {
905
+ items: {},
906
+ version: listData?.version ?? 0,
907
+ createdTime: listData?.created_time,
908
+ updatedTime: listData?.updated_time,
909
+ name: listData?.name ?? listId,
910
+ id: listId,
911
+ authz: {
912
+ version: listData.authz.version,
913
+ authz: listData.authz
914
+ }
915
+ });
916
+ return items;
917
+ };
918
+ const BuildLists = (data)=>{
919
+ return Object.entries(data?.lists).reduce((acc, [listId, listData])=>{
920
+ const list = BuildList(listId, listData);
921
+ if (list) acc[listId] = list;
922
+ return acc;
923
+ }, {});
924
+ };
925
+ /**
926
+ * Calculates the total number of items within a DataList object.
927
+ *
928
+ * @param {DataList} dataList - The DataList object to count items from.
929
+ * @return {number} The total number of items in the DataList.
930
+ */ const getNumberOfItemsInDatalist = (dataList)=>{
931
+ if (!dataList?.items) return 0;
932
+ return Object.values(dataList.items).reduce((count, item)=>{
933
+ if (isCohortItem(item)) {
934
+ return count + 1;
935
+ } else {
936
+ return count + Object.values(item?.items ?? {}).reduce((fileCount, x)=>{
937
+ if (isFileItem(x)) {
938
+ return fileCount + 1;
939
+ }
940
+ return fileCount;
941
+ }, 0);
942
+ }
943
+ }, 0);
944
+ };
945
+ const getTimestamp = ()=>{
946
+ return new Date(Date.now()).toLocaleString();
947
+ };
948
+ const flattenDataList = (dataList)=>{
949
+ // convert datalist into user-data-library for for updating.
950
+ const items = Object.entries(dataList.items).reduce((acc, [id, value])=>{
951
+ if (isCohortItem(value)) {
952
+ acc[id] = value;
953
+ } else {
954
+ return {
955
+ ...acc,
956
+ ...value.items
957
+ };
958
+ }
959
+ return acc;
960
+ }, {});
961
+ return {
962
+ name: dataList.name,
963
+ items: items
964
+ };
965
+ };
966
+
967
+ const TAGS = 'dataLibrary';
968
+ const dataLibraryTags = gen3Api.enhanceEndpoints({
969
+ addTagTypes: [
970
+ TAGS
971
+ ]
972
+ });
973
+ /**
974
+ * RTKQuery hooks for data-library list CRUD operations.
975
+ * @param getLists returns all the list in the user's data library
976
+ * @param getList args: id returns the list for the given id
977
+ * @param addLists args: Record<string, ListItemDefinition> populates the whole datalibrary
978
+ * @param addList args: id ListItemDefinition creates a new list
979
+ * @param updateList args: id ListItemDefinition updates a list
980
+ * @param deleteList args: id deletes the list by id
981
+ */ const dataLibraryApi = dataLibraryTags.injectEndpoints({
982
+ endpoints: (builder)=>({
983
+ getDataLibraryLists: builder.query({
984
+ query: ()=>`${GEN3_DATA_LIBRARY_API}`,
985
+ transformResponse: (res)=>{
986
+ return {
987
+ lists: BuildLists(res)
988
+ };
989
+ },
990
+ transformErrorResponse (response) {
991
+ if ('originalStatus' in response) return {
992
+ status: response.originalStatus,
993
+ data: response.data
994
+ };
995
+ return response;
996
+ }
997
+ }),
998
+ getDataLibraryList: builder.query({
999
+ query: (id)=>`${GEN3_DATA_LIBRARY_API}/${id}`,
1000
+ transformResponse: (res)=>BuildLists(res)?.lists
1001
+ }),
1002
+ addAllDataLibraryLists: builder.mutation({
1003
+ query: (lists)=>({
1004
+ url: `${GEN3_DATA_LIBRARY_API}`,
1005
+ method: 'POST',
1006
+ body: lists
1007
+ }),
1008
+ invalidatesTags: [
1009
+ TAGS
1010
+ ],
1011
+ transformErrorResponse (response) {
1012
+ if ('originalStatus' in response) return {
1013
+ status: response.originalStatus,
1014
+ data: response.data
1015
+ };
1016
+ return response;
1017
+ }
1018
+ }),
1019
+ addDataLibraryList: builder.mutation({
1020
+ query: (list)=>({
1021
+ url: `${GEN3_DATA_LIBRARY_API}/${toolkit.nanoid()}`,
1022
+ method: 'POST',
1023
+ body: list ?? {}
1024
+ }),
1025
+ transformErrorResponse (response) {
1026
+ if ('originalStatus' in response) return {
1027
+ status: response.originalStatus,
1028
+ data: response.data
1029
+ };
1030
+ return response;
1031
+ },
1032
+ invalidatesTags: [
1033
+ TAGS
1034
+ ]
1035
+ }),
1036
+ updateDataLibraryList: builder.mutation({
1037
+ query: ({ id, list })=>({
1038
+ url: `${GEN3_DATA_LIBRARY_API}/${id}`,
1039
+ method: 'PUT',
1040
+ body: list
1041
+ }),
1042
+ transformErrorResponse (response) {
1043
+ if ('originalStatus' in response) return {
1044
+ status: response.originalStatus,
1045
+ data: response.data
1046
+ };
1047
+ return response;
1048
+ },
1049
+ invalidatesTags: [
1050
+ TAGS
1051
+ ]
1052
+ }),
1053
+ deleteDataLibraryList: builder.mutation({
1054
+ query: (id)=>({
1055
+ url: `${GEN3_DATA_LIBRARY_API}/${id}`,
1056
+ method: 'DELETE'
1057
+ }),
1058
+ transformErrorResponse (response) {
1059
+ if ('originalStatus' in response) return {
1060
+ status: response.originalStatus,
1061
+ data: response.data
1062
+ };
1063
+ return response;
1064
+ },
1065
+ invalidatesTags: [
1066
+ TAGS
1067
+ ]
1068
+ }),
1069
+ deleteAllDataLibrary: builder.mutation({
1070
+ query: ()=>({
1071
+ url: `${GEN3_DATA_LIBRARY_API}`,
1072
+ method: 'DELETE'
1073
+ }),
1074
+ transformErrorResponse (response) {
1075
+ if ('originalStatus' in response) return {
1076
+ status: response.originalStatus,
1077
+ data: response.data
1078
+ };
1079
+ return response;
1080
+ },
1081
+ invalidatesTags: [
1082
+ TAGS
1083
+ ]
1084
+ })
1085
+ })
1086
+ });
1087
+ const { useGetDataLibraryListQuery, useGetDataLibraryListsQuery, useAddDataLibraryListMutation, useAddAllDataLibraryListsMutation, useDeleteDataLibraryListMutation, useDeleteAllDataLibraryMutation, useUpdateDataLibraryListMutation } = dataLibraryApi;
1088
+
1089
+ const DATABASE_NAME = 'Gen3DataLibrary';
1090
+ const STORE_NAME = 'DataLibraryLists';
1091
+ const getDb = async ()=>{
1092
+ return idb.openDB(DATABASE_NAME, 1, {
1093
+ upgrade (db) {
1094
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
1095
+ db.createObjectStore(STORE_NAME, {
1096
+ keyPath: 'id'
1097
+ });
1098
+ }
1099
+ }
1100
+ });
1101
+ };
1102
+ /**
1103
+ * Deletes a list from the database.
1104
+ *
1105
+ * @async
1106
+ * @param {string} id - The unique identifier of the list to delete.
1107
+ * @returns {Promise<ReturnStatus>} The status of the deletion operation.
1108
+ * @throws {Error} If the list with the provided id does not exist.
1109
+ */ const deleteListIndexDB = async (id)=>{
1110
+ try {
1111
+ const db = await getDb();
1112
+ const tx = db.transaction(STORE_NAME, 'readwrite');
1113
+ const store = tx.objectStore(STORE_NAME);
1114
+ const item = await store.get(id);
1115
+ if (!item) {
1116
+ throw new Error(`List ${id} does not exist`);
1117
+ }
1118
+ store.delete(id);
1119
+ await tx.done;
1120
+ return {
1121
+ status: `${id} deleted`
1122
+ };
1123
+ } catch (error) {
1124
+ return {
1125
+ isError: true
1126
+ };
1127
+ }
1128
+ };
1129
+ const deleteAll = async ()=>{
1130
+ try {
1131
+ const db = await getDb();
1132
+ const tx = db.transaction(STORE_NAME, 'readwrite');
1133
+ tx.objectStore(STORE_NAME).clear();
1134
+ await tx.done;
1135
+ return {
1136
+ status: 'list added'
1137
+ };
1138
+ } catch (error) {
1139
+ return {
1140
+ isError: true,
1141
+ status: `unable to clear library`
1142
+ };
1143
+ }
1144
+ };
1145
+ const addListToDataLibraryIndexDB = async (body)=>{
1146
+ const timestamp = getTimestamp();
1147
+ try {
1148
+ const db = await getDb();
1149
+ const tx = db.transaction(STORE_NAME, 'readwrite');
1150
+ const id = toolkit.nanoid(); // Create an id for the list
1151
+ tx.objectStore(STORE_NAME).put({
1152
+ id,
1153
+ version: 0,
1154
+ items: body?.items ?? {},
1155
+ creator: '{{subject_id}}',
1156
+ authz: {
1157
+ version: 0,
1158
+ authz: [
1159
+ `/users/{{subject_id}}/user-library/lists/${id}`
1160
+ ]
1161
+ },
1162
+ name: body?.name ?? 'New List',
1163
+ created_time: timestamp,
1164
+ updated_time: timestamp
1165
+ });
1166
+ await tx.done;
1167
+ return {
1168
+ status: 'list added'
1169
+ };
1170
+ } catch (error) {
1171
+ return {
1172
+ isError: true,
1173
+ status: `unable to add list`
1174
+ };
1175
+ }
1176
+ };
1177
+ const updateListIndexDB = async (id, list)=>{
1178
+ try {
1179
+ const db = await getDb();
1180
+ const tx = db.transaction(STORE_NAME, 'readwrite');
1181
+ const store = tx.objectStore(STORE_NAME);
1182
+ const listData = await store.get(id);
1183
+ if (!listData) {
1184
+ throw new Error(`List ${id} does not exist`);
1185
+ }
1186
+ const timestamp = getTimestamp();
1187
+ const version = listData.version ? listData.version + 1 : 0;
1188
+ const updated = {
1189
+ ...listData,
1190
+ ...list,
1191
+ version: version,
1192
+ updated_time: timestamp,
1193
+ created_time: listData.created_time
1194
+ };
1195
+ store.put(updated);
1196
+ await tx.done;
1197
+ return {
1198
+ status: 'success'
1199
+ };
1200
+ } catch (error) {
1201
+ let errorMessage = 'An unknown error occurred';
1202
+ if (error instanceof Error) {
1203
+ errorMessage = error.message;
1204
+ }
1205
+ return {
1206
+ isError: true,
1207
+ status: `Unable to update list: ${id}. Error: ${errorMessage}`
1208
+ };
1209
+ }
1210
+ };
1211
+ const addAllListIndexDB = async (data)=>{
1212
+ if (!Object.keys(data).includes('lists') || !lodash.isArray(data['lists'])) {
1213
+ return {
1214
+ isError: true,
1215
+ status: 'lists not found in request'
1216
+ };
1217
+ }
1218
+ const timestamp = getTimestamp();
1219
+ const allLists = data['lists'].reduce((acc, x)=>{
1220
+ if (!isJSONObject(x)) return acc;
1221
+ const id = toolkit.nanoid(10);
1222
+ acc[id] = {
1223
+ ...x,
1224
+ version: 0,
1225
+ created_time: timestamp,
1226
+ updated_time: timestamp,
1227
+ creator: '{{subject_id}}',
1228
+ authz: {
1229
+ version: 0,
1230
+ authz: [
1231
+ `/users/{{subject_id}}/user-library/lists/${id}`
1232
+ ]
1233
+ }
1234
+ };
1235
+ return acc;
1236
+ }, {});
1237
+ try {
1238
+ const db = await getDb();
1239
+ const tx = db.transaction(STORE_NAME, 'readwrite');
1240
+ for (const [id, list] of Object.entries(allLists)){
1241
+ tx.objectStore(STORE_NAME).put({
1242
+ id,
1243
+ ...list
1244
+ });
1245
+ }
1246
+ await tx.done;
1247
+ return {
1248
+ status: 'success'
1249
+ };
1250
+ } catch (error) {
1251
+ return {
1252
+ isError: true,
1253
+ status: 'unable to add lists'
1254
+ };
1255
+ }
1256
+ };
1257
+ const getDataLibraryListIndexDB = async (id)=>{
1258
+ try {
1259
+ const db = await getDb();
1260
+ const tx = db.transaction(STORE_NAME, 'readonly');
1261
+ const store = tx.objectStore(STORE_NAME);
1262
+ if (id !== undefined) ; else {
1263
+ const lists = await store.getAll();
1264
+ const listMap = lists.reduce((acc, x)=>{
1265
+ acc[x.id] = {
1266
+ ...x,
1267
+ items: x.items
1268
+ };
1269
+ return acc;
1270
+ }, {});
1271
+ const datalists = BuildLists({
1272
+ lists: listMap
1273
+ });
1274
+ return {
1275
+ status: 'success',
1276
+ lists: datalists
1277
+ };
1278
+ }
1279
+ } catch (error) {
1280
+ return {
1281
+ isError: true
1282
+ };
1283
+ }
1284
+ };
1285
+
1286
+ const useDataLibrary = (useApi)=>{
1287
+ const [localLibrary, setLocalLibrary] = React.useState({});
1288
+ const { data: apiLibrary, refetch: refetchLibraryFromApi, error: apiError, isError: isAPIListError, isLoading: isAPIListLoading } = useGetDataLibraryListsQuery(undefined, {
1289
+ skip: !useApi
1290
+ });
1291
+ const [addItemToLibraryApi] = useAddDataLibraryListMutation();
1292
+ const [addAllItemsToLibraryApi] = useAddAllDataLibraryListsMutation();
1293
+ const [deleteItemInLibraryApi] = useDeleteDataLibraryListMutation();
1294
+ const [updateItemInLibraryApi, { isLoading: isUpdatingLoading }] = useUpdateDataLibraryListMutation();
1295
+ const [deleteAllApi] = useDeleteAllDataLibraryMutation();
1296
+ let hasError = false;
1297
+ let errorData = null;
1298
+ let isLoading = false;
1299
+ // TODO: Add error message from indexedDB
1300
+ if (useApi && isAPIListError) {
1301
+ hasError = true;
1302
+ errorData = apiError;
1303
+ }
1304
+ if (useApi && (isUpdatingLoading || isAPIListLoading)) {
1305
+ isLoading = true;
1306
+ }
1307
+ const generateUniqueName = (baseName = 'List')=>{
1308
+ let uniqueName = baseName;
1309
+ let counter = 1;
1310
+ const existingNames = dataLibrary ? Object.values(dataLibrary).map((x)=>x.name) : [];
1311
+ while(existingNames.includes(uniqueName)){
1312
+ uniqueName = `${baseName} ${counter}`;
1313
+ counter++;
1314
+ }
1315
+ return uniqueName;
1316
+ };
1317
+ const refetchLocalLists = async ()=>{
1318
+ const { isError, lists } = await getDataLibraryListIndexDB();
1319
+ setLocalLibrary(lists ?? {});
1320
+ hasError = isError === true;
1321
+ };
1322
+ React.useEffect(()=>{
1323
+ const fetchLibrary = async ()=>{
1324
+ if (!useApi) {
1325
+ const { isError, lists } = await getDataLibraryListIndexDB();
1326
+ if (!isError) {
1327
+ setLocalLibrary(lists ?? {});
1328
+ }
1329
+ }
1330
+ };
1331
+ fetchLibrary();
1332
+ }, [
1333
+ useApi
1334
+ ]);
1335
+ const addListToDataLibrary = async (item)=>{
1336
+ const adjustedData = {
1337
+ ...item ?? {},
1338
+ name: generateUniqueName(item?.name ?? 'List')
1339
+ };
1340
+ if (useApi) {
1341
+ await addItemToLibraryApi(adjustedData);
1342
+ refetchLibraryFromApi();
1343
+ } else {
1344
+ const { isError } = await addListToDataLibraryIndexDB(adjustedData);
1345
+ await refetchLocalLists();
1346
+ hasError = isError === true;
1347
+ }
1348
+ };
1349
+ const deleteListFromDataLibrary = async (id)=>{
1350
+ if (useApi) {
1351
+ await deleteItemInLibraryApi(id);
1352
+ refetchLibraryFromApi();
1353
+ } else {
1354
+ const { isError } = await deleteListIndexDB(id);
1355
+ refetchLocalLists();
1356
+ hasError = isError === true;
1357
+ }
1358
+ };
1359
+ const setAllListsInDataLibrary = async (data)=>{
1360
+ if (useApi) {
1361
+ await addAllItemsToLibraryApi(data);
1362
+ refetchLibraryFromApi();
1363
+ } else {
1364
+ const { isError } = await addAllListIndexDB(data);
1365
+ refetchLocalLists();
1366
+ hasError = isError === true;
1367
+ }
1368
+ };
1369
+ const updateListInDataLibrary = async (id, data)=>{
1370
+ const flattend = flattenDataList(data);
1371
+ if (useApi) {
1372
+ await updateItemInLibraryApi({
1373
+ id,
1374
+ list: flattend
1375
+ });
1376
+ refetchLibraryFromApi();
1377
+ } else {
1378
+ const { isError } = await updateListIndexDB(id, data);
1379
+ refetchLocalLists();
1380
+ hasError = isError === true;
1381
+ }
1382
+ };
1383
+ const clearLibrary = async ()=>{
1384
+ if (useApi) {
1385
+ await deleteAllApi();
1386
+ refetchLibraryFromApi();
1387
+ } else {
1388
+ const { isError } = await deleteAll();
1389
+ refetchLocalLists();
1390
+ hasError = isError === true;
1391
+ }
1392
+ };
1393
+ const dataLibrary = useApi ? apiLibrary ? apiLibrary.lists : {} : localLibrary;
1394
+ return {
1395
+ dataLibrary,
1396
+ isError: hasError,
1397
+ isLoading,
1398
+ error: errorData,
1399
+ addListToDataLibrary,
1400
+ deleteListFromDataLibrary,
1401
+ clearLibrary,
1402
+ setAllListsInDataLibrary,
1403
+ updateListInDataLibrary
1404
+ };
1405
+ };
1406
+
1407
+ const initialState$1 = {};
1408
+ const dataLibrarySlice = toolkit.createSlice({
1409
+ name: 'dataLibrary',
1410
+ initialState: initialState$1,
1411
+ reducers: {
1412
+ setDataLibraryListSelection: (state, action)=>{
1413
+ const { listId, itemIds } = action.payload;
1414
+ state[listId] = itemIds;
1415
+ },
1416
+ clearDataLibrarySelection: ()=>{
1417
+ return initialState$1;
1418
+ }
1419
+ }
1420
+ });
1421
+ const { setDataLibraryListSelection, clearDataLibrarySelection } = dataLibrarySlice.actions;
1422
+ const dataLibrarySelectionReducer = dataLibrarySlice.reducer;
1423
+ // Selector
1424
+ const selectDataLibrary = (state)=>state.dataLibrarySelection;
1425
+ // Memoized selector for getting data library items for a specific root object
1426
+ toolkit.createSelector([
1427
+ selectDataLibrary,
1428
+ (_, rootObjectId)=>rootObjectId
1429
+ ], (dataLibrary, rootObjectId)=>dataLibrary[rootObjectId] || []);
1430
+
789
1431
  exports.WorkspaceStatus = void 0;
790
1432
  (function(WorkspaceStatus) {
791
1433
  WorkspaceStatus["Launching"] = "Launching";
@@ -909,1127 +1551,1268 @@ const guppyAPISliceMiddleware = guppyApi.middleware;
909
1551
  const guppyApiSliceReducerPath = guppyApi.reducerPath;
910
1552
  const guppyApiReducer = guppyApi.reducer;
911
1553
 
912
- const rootReducer = toolkit.combineReducers({
913
- gen3Services: gen3ServicesReducer,
914
- user: userReducer,
915
- gen3Apps: gen3AppReducer,
916
- drsHostnames: drsHostnamesReducer,
917
- modals: modalReducer,
918
- cohorts: cohortReducer,
919
- activeWorkspace: activeWorkspaceReducer,
920
- [guppyApiSliceReducerPath]: guppyApiReducer,
921
- [userAuthApiReducerPath]: userAuthApiReducer
922
- });
923
-
924
- const coreStore = toolkit.configureStore({
925
- reducer: rootReducer,
926
- middleware: (getDefaultMiddleware)=>getDefaultMiddleware().concat(gen3ServicesReducerMiddleware, guppyAPISliceMiddleware, userAuthApiMiddleware)
927
- });
928
- query.setupListeners(coreStore.dispatch);
929
-
930
- const CoreProvider = ({ children })=>{
931
- return /*#__PURE__*/ React.createElement(reactRedux.Provider, {
932
- store: coreStore
933
- }, children);
1554
+ const isOperationWithField = (operation)=>{
1555
+ return operation?.field !== undefined;
934
1556
  };
935
-
936
- /**
937
- * Creates the authzApi for checking arborist permissions for a selected user
938
- * @see https://petstore.swagger.io/?url=https://raw.githubusercontent.com/uc-cdis/arborist/master/docs/openapi.yaml#/auth/get_auth_mapping
939
- * @see https://github.com/uc-cdis/arborist/blob/master/docs/relationships.simplified.png
940
- * @returns: An arborist response dict of user permissions {method, service} for each resource path.
941
- */ const authzApi = gen3Api.injectEndpoints({
942
- endpoints: (builder)=>({
943
- getAuthzMappings: builder.query({
944
- query: ()=>`${GEN3_AUTHZ_API}/mapping`
945
- })
946
- })
947
- });
948
- const { useGetAuthzMappingsQuery } = authzApi;
949
- const selectAuthzMapping = authzApi.endpoints.getAuthzMappings.select();
950
- const selectAuthzMappingData = toolkit.createSelector(selectAuthzMapping, (authzMapping)=>authzMapping?.data ?? {
951
- mappings: []
952
- });
953
-
954
- const HasEnoughData = (data, keys, limit)=>{
955
- const numEmptyKeys = keys.filter((k)=>Object.hasOwn(data, k) && typeof data[k] === 'string' && data[k].trim() === '').length;
956
- return numEmptyKeys < limit;
1557
+ const extractFilterValue = (op)=>{
1558
+ const valueExtractorHandler = new ValueExtractorHandler();
1559
+ return handleOperation(valueExtractorHandler, op);
1560
+ };
1561
+ const extractEnumFilterValue = (op)=>{
1562
+ const enumValueExtractorHandler = new EnumValueExtractorHandler();
1563
+ const results = handleOperation(enumValueExtractorHandler, op);
1564
+ return results ?? [];
1565
+ };
1566
+ const assertNever = (x)=>{
1567
+ throw Error(`Exhaustive comparison did not handle: ${x}`);
1568
+ };
1569
+ const handleOperation = (handler, op)=>{
1570
+ switch(op.operator){
1571
+ case '=':
1572
+ return handler.handleEquals(op);
1573
+ case '!=':
1574
+ return handler.handleNotEquals(op);
1575
+ case '<':
1576
+ return handler.handleLessThan(op);
1577
+ case '<=':
1578
+ return handler.handleLessThanOrEquals(op);
1579
+ case '>':
1580
+ return handler.handleGreaterThan(op);
1581
+ case '>=':
1582
+ return handler.handleGreaterThanOrEquals(op);
1583
+ case 'and':
1584
+ return handler.handleIntersection(op);
1585
+ case 'or':
1586
+ return handler.handleUnion(op);
1587
+ case 'nested':
1588
+ return handler.handleNestedFilter(op);
1589
+ case 'in':
1590
+ return handler.handleIncludes(op);
1591
+ case 'excludeifany':
1592
+ return handler.handleExcludeIfAny(op);
1593
+ case 'excludes':
1594
+ return handler.handleExcludes(op);
1595
+ default:
1596
+ return assertNever(op);
1597
+ }
957
1598
  };
958
1599
  /**
959
- * Defines metadataApi service using a base URL and expected endpoints. Derived from gen3Api core API.
960
- *
961
- * @param endpoints - Defines endpoints used in discovery page
962
- * @param getAggMDS - Queries aggregate metadata service
963
- * @see https://github.com/uc-cdis/metadata-service/blob/master/docs/agg_mds.md
964
- * @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
965
- * @param getMDS - Queries normal metadata service
966
- * @see https://petstore.swagger.io/?url=https://raw.githubusercontent.com/uc-cdis/metadata-service/master/docs/openapi.yaml#/Query/search_metadata_metadata_get
967
- * @param getIndexAggMDS - queries the Aggregate Metadata service and returns all common passed in indexKeys
968
- * @param getTags - Probably refering to Aggregate metadata service summary statistics query
969
- * @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
970
- * @param getData - Looks like a duplicate of getMDS handler. unused in ./frontend package
971
- * @param getCrosswalkData - Maps ids from one source to another
972
- * @returns: A guppy download API for fetching bulk metadata
973
- */ const metadataApi = gen3Api.injectEndpoints({
974
- endpoints: (builder)=>({
975
- getAggMDS: builder.query({
976
- query: ({ offset, pageSize })=>{
977
- return `${GEN3_MDS_API}/aggregate/metadata?flatten=true&pagination=true&offset=${offset}&limit=${pageSize}`;
978
- },
979
- transformResponse: (response, _meta, params)=>{
980
- return {
981
- data: response.results.map((x)=>{
982
- const objValues = Object.values(x);
983
- const firstValue = objValues ? objValues.at(0) : undefined;
984
- return firstValue ? firstValue[params.studyField] : undefined;
985
- }),
986
- hits: response.pagination.hits
987
- };
1600
+ * Return true if a FilterSet's root value is an empty object
1601
+ * @param fs - FilterSet to test
1602
+ */ const isFilterEmpty = (fs)=>lodash.isEqual({}, fs);
1603
+ class ToGqlHandler {
1604
+ constructor(){
1605
+ this.handleEquals = (op)=>({
1606
+ '=': {
1607
+ [op.field]: op.operand
988
1608
  }
989
- }),
990
- getIndexAggMDS: builder.query({
991
- query: ({ pageSize })=>{
992
- return `${GEN3_MDS_API}/aggregate/metadata?limit=${pageSize}`;
993
- },
994
- transformResponse: (response, _meta, params)=>{
995
- const dataFromIndexes = params.indexKeys.reduce((acc, key)=>{
996
- if (response[key]) {
997
- acc.push(...response[key]);
998
- }
999
- return acc;
1000
- }, []);
1001
- return {
1002
- data: dataFromIndexes.map((x)=>{
1003
- const objValues = Object.values(x);
1004
- const objIds = Object.keys(x);
1005
- let firstValue = objValues ? objValues.at(0) : undefined;
1006
- if (params?.filterEmpty) {
1007
- // remove any data that has < limit if defined
1008
- if (firstValue && !HasEnoughData(firstValue[params.studyField], params.filterEmpty.keys, params.filterEmpty.limit)) firstValue = undefined;
1009
- }
1010
- return firstValue ? {
1011
- gen3MDSGUID: objIds.at(0),
1012
- ...firstValue[params.studyField]
1013
- } : undefined;
1014
- }).filter((x)=>x !== undefined),
1015
- hits: dataFromIndexes.length
1016
- };
1609
+ });
1610
+ this.handleNotEquals = (op)=>({
1611
+ '!=': {
1612
+ [op.field]: op.operand
1017
1613
  }
1018
- }),
1019
- getMDS: builder.query({
1020
- query: ({ guidType, offset, pageSize })=>{
1021
- return `${GEN3_MDS_API}/metadata?data=True&_guid_type=${guidType}&limit=${pageSize}&offset=${offset}`;
1022
- },
1023
- transformResponse: (response, _meta)=>{
1024
- return {
1025
- data: Object.keys(response).map((guid)=>response[guid]),
1026
- hits: Object.keys(response).length
1027
- };
1614
+ });
1615
+ this.handleLessThan = (op)=>({
1616
+ '<': {
1617
+ [op.field]: op.operand
1028
1618
  }
1029
- }),
1030
- getTags: builder.query({
1031
- query: ()=>'tags'
1032
- }),
1033
- getData: builder.query({
1034
- query: (params)=>({
1035
- url: `metadata?${params}`
1036
- })
1037
- }),
1038
- // TODO: Move this to own slice
1039
- getCrosswalkData: builder.query({
1040
- queryFn: async (arg, _queryApi, _extraOptions, fetchWithBQ)=>{
1041
- const queryMultiple = async ()=>{
1042
- let result = [];
1043
- const queue = Queue({
1044
- concurrency: 15
1045
- });
1046
- for (const id of arg.ids){
1047
- queue.push(async (callback)=>{
1048
- const response = await fetchWithBQ({
1049
- url: `${GEN3_CROSSWALK_API}/metadata/${id}`
1050
- });
1051
- if (response.error) {
1052
- return {
1053
- error: response.error
1054
- };
1055
- }
1056
- const toData = arg.toPaths.reduce((acc, path)=>{
1057
- acc[path.id] = jsonpathPlus.JSONPath({
1058
- json: response.data,
1059
- path: `$.[${path.dataPath}]`,
1060
- resultType: 'value'
1061
- })?.[0] ?? 'n/a';
1062
- return acc;
1063
- }, {});
1064
- result = [
1065
- ...result,
1066
- {
1067
- from: id,
1068
- to: toData
1069
- }
1070
- ];
1071
- if (callback) callback();
1072
- return result;
1073
- });
1074
- }
1075
- return new Promise((resolve, reject)=>{
1076
- queue.start((err)=>{
1077
- if (err) {
1078
- reject(err);
1079
- } else {
1080
- resolve(result);
1081
- }
1082
- });
1083
- });
1084
- };
1085
- const result = await queryMultiple();
1086
- return {
1087
- data: result
1088
- };
1619
+ });
1620
+ this.handleLessThanOrEquals = (op)=>({
1621
+ '<=': {
1622
+ [op.field]: op.operand
1089
1623
  }
1090
- })
1091
- })
1092
- });
1093
- const { useGetAggMDSQuery, useGetMDSQuery, useGetTagsQuery, useGetDataQuery, useGetCrosswalkDataQuery, useLazyGetCrosswalkDataQuery, useGetIndexAggMDSQuery } = metadataApi;
1094
-
1095
- // using a random uuid v4 as the namespace
1096
- const GEN3_APP_NAMESPACE = '7bfaa818-c69c-457e-8d87-413cf60c25f0';
1097
-
1098
- const getGen3AppId = (name, version)=>{
1099
- const nameVersion = `${name}::${version}`;
1100
- return uuid.v5(nameVersion, GEN3_APP_NAMESPACE);
1101
- };
1102
- /**
1103
- * Creates a Gen3App that is dynamically loaded
1104
- */ const createGen3App = ({ App, name, version, requiredEntityTypes })=>{
1105
- // create a stable id for this app
1106
- const nameVersion = `${name}::${version}`;
1107
- const id = uuid.v5(nameVersion, GEN3_APP_NAMESPACE);
1108
- // need to create store and provider.
1109
- // return a component representing this app
1110
- // if component gets added to a list, then the list can be iterated in index.js and each provider component can be added
1111
- // a route can be setup for the app
1112
- // need to register its name, category, path, data requirements
1113
- // this will be used to build page3
1114
- // click app link
1115
- // const store = configureStore({
1116
- // // TODO allow user to pass in a reducer in CreateGen3AppOptions?
1117
- // reducer: (state) => state,
1118
- // devTools: {
1119
- // name: `${nameVersion}::${id}`,
1120
- // },
1121
- // });
1122
- const Gen3AppWrapper = (props)=>{
1123
- React.useEffect(()=>{
1124
- document.title = `GEN3 - ${name}`;
1125
- });
1126
- return /*#__PURE__*/ React.createElement(App, props);
1127
- };
1128
- // add the app to the store
1129
- coreStore.dispatch(addGen3AppMetadata({
1130
- id,
1131
- name,
1132
- version,
1133
- requiredEntityTypes
1134
- }));
1135
- registerGen3App(name, Gen3AppWrapper);
1136
- return Gen3AppWrapper;
1137
- };
1138
- // ----------------------------------------------------------------------------------------
1139
- // Apps with Local Storage
1140
- //
1141
- const createAppStore = (options)=>{
1142
- const { name, version, reducers, middleware } = options;
1143
- const nameVersion = `${name}::${version}`;
1144
- const id = uuid.v5(nameVersion, GEN3_APP_NAMESPACE);
1145
- const store = toolkit.configureStore({
1146
- reducer: reducers,
1147
- devTools: {
1148
- name: `${nameVersion}::${id}`
1149
- },
1150
- middleware: (getDefaultMiddleware)=>middleware ? getDefaultMiddleware({
1151
- serializableCheck: {
1152
- ignoredActions: [
1153
- reduxPersist.FLUSH,
1154
- reduxPersist.REHYDRATE,
1155
- reduxPersist.PAUSE,
1156
- reduxPersist.PERSIST,
1157
- reduxPersist.PURGE,
1158
- reduxPersist.REGISTER
1159
- ]
1624
+ });
1625
+ this.handleGreaterThan = (op)=>({
1626
+ '>': {
1627
+ [op.field]: op.operand
1160
1628
  }
1161
- }).concat(middleware) : getDefaultMiddleware({
1162
- serializableCheck: {
1163
- ignoredActions: [
1164
- reduxPersist.FLUSH,
1165
- reduxPersist.REHYDRATE,
1166
- reduxPersist.PAUSE,
1167
- reduxPersist.PERSIST,
1168
- reduxPersist.PURGE,
1169
- reduxPersist.REGISTER
1170
- ]
1629
+ });
1630
+ this.handleGreaterThanOrEquals = (op)=>({
1631
+ '>=': {
1632
+ [op.field]: op.operand
1171
1633
  }
1172
- })
1173
- });
1174
- const context = /*#__PURE__*/ React.createContext(null);
1175
- const useAppSelector = reactRedux.createSelectorHook(context);
1176
- const useAppDispatch = reactRedux.createDispatchHook(context);
1177
- const useAppStore = reactRedux.createStoreHook(context);
1178
- return {
1179
- id: id,
1180
- AppStore: store,
1181
- AppContext: context,
1182
- useAppSelector: useAppSelector,
1183
- useAppDispatch: useAppDispatch,
1184
- useAppStore: useAppStore
1185
- };
1186
- };
1187
- const createGen3AppWithOwnStore = (options)=>{
1188
- const { App, id, name, version, requiredEntityTypes, store, context } = options;
1189
- // need to create store and provider.
1190
- // return a component representing this app
1191
- // if component gets added to a list, then the list can be iterated in index.js and each provider component can be added
1192
- // a route can be setup for the app
1193
- // need to register its name, category, path, data requirements
1194
- // this will be used to build page3
1195
- // click app link
1196
- const Gen3AppWrapper = (props)=>{
1197
- React.useEffect(()=>{
1198
- document.title = `GEN3 - ${name}`;
1199
- });
1200
- return /*#__PURE__*/ React.createElement(reactRedux.Provider, {
1201
- store: store,
1202
- context: context
1203
- }, /*#__PURE__*/ React.createElement(reactCookie.CookiesProvider, null, /*#__PURE__*/ React.createElement(App, props)));
1204
- };
1205
- // add the app to the store
1206
- coreStore.dispatch(addGen3AppMetadata({
1207
- id,
1208
- name,
1209
- version,
1210
- requiredEntityTypes
1211
- }));
1212
- registerGen3App(name, Gen3AppWrapper);
1213
- return Gen3AppWrapper;
1214
- };
1215
-
1216
- const createAppApiForRTKQ = (reducerPath, baseQuery)=>{
1217
- const appContext = React__namespace.createContext(null);
1218
- const useAppSelector = reactRedux.useSelector.withTypes();
1219
- const useAppDispatch = reactRedux.createDispatchHook(appContext);
1220
- const useAppStore = reactRedux.createStoreHook(appContext);
1221
- const appCreateApi = react.buildCreateApi(react.coreModule(), react.reactHooksModule({
1222
- hooks: {
1223
- useDispatch: useAppDispatch,
1224
- useSelector: useAppSelector,
1225
- useStore: useAppStore
1226
- }
1227
- }));
1228
- const appRTKQApi = appCreateApi({
1229
- reducerPath: reducerPath,
1230
- baseQuery: baseQuery ?? react.fetchBaseQuery({
1231
- baseUrl: `${GEN3_API}`,
1232
- prepareHeaders: (headers)=>{
1233
- headers.set('Content-Type', 'application/json');
1234
- let accessToken = undefined;
1235
- if (process.env.NODE_ENV === 'development') {
1236
- // NOTE: This cookie can only be accessed from the client side
1237
- // in development mode. Otherwise, the cookie is set as httpOnly
1238
- accessToken = cookiesNext.getCookie('credentials_token');
1634
+ });
1635
+ this.handleIncludes = (op)=>({
1636
+ in: {
1637
+ [op.field]: op.operands
1239
1638
  }
1240
- if (accessToken) headers.set('Authorization', `Bearer ${accessToken}`);
1241
- return headers;
1242
- }
1243
- }),
1244
- endpoints: ()=>({})
1245
- });
1246
- const appMiddleware = appRTKQApi.middleware;
1247
- const appStore = toolkit.configureStore({
1248
- reducer: {
1249
- [appRTKQApi.reducerPath]: appRTKQApi.reducer
1250
- },
1251
- middleware: (getDefaultMiddleware)=>getDefaultMiddleware({
1252
- serializableCheck: {
1253
- ignoredActions: [
1254
- reduxPersist.FLUSH,
1255
- reduxPersist.REHYDRATE,
1256
- reduxPersist.PAUSE,
1257
- reduxPersist.PERSIST,
1258
- reduxPersist.PURGE,
1259
- reduxPersist.REGISTER
1260
- ]
1639
+ });
1640
+ this.handleExcludes = (op)=>({
1641
+ exclude: {
1642
+ [op.field]: op.operands
1261
1643
  }
1262
- }).concat(appMiddleware)
1263
- });
1264
- return {
1265
- useAppSelector: useAppSelector,
1266
- useAppDispatch: useAppDispatch,
1267
- useAppStore: useAppStore,
1268
- AppContext: appContext,
1269
- appApi: appRTKQApi,
1270
- appContext: appContext,
1271
- appStore: appStore
1644
+ });
1645
+ this.handleExcludeIfAny = (op)=>({
1646
+ excludeifany: {
1647
+ [op.field]: op.operands
1648
+ }
1649
+ });
1650
+ this.handleIntersection = (op)=>({
1651
+ and: op.operands.map((x)=>convertFilterToGqlFilter(x))
1652
+ });
1653
+ this.handleUnion = (op)=>({
1654
+ or: op.operands.map((x)=>convertFilterToGqlFilter(x))
1655
+ });
1656
+ this.handleNestedFilter = (op)=>{
1657
+ const child = convertFilterToGqlFilter(op.operand);
1658
+ return {
1659
+ nested: {
1660
+ path: op.path,
1661
+ ...child
1662
+ }
1663
+ };
1664
+ };
1665
+ }
1666
+ }
1667
+ const convertFilterToGqlFilter = (filter)=>{
1668
+ const handler = new ToGqlHandler();
1669
+ return handleOperation(handler, filter);
1670
+ };
1671
+ const convertFilterSetToGqlFilter = (fs, toplevelOp = 'and')=>{
1672
+ const fsKeys = Object.keys(fs.root);
1673
+ // if no keys return undefined
1674
+ if (fsKeys.length === 0) return {
1675
+ and: []
1676
+ };
1677
+ return toplevelOp === 'and' ? {
1678
+ and: fsKeys.map((key)=>convertFilterToGqlFilter(fs.root[key]))
1679
+ } : {
1680
+ or: fsKeys.map((key)=>convertFilterToGqlFilter(fs.root[key]))
1272
1681
  };
1273
1682
  };
1274
-
1275
- const graphQLWithTags = gen3Api.enhanceEndpoints({
1276
- addTagTypes: [
1277
- 'graphQL'
1278
- ]
1279
- });
1280
1683
  /**
1281
- * Creates a graphQLAPI for graphql queries to elasticsearch indices via guppy
1282
- * @see https://github.com/uc-cdis/guppy/blob/master/doc/queries.md
1283
- * @param query - Resolver function which configures the graphql query with graphQLParams argument
1284
- * @returns: A guppy search API for fetching metadata
1285
- */ const graphQLAPI = graphQLWithTags.injectEndpoints({
1286
- endpoints: (builder)=>({
1287
- graphQL: builder.query({
1288
- query: (graphQLParams)=>({
1289
- url: `${GEN3_GUPPY_API}/graphql`,
1290
- method: 'POST',
1291
- credentials: 'include',
1292
- body: JSON.stringify(graphQLParams)
1293
- })
1294
- })
1295
- })
1296
- });
1297
- const { useGraphQLQuery } = graphQLAPI;
1684
+ * Extract the operand values, if operands themselves have values, otherwise undefined.
1685
+ */ class ValueExtractorHandler {
1686
+ constructor(){
1687
+ this.handleEquals = (op)=>op.operand;
1688
+ this.handleNotEquals = (op)=>op.operand;
1689
+ this.handleIncludes = (op)=>op.operands;
1690
+ this.handleExcludes = (op)=>op.operands;
1691
+ this.handleExcludeIfAny = (op)=>op.operands;
1692
+ this.handleGreaterThanOrEquals = (op)=>op.operand;
1693
+ this.handleGreaterThan = (op)=>op.operand;
1694
+ this.handleLessThan = (op)=>op.operand;
1695
+ this.handleLessThanOrEquals = (op)=>op.operand;
1696
+ this.handleIntersection = (_arg)=>undefined;
1697
+ this.handleUnion = (_)=>undefined;
1698
+ this.handleNestedFilter = (_)=>undefined;
1699
+ }
1700
+ }
1701
+ /**
1702
+ * Extract the operand values, if operands themselves have values, otherwise undefined.
1703
+ */ class EnumValueExtractorHandler {
1704
+ constructor(){
1705
+ this.handleEquals = (_)=>undefined;
1706
+ this.handleNotEquals = (_)=>undefined;
1707
+ this.handleIncludes = (op)=>op.operands;
1708
+ this.handleExcludes = (op)=>op.operands;
1709
+ this.handleExcludeIfAny = (op)=>op.operands;
1710
+ this.handleGreaterThanOrEquals = (_)=>undefined;
1711
+ this.handleGreaterThan = (_)=>undefined;
1712
+ this.handleLessThan = (_)=>undefined;
1713
+ this.handleLessThanOrEquals = (_)=>undefined;
1714
+ this.handleIntersection = (_)=>undefined;
1715
+ this.handleUnion = (_)=>undefined;
1716
+ this.handleNestedFilter = (op)=>{
1717
+ return extractEnumFilterValue(op.operand);
1718
+ };
1719
+ }
1720
+ }
1298
1721
 
1299
- const isOperationWithField = (operation)=>{
1300
- return operation?.field !== undefined;
1722
+ const isFilterSet = (input)=>{
1723
+ if (typeof input !== 'object' || input === null) {
1724
+ return false;
1725
+ }
1726
+ const { root, mode } = input;
1727
+ if (typeof root !== 'object' || root === null) {
1728
+ return false;
1729
+ }
1730
+ if (![
1731
+ 'and',
1732
+ 'or'
1733
+ ].includes(mode)) {
1734
+ return false;
1735
+ }
1736
+ return true;
1301
1737
  };
1302
- const extractFilterValue = (op)=>{
1303
- const valueExtractorHandler = new ValueExtractorHandler();
1304
- return handleOperation(valueExtractorHandler, op);
1738
+
1739
+ const FieldNameOverrides = {};
1740
+ const COMMON_PREPOSITIONS = [
1741
+ 'a',
1742
+ 'an',
1743
+ 'and',
1744
+ 'at',
1745
+ 'but',
1746
+ 'by',
1747
+ 'for',
1748
+ 'in',
1749
+ 'is',
1750
+ 'nor',
1751
+ 'of',
1752
+ 'on',
1753
+ 'or',
1754
+ 'out',
1755
+ 'so',
1756
+ 'the',
1757
+ 'to',
1758
+ 'up',
1759
+ 'yet'
1760
+ ];
1761
+ const capitalize = (s)=>s.length > 0 ? s[0].toUpperCase() + s.slice(1) : '';
1762
+ const trimFirstFieldNameToTitle = (fieldName, trim = false)=>{
1763
+ if (trim) {
1764
+ const source = fieldName.slice(fieldName.indexOf('.') + 1);
1765
+ return fieldNameToTitle(source ? source : fieldName, 0);
1766
+ }
1767
+ return fieldNameToTitle(fieldName);
1768
+ };
1769
+ /**
1770
+ * Converts a filter name to a title,
1771
+ * For example files.input.experimental_strategy will get converted to Experimental Strategy
1772
+ * if sections == 2 then the output would be Input Experimental Strategy
1773
+ * @param fieldName input filter expected to be: string.firstpart_secondpart
1774
+ * @param sections number of "sections" string.string.string to got back from the end of the field
1775
+ */ const fieldNameToTitle = (fieldName, sections = 1)=>{
1776
+ if (fieldName in FieldNameOverrides) {
1777
+ return FieldNameOverrides[fieldName];
1778
+ }
1779
+ if (fieldName === undefined) return 'No Title';
1780
+ return fieldName.split('.').slice(-sections).map((s)=>s.split('_')).flat().map((word)=>COMMON_PREPOSITIONS.includes(word) ? word : capitalize(word)).join(' ');
1781
+ };
1782
+ /**
1783
+ * Extracts the index name from the field name
1784
+ * @param fieldName
1785
+ */ const extractIndexFromFullFieldName = (fieldName)=>fieldName.split('.')[0];
1786
+ /**
1787
+ * prepend the index name to the field name
1788
+ */ const prependIndexToFieldName = (fieldName, index)=>`${index}.${fieldName}`;
1789
+ /**
1790
+ * extract the field name from the index.field name
1791
+ */ const extractFieldNameFromFullFieldName = (fieldName)=>fieldName.split('.').slice(1).join('.');
1792
+ /**
1793
+ * extract the field name and the index from the index.field name returning as a tuple
1794
+ */ const extractIndexAndFieldNameFromFullFieldName = (fieldName)=>{
1795
+ const [index, ...rest] = fieldName.split('.');
1796
+ return [
1797
+ index,
1798
+ rest.join('.')
1799
+ ];
1800
+ };
1801
+
1802
+ const statusEndpoint = '/_status';
1803
+ const processHistogramResponse = (data)=>{
1804
+ const valueData = jsonpathPlus.JSONPath({
1805
+ json: data,
1806
+ path: '$..histogram',
1807
+ resultType: 'value'
1808
+ });
1809
+ const pointerData = jsonpathPlus.JSONPath({
1810
+ json: data,
1811
+ path: '$..histogram',
1812
+ resultType: 'pointer'
1813
+ });
1814
+ const results = pointerData.reduce((acc, element, idx)=>{
1815
+ const key = element.slice(1).replace(/\/histogram/g, '').replace(/\//g, '.');
1816
+ return {
1817
+ ...acc,
1818
+ [key]: valueData[idx]
1819
+ };
1820
+ }, {});
1821
+ return results;
1822
+ };
1823
+ const fetchJson = async (url)=>{
1824
+ const res = await fetch(url, {
1825
+ method: 'GET',
1826
+ headers: {
1827
+ 'Content-type': 'application/json'
1828
+ }
1829
+ });
1830
+ if (!res.ok) throw new Error('An error occurred while fetching the data.');
1831
+ return await res.json();
1832
+ };
1833
+ const useGetStatus = ()=>{
1834
+ const fetcher = ()=>fetchJson(`${GEN3_GUPPY_API}${statusEndpoint}`);
1835
+ return useSWR('explorerStatus', fetcher);
1836
+ };
1837
+ /**
1838
+ * The main endpoint used in templating Exploration page queries.
1839
+ * Includes table, filter and aggregation query types and leverages guppyApi defined in ./gupplApi.ts
1840
+ * Query templates support filters where applicable
1841
+ *
1842
+ * @param endpoints - Defines endpoints used in Exploration page:
1843
+ * @param getAllFieldsForType - A mapping query that returns all property key names vertex types specified.
1844
+ * @see https://github.com/uc-cdis/guppy/blob/master/doc/queries.md#mapping-query
1845
+ * @param getAccessibleData - An aggregation histogram counts query that filters based on access type
1846
+ * @see https://github.com/uc-cdis/guppy/blob/master/doc/queries.md#accessibility-argument-for-regular-tier-access-level
1847
+ * @param getRawDataAndTotalCounts - Queries both _totalCount for selected vertex types and
1848
+ * tabular results containing the raw data in the rows of selected vertex types
1849
+ * @see https://github.com/uc-cdis/guppy/blob/master/doc/queries.md#1-total-count-aggregation
1850
+ * @param getAggs - An aggregated histogram counts query which outputs vertex property frequencies
1851
+ * @param getSubAggs - TODO: not sure what this one does. Looks like nested aggregation
1852
+ * @param getCounts - Returns total counts of a vertex type
1853
+ * @returns: A guppy API endpoint for templating queriable data displayed on the exploration page
1854
+ */ const explorerApi = guppyApi.injectEndpoints({
1855
+ endpoints: (builder)=>({
1856
+ getAllFieldsForType: builder.query({
1857
+ query: (type)=>({
1858
+ query: `{ _mapping ${type} } }`
1859
+ }),
1860
+ transformResponse: (response, _meta, params)=>{
1861
+ return response[params.type];
1862
+ }
1863
+ }),
1864
+ getAccessibleData: builder.query({
1865
+ query: ({ type, fields, accessType })=>{
1866
+ const fieldParts = fields.map((field)=>`${field} { histogram { key count } }`);
1867
+ return {
1868
+ query: `_aggregation {
1869
+ ${type} (accessibility: ${accessType}) {
1870
+ ${fieldParts.join(',')}
1871
+ }
1872
+ }`
1873
+ };
1874
+ }
1875
+ }),
1876
+ getRawDataAndTotalCounts: builder.query({
1877
+ query: ({ type, fields, filters, sort, offset = 0, size = 20, accessibility = exports.Accessibility.ALL, format = undefined })=>{
1878
+ const gqlFilter = convertFilterSetToGqlFilter(filters);
1879
+ const params = [
1880
+ ...sort ? [
1881
+ '$sort: JSON'
1882
+ ] : [],
1883
+ ...gqlFilter ? [
1884
+ '$filter: JSON'
1885
+ ] : [],
1886
+ ...format ? [
1887
+ '$format: Format'
1888
+ ] : []
1889
+ ].join(',');
1890
+ const queryLine = `query getRawDataAndTotalCounts (${params}) {`;
1891
+ const dataParams = [
1892
+ ...format ? [
1893
+ 'format: $format'
1894
+ ] : [],
1895
+ ...sort ? [
1896
+ 'sort: $sort'
1897
+ ] : [],
1898
+ ...gqlFilter ? [
1899
+ 'filter: $filter'
1900
+ ] : []
1901
+ ].join(',');
1902
+ const dataTypeLine = `${type} (accessibility: ${accessibility}, offset: ${offset}, first: ${size},
1903
+ ${dataParams}) {`;
1904
+ const typeAggsLine = `${type} (${gqlFilter && 'filter: $filter,'} accessibility: ${accessibility}) {`;
1905
+ const processedFields = fields.map((field)=>rawDataQueryStrForEachField(field));
1906
+ const query = `${queryLine}
1907
+ ${dataTypeLine}
1908
+ ${processedFields.join(' ')}
1909
+ }
1910
+ _aggregation {
1911
+ ${typeAggsLine}
1912
+ _totalCount
1913
+ }
1914
+ }
1915
+ }`;
1916
+ const variables = {
1917
+ ...sort && {
1918
+ sort
1919
+ },
1920
+ ...gqlFilter && {
1921
+ filter: gqlFilter
1922
+ },
1923
+ ...format && {
1924
+ format
1925
+ }
1926
+ };
1927
+ return {
1928
+ query,
1929
+ variables
1930
+ };
1931
+ }
1932
+ }),
1933
+ getAggs: builder.query({
1934
+ query: ({ type, fields, filters, accessibility = exports.Accessibility.ALL })=>{
1935
+ const queryStart = isFilterEmpty(filters) ? `
1936
+ query getAggs {
1937
+ _aggregation {
1938
+ ${type} (accessibility: ${accessibility}) {` : `query getAggs ($filter: JSON) {
1939
+ _aggregation {
1940
+ ${type} (filter: $filter, filterSelf: false, accessibility: ${accessibility}) {`;
1941
+ const query = `${queryStart}
1942
+ ${fields.map((field)=>histogramQueryStrForEachField(field))}
1943
+ }
1944
+ }
1945
+ }`;
1946
+ const queryBody = {
1947
+ query: query,
1948
+ variables: {
1949
+ filter: convertFilterSetToGqlFilter(filters)
1950
+ }
1951
+ };
1952
+ return queryBody;
1953
+ },
1954
+ transformResponse: (response, _meta, args)=>{
1955
+ return processHistogramResponse(response.data._aggregation[args.type]);
1956
+ }
1957
+ }),
1958
+ getSubAggs: builder.query({
1959
+ query: ({ type, mainField, termsFields = undefined, missingFields = undefined, numericAggAsText = false, gqlFilter = undefined, accessibility = exports.Accessibility.ALL })=>{
1960
+ const nestedAggFields = {
1961
+ termsFields: termsFields,
1962
+ missingFields: missingFields
1963
+ };
1964
+ const query = `query getSubAggs ( ${gqlFilter ?? '$filter: JSON,'} $nestedAggFields: JSON) {
1965
+ _aggregation {
1966
+ ${type} ( ${gqlFilter ?? 'filter: $filter, filterSelf: false,'} nestedAggFields: $nestedAggFields, accessibility: ${accessibility}) {
1967
+ ${nestedHistogramQueryStrForEachField(mainField, numericAggAsText)}
1968
+ }`;
1969
+ return {
1970
+ query: query,
1971
+ variables: {
1972
+ ...gqlFilter && {
1973
+ filter: convertFilterSetToGqlFilter(gqlFilter)
1974
+ },
1975
+ nestedAggFields: nestedAggFields
1976
+ }
1977
+ };
1978
+ },
1979
+ transformResponse: (response, _meta, args)=>{
1980
+ return processHistogramResponse(response.data._aggregation[args.type]);
1981
+ }
1982
+ }),
1983
+ getCounts: builder.query({
1984
+ query: ({ type, filters, accessibility = exports.Accessibility.ALL })=>{
1985
+ const gqlFilters = convertFilterSetToGqlFilter(filters);
1986
+ const queryLine = `query totalCounts ${gqlFilters ? '($filter: JSON)' : ''}{`;
1987
+ const typeAggsLine = `${type} ${gqlFilters ? '(filter: $filter, ' : '('} accessibility: ${accessibility}) {`;
1988
+ const query = `${queryLine} _aggregation {
1989
+ ${typeAggsLine}
1990
+ _totalCount
1991
+ }
1992
+ }
1993
+ }`;
1994
+ return {
1995
+ query: query,
1996
+ variables: {
1997
+ ...gqlFilters && {
1998
+ filter: gqlFilters
1999
+ }
2000
+ }
2001
+ };
2002
+ },
2003
+ transformResponse: (response, _meta, args)=>{
2004
+ return response.data._aggregation[args.type]._totalCount;
2005
+ }
2006
+ }),
2007
+ getFieldCountSummary: builder.query({
2008
+ query: ({ type, field, filters, accessibility = exports.Accessibility.ALL })=>{
2009
+ const gqlFilters = convertFilterSetToGqlFilter(filters);
2010
+ const query = `query summary ($filter: JSON) {
2011
+ _aggregation {
2012
+ ${type} (filter: $filter, accessibility: ${accessibility}) {
2013
+ ${field} {
2014
+ histogram {
2015
+ sum,
2016
+ }
2017
+ }
2018
+ }
2019
+ }
2020
+ }`;
2021
+ return {
2022
+ query: query,
2023
+ variables: {
2024
+ ...gqlFilters && {
2025
+ filter: gqlFilters
2026
+ }
2027
+ }
2028
+ };
2029
+ }
2030
+ }),
2031
+ getFieldsForIndex: builder.query({
2032
+ query: (index)=>{
2033
+ return {
2034
+ query: `{
2035
+ _mapping ${index}
2036
+ }`
2037
+ };
2038
+ },
2039
+ transformResponse: (response)=>{
2040
+ return response['_mapping'];
2041
+ }
2042
+ }),
2043
+ generalGQL: builder.query({
2044
+ query: ({ query, variables })=>{
2045
+ return {
2046
+ query: query,
2047
+ variables: variables
2048
+ };
2049
+ }
2050
+ })
2051
+ })
2052
+ });
2053
+ // query for aggregate data
2054
+ // convert the function below to typescript
2055
+ const histogramQueryStrForEachField = (field)=>{
2056
+ const splittedFieldArray = field.split('.');
2057
+ const splittedField = splittedFieldArray.shift();
2058
+ if (splittedFieldArray.length === 0) {
2059
+ return `
2060
+ ${splittedField} {
2061
+ histogram {
2062
+ key
2063
+ count
2064
+ }
2065
+ }`;
2066
+ }
2067
+ return `
2068
+ ${splittedField} {
2069
+ ${histogramQueryStrForEachField(splittedFieldArray.join('.'))}
2070
+ }`;
1305
2071
  };
1306
- const extractEnumFilterValue = (op)=>{
1307
- const enumValueExtractorHandler = new EnumValueExtractorHandler();
1308
- const results = handleOperation(enumValueExtractorHandler, op);
1309
- return results ?? [];
2072
+ const nestedHistogramQueryStrForEachField = (mainField, numericAggAsText)=>`
2073
+ ${mainField} {
2074
+ ${numericAggAsText ? 'asTextHistogram' : 'histogram'} {
2075
+ key
2076
+ count
2077
+ missingFields {
2078
+ field
2079
+ count
2080
+ }
2081
+ termsFields {
2082
+ field
2083
+ count
2084
+ terms {
2085
+ key
2086
+ count
2087
+ }
2088
+ }
2089
+ }
2090
+ }`;
2091
+ const rawDataQueryStrForEachField = (field)=>{
2092
+ const splitFieldArray = field.split('.');
2093
+ const splitField = splitFieldArray.shift();
2094
+ if (splitFieldArray.length === 0) {
2095
+ return `
2096
+ ${splitField}
2097
+ `;
2098
+ }
2099
+ return `
2100
+ ${splitField} {
2101
+ ${rawDataQueryStrForEachField(splitFieldArray.join('.'))}
2102
+ }`;
1310
2103
  };
1311
- const assertNever = (x)=>{
1312
- throw Error(`Exhaustive comparison did not handle: ${x}`);
2104
+ const useGetArrayTypes = ()=>{
2105
+ {
2106
+ const { data, error } = useGetStatus();
2107
+ if (error) {
2108
+ return {};
2109
+ }
2110
+ return data ? data['indices'] : {};
2111
+ }
1313
2112
  };
1314
- const handleOperation = (handler, op)=>{
1315
- switch(op.operator){
1316
- case '=':
1317
- return handler.handleEquals(op);
1318
- case '!=':
1319
- return handler.handleNotEquals(op);
1320
- case '<':
1321
- return handler.handleLessThan(op);
1322
- case '<=':
1323
- return handler.handleLessThanOrEquals(op);
1324
- case '>':
1325
- return handler.handleGreaterThan(op);
1326
- case '>=':
1327
- return handler.handleGreaterThanOrEquals(op);
1328
- case 'and':
1329
- return handler.handleIntersection(op);
1330
- case 'or':
1331
- return handler.handleUnion(op);
1332
- case 'nested':
1333
- return handler.handleNestedFilter(op);
1334
- case 'in':
1335
- return handler.handleIncludes(op);
1336
- case 'excludeifany':
1337
- return handler.handleExcludeIfAny(op);
1338
- case 'excludes':
1339
- return handler.handleExcludes(op);
1340
- default:
1341
- return assertNever(op);
2113
+ const { useGetRawDataAndTotalCountsQuery, useGetAccessibleDataQuery, useGetAllFieldsForTypeQuery, useGetAggsQuery, useLazyGetAggsQuery, useGetSubAggsQuery, useGetCountsQuery, useGetFieldCountSummaryQuery, useGetFieldsForIndexQuery, useGeneralGQLQuery, useLazyGeneralGQLQuery } = explorerApi;
2114
+
2115
+ /**
2116
+ * Flattens a deep nested JSON object skipping
2117
+ * the first level to avoid potentially flattening
2118
+ * non-nested data.
2119
+ * @param {JSON} json
2120
+ */ function flattenJson(json) {
2121
+ const flattenedJson = [];
2122
+ Object.keys(json).forEach((key)=>{
2123
+ flattenedJson.push(flat.flatten(json[key], {
2124
+ delimiter: '_'
2125
+ }));
2126
+ });
2127
+ return flattenedJson;
2128
+ }
2129
+ /**
2130
+ * Converts JSON based on a config.
2131
+ * @param {JSON} json
2132
+ * @param {Object} config
2133
+ */ async function conversion(json, config) {
2134
+ return Papa.unparse(json, config);
2135
+ }
2136
+ /**
2137
+ * Converts JSON to a specified file format.
2138
+ * Defaults to JSON if file format is not supported.
2139
+ * @param {JSON} json
2140
+ * @param {string} format
2141
+ */ async function jsonToFormat(json, format) {
2142
+ if (Object.keys(FILE_DELIMITERS).includes(format)) {
2143
+ const flatJson = await flattenJson(json);
2144
+ const data = await conversion(flatJson, {
2145
+ delimiter: FILE_DELIMITERS[format]
2146
+ });
2147
+ return data;
1342
2148
  }
2149
+ return json;
2150
+ }
2151
+
2152
+ /**
2153
+ * Prepares a URL for downloading by appending '/download' to the provided apiUrl.
2154
+ *
2155
+ * @param {string} apiUrl - The base URL to be used for preparing the download URL.
2156
+ * @returns {URL} - The prepared download URL as a URL object.
2157
+ */ const prepareUrl$1 = (apiUrl)=>new URL(apiUrl + '/download');
2158
+ /**
2159
+ * Prepares a fetch configuration object for downloading files from Guppy.
2160
+ *
2161
+ * @param {GuppyFileDownloadRequestParams} parameters - The parameters to include in the request body.
2162
+ * @param {string} csrfToken - The CSRF token to include in the request headers.
2163
+ * @returns {FetchConfig} - The prepared fetch configuration object.
2164
+ */ const prepareFetchConfig = (parameters, csrfToken)=>{
2165
+ return {
2166
+ method: 'POST',
2167
+ headers: {
2168
+ 'Content-Type': 'application/json',
2169
+ ...csrfToken !== undefined && {
2170
+ 'X-CSRF-Token': csrfToken
2171
+ }
2172
+ },
2173
+ body: JSON.stringify({
2174
+ type: parameters.type,
2175
+ filter: convertFilterSetToGqlFilter(parameters.filter),
2176
+ accessibility: parameters.accessibility,
2177
+ fields: parameters?.fields,
2178
+ sort: parameters?.sort
2179
+ })
2180
+ };
1343
2181
  };
1344
2182
  /**
1345
- * Return true if a FilterSet's root value is an empty object
1346
- * @param fs - FilterSet to test
1347
- */ const isFilterEmpty = (fs)=>lodash.isEqual({}, fs);
1348
- class ToGqlHandler {
1349
- constructor(){
1350
- this.handleEquals = (op)=>({
1351
- '=': {
1352
- [op.field]: op.operand
1353
- }
1354
- });
1355
- this.handleNotEquals = (op)=>({
1356
- '!=': {
1357
- [op.field]: op.operand
1358
- }
1359
- });
1360
- this.handleLessThan = (op)=>({
1361
- '<': {
1362
- [op.field]: op.operand
1363
- }
1364
- });
1365
- this.handleLessThanOrEquals = (op)=>({
1366
- '<=': {
1367
- [op.field]: op.operand
1368
- }
1369
- });
1370
- this.handleGreaterThan = (op)=>({
1371
- '>': {
1372
- [op.field]: op.operand
1373
- }
1374
- });
1375
- this.handleGreaterThanOrEquals = (op)=>({
1376
- '>=': {
1377
- [op.field]: op.operand
1378
- }
1379
- });
1380
- this.handleIncludes = (op)=>({
1381
- in: {
1382
- [op.field]: op.operands
1383
- }
1384
- });
1385
- this.handleExcludes = (op)=>({
1386
- exclude: {
1387
- [op.field]: op.operands
1388
- }
1389
- });
1390
- this.handleExcludeIfAny = (op)=>({
1391
- excludeifany: {
1392
- [op.field]: op.operands
1393
- }
1394
- });
1395
- this.handleIntersection = (op)=>({
1396
- and: op.operands.map((x)=>convertFilterToGqlFilter(x))
2183
+ * Downloads a file from Guppy using the provided parameters.
2184
+ * It will optionally convert the data to the specified format.
2185
+ *
2186
+ * @param {DownloadFromGuppyParams} parameters - The parameters to use for the download request.
2187
+ * @param onStart - The function to call when the download starts.
2188
+ * @param onDone - The function to call when the download is done.
2189
+ * @param onError - The function to call when the download fails.
2190
+ * @param onAbort - The function to call when the download is aborted.
2191
+ * @param signal - AbortSignal to use for the request.
2192
+ */ const downloadFromGuppyToBlob = async ({ parameters, onStart = ()=>null, onDone = (_)=>null, onError = (_)=>null, onAbort = ()=>null, signal = undefined })=>{
2193
+ const csrfToken = selectCSRFToken(coreStore.getState());
2194
+ onStart?.();
2195
+ const url = prepareUrl$1(GEN3_GUPPY_API);
2196
+ const fetchConfig = prepareFetchConfig(parameters, csrfToken);
2197
+ fetch(url.toString(), {
2198
+ ...fetchConfig,
2199
+ ...signal ? {
2200
+ signal: signal
2201
+ } : {}
2202
+ }).then(async (response)=>{
2203
+ if (!response.ok) {
2204
+ throw new Error(response.statusText);
2205
+ }
2206
+ let jsonData = await response.json();
2207
+ if (parameters?.rootPath && parameters.rootPath) {
2208
+ // if rootPath is provided, extract the data from the rootPath
2209
+ jsonData = jsonpathPlus.JSONPath({
2210
+ json: jsonData,
2211
+ path: `$.[${parameters.rootPath}]`,
2212
+ resultType: 'value'
1397
2213
  });
1398
- this.handleUnion = (op)=>({
1399
- or: op.operands.map((x)=>convertFilterToGqlFilter(x))
2214
+ }
2215
+ // convert the data to the specified format and return a Blob
2216
+ let str = '';
2217
+ if (parameters.format === 'json') {
2218
+ str = JSON.stringify(jsonData);
2219
+ } else {
2220
+ const convertedData = await jsonToFormat(jsonData, parameters.format);
2221
+ if (isJSONObject(convertedData)) {
2222
+ str = JSON.stringify(convertedData, null, 2);
2223
+ } else {
2224
+ str = convertedData;
2225
+ }
2226
+ }
2227
+ return new Blob([
2228
+ str
2229
+ ], {
2230
+ type: 'application/json'
2231
+ });
2232
+ }).then((blob)=>onDone?.(blob)).catch((error)=>{
2233
+ // Abort is handle as an exception
2234
+ if (error.name == 'AbortError') {
2235
+ // handle abort()
2236
+ onAbort?.();
2237
+ }
2238
+ onError?.(error);
2239
+ });
2240
+ };
2241
+ const downloadJSONDataFromGuppy = async ({ parameters, onAbort = ()=>null, signal = undefined })=>{
2242
+ const csrfToken = selectCSRFToken(coreStore.getState());
2243
+ const url = prepareUrl$1(GEN3_GUPPY_API);
2244
+ const fetchConfig = prepareFetchConfig(parameters, csrfToken);
2245
+ try {
2246
+ const response = await fetch(url.toString(), {
2247
+ ...fetchConfig,
2248
+ ...signal ? {
2249
+ signal: signal
2250
+ } : {}
2251
+ });
2252
+ let jsonData = await response.json();
2253
+ if (parameters?.rootPath && parameters.rootPath) {
2254
+ // if rootPath is provided, extract the data from the rootPath
2255
+ jsonData = jsonpathPlus.JSONPath({
2256
+ json: jsonData,
2257
+ path: `$.[${parameters.rootPath}]`,
2258
+ resultType: 'value'
1400
2259
  });
1401
- this.handleNestedFilter = (op)=>{
1402
- const child = convertFilterToGqlFilter(op.operand);
1403
- return {
1404
- nested: {
1405
- path: op.path,
1406
- ...child
1407
- }
1408
- };
1409
- };
2260
+ }
2261
+ // convert the data to the specified format and return a Blob
2262
+ return jsonData;
2263
+ } catch (error) {
2264
+ // Abort is handle as an exception
2265
+ if (error.name == 'AbortError') {
2266
+ // handle abort()
2267
+ onAbort?.();
2268
+ }
2269
+ throw new Error(error);
1410
2270
  }
1411
- }
1412
- const convertFilterToGqlFilter = (filter)=>{
1413
- const handler = new ToGqlHandler();
1414
- return handleOperation(handler, filter);
1415
2271
  };
1416
- const convertFilterSetToGqlFilter = (fs, toplevelOp = 'and')=>{
1417
- const fsKeys = Object.keys(fs.root);
1418
- // if no keys return undefined
1419
- if (fsKeys.length === 0) return {
1420
- and: []
1421
- };
1422
- return toplevelOp === 'and' ? {
1423
- and: fsKeys.map((key)=>convertFilterToGqlFilter(fs.root[key]))
1424
- } : {
1425
- or: fsKeys.map((key)=>convertFilterToGqlFilter(fs.root[key]))
1426
- };
2272
+ const useGetIndexFields = (index)=>{
2273
+ const { data } = useGetFieldsForIndexQuery(index);
2274
+ return data ?? [];
1427
2275
  };
2276
+
1428
2277
  /**
1429
- * Extract the operand values, if operands themselves have values, otherwise undefined.
1430
- */ class ValueExtractorHandler {
1431
- constructor(){
1432
- this.handleEquals = (op)=>op.operand;
1433
- this.handleNotEquals = (op)=>op.operand;
1434
- this.handleIncludes = (op)=>op.operands;
1435
- this.handleExcludes = (op)=>op.operands;
1436
- this.handleExcludeIfAny = (op)=>op.operands;
1437
- this.handleGreaterThanOrEquals = (op)=>op.operand;
1438
- this.handleGreaterThan = (op)=>op.operand;
1439
- this.handleLessThan = (op)=>op.operand;
1440
- this.handleLessThanOrEquals = (op)=>op.operand;
1441
- this.handleIntersection = (_arg)=>undefined;
1442
- this.handleUnion = (_)=>undefined;
1443
- this.handleNestedFilter = (_)=>undefined;
1444
- }
1445
- }
1446
- /**
1447
- * Extract the operand values, if operands themselves have values, otherwise undefined.
1448
- */ class EnumValueExtractorHandler {
1449
- constructor(){
1450
- this.handleEquals = (_)=>undefined;
1451
- this.handleNotEquals = (_)=>undefined;
1452
- this.handleIncludes = (op)=>op.operands;
1453
- this.handleExcludes = (op)=>op.operands;
1454
- this.handleExcludeIfAny = (op)=>op.operands;
1455
- this.handleGreaterThanOrEquals = (_)=>undefined;
1456
- this.handleGreaterThan = (_)=>undefined;
1457
- this.handleLessThan = (_)=>undefined;
1458
- this.handleLessThanOrEquals = (_)=>undefined;
1459
- this.handleIntersection = (_)=>undefined;
1460
- this.handleUnion = (_)=>undefined;
1461
- this.handleNestedFilter = (op)=>{
1462
- return extractEnumFilterValue(op.operand);
1463
- };
1464
- }
1465
- }
2278
+ * Creates a Guppy API for fetching bulk (> 10K rows) elasticsearch data
2279
+ * @see https://github.com/uc-cdis/guppy/blob/master/doc/download.md
2280
+ * @param endpoints - Resolver function which configures the query with
2281
+ * type, filter, accessibility, fields, and sort arguments
2282
+ * @returns: A guppy download API for fetching bulk metadata
2283
+ */ const downloadRequestApi = gen3Api.injectEndpoints({
2284
+ endpoints: (builder)=>({
2285
+ downloadFromGuppy: builder.mutation({
2286
+ query: ({ type, filter, accessibility, fields, sort })=>{
2287
+ const queryBody = {
2288
+ filter: convertFilterSetToGqlFilter(filter),
2289
+ ...{
2290
+ type,
2291
+ accessibility,
2292
+ fields,
2293
+ sort
2294
+ }
2295
+ };
2296
+ return {
2297
+ url: `${GEN3_GUPPY_API}/download`,
2298
+ method: 'POST',
2299
+ queryBody,
2300
+ cache: 'no-cache'
2301
+ };
2302
+ },
2303
+ transformResponse: (response)=>{
2304
+ return response;
2305
+ }
2306
+ })
2307
+ })
2308
+ });
2309
+ const { useDownloadFromGuppyMutation } = downloadRequestApi;
1466
2310
 
1467
- const FieldNameOverrides = {};
1468
- const COMMON_PREPOSITIONS = [
1469
- 'a',
1470
- 'an',
1471
- 'and',
1472
- 'at',
1473
- 'but',
1474
- 'by',
1475
- 'for',
1476
- 'in',
1477
- 'is',
1478
- 'nor',
1479
- 'of',
1480
- 'on',
1481
- 'or',
1482
- 'out',
1483
- 'so',
1484
- 'the',
1485
- 'to',
1486
- 'up',
1487
- 'yet'
1488
- ];
1489
- const capitalize = (s)=>s.length > 0 ? s[0].toUpperCase() + s.slice(1) : '';
1490
- const trimFirstFieldNameToTitle = (fieldName, trim = false)=>{
1491
- if (trim) {
1492
- const source = fieldName.slice(fieldName.indexOf('.') + 1);
1493
- return fieldNameToTitle(source ? source : fieldName, 0);
1494
- }
1495
- return fieldNameToTitle(fieldName);
2311
+ const rootReducer = toolkit.combineReducers({
2312
+ gen3Services: gen3ServicesReducer,
2313
+ user: userReducer,
2314
+ gen3Apps: gen3AppReducer,
2315
+ drsHostnames: drsHostnamesReducer,
2316
+ modals: modalReducer,
2317
+ cohorts: cohortReducer,
2318
+ activeWorkspace: activeWorkspaceReducer,
2319
+ dataLibrarySelection: dataLibrarySelectionReducer,
2320
+ [guppyApiSliceReducerPath]: guppyApiReducer,
2321
+ [userAuthApiReducerPath]: userAuthApiReducer
2322
+ });
2323
+
2324
+ const coreStore = toolkit.configureStore({
2325
+ reducer: rootReducer,
2326
+ middleware: (getDefaultMiddleware)=>getDefaultMiddleware().concat(gen3ServicesReducerMiddleware, guppyAPISliceMiddleware, userAuthApiMiddleware)
2327
+ });
2328
+ query.setupListeners(coreStore.dispatch);
2329
+
2330
+ const isNotDefined = (x)=>{
2331
+ return x === undefined || x === null || x === void 0;
1496
2332
  };
1497
- /**
1498
- * Converts a filter name to a title,
1499
- * For example files.input.experimental_strategy will get converted to Experimental Strategy
1500
- * if sections == 2 then the output would be Input Experimental Strategy
1501
- * @param fieldName input filter expected to be: string.firstpart_secondpart
1502
- * @param sections number of "sections" string.string.string to got back from the end of the field
1503
- */ const fieldNameToTitle = (fieldName, sections = 1)=>{
1504
- if (fieldName in FieldNameOverrides) {
1505
- return FieldNameOverrides[fieldName];
1506
- }
1507
- if (fieldName === undefined) return 'No Title';
1508
- return fieldName.split('.').slice(-sections).map((s)=>s.split('_')).flat().map((word)=>COMMON_PREPOSITIONS.includes(word) ? word : capitalize(word)).join(' ');
2333
+ const isObject = (x)=>{
2334
+ return typeof x === 'object';
1509
2335
  };
1510
- /**
1511
- * Extracts the index name from the field name
1512
- * @param fieldName
1513
- */ const extractIndexFromFullFieldName = (fieldName)=>fieldName.split('.')[0];
1514
- /**
1515
- * prepend the index name to the field name
1516
- */ const prependIndexToFieldName = (fieldName, index)=>`${index}.${fieldName}`;
1517
- /**
1518
- * extract the field name from the index.field name
1519
- */ const extractFieldNameFromFullFieldName = (fieldName)=>fieldName.split('.').slice(1).join('.');
1520
- /**
1521
- * extract the field name and the index from the index.field name returning as a tuple
1522
- */ const extractIndexAndFieldNameFromFullFieldName = (fieldName)=>{
1523
- const [index, ...rest] = fieldName.split('.');
1524
- return [
1525
- index,
1526
- rest.join('.')
1527
- ];
2336
+ const isArray = (x)=>{
2337
+ return Array.isArray(x);
1528
2338
  };
1529
-
1530
- const statusEndpoint = '/_status';
1531
- const processHistogramResponse = (data)=>{
1532
- const pathData = jsonpathPlus.JSONPath({
1533
- json: data,
1534
- path: '$..histogram',
1535
- resultType: 'all'
1536
- });
1537
- const results = pathData.reduce((acc, element)=>{
1538
- const key = element.pointer.slice(1).replace(/\/histogram/g, '').replace(/\//g, '.');
1539
- return {
1540
- ...acc,
1541
- [key]: element.value
1542
- };
1543
- }, {});
1544
- return results;
2339
+ const isString = (x)=>{
2340
+ return typeof x === 'string';
1545
2341
  };
1546
- const fetchJson = async (url)=>{
1547
- const res = await fetch(url, {
1548
- method: 'GET',
1549
- headers: {
1550
- 'Content-type': 'application/json'
2342
+
2343
+ /**
2344
+ * Prepares a URL for downloading by appending '/download' to the provided apiUrl.
2345
+ *
2346
+ * @param {string} apiUrl - The base URL to be used for preparing the download URL.
2347
+ * @returns {URL} - The prepared download URL as a URL object.
2348
+ */ const prepareUrl = (apiUrl)=>new URL(apiUrl + '/download');
2349
+
2350
+ const HTTPErrorMessages = {
2351
+ // 4xx Client Errors
2352
+ 400: 'Bad Request',
2353
+ 401: 'Unauthorized',
2354
+ 402: 'Payment Required',
2355
+ 403: 'Forbidden',
2356
+ 404: 'Not Found',
2357
+ 405: 'Method Not Allowed',
2358
+ 406: 'Not Acceptable',
2359
+ 407: 'Proxy Authentication Required',
2360
+ 408: 'Request Timeout',
2361
+ 409: 'Conflict',
2362
+ 410: 'Gone',
2363
+ 411: 'Length Required',
2364
+ 412: 'Precondition Failed',
2365
+ 413: 'Payload Too Large',
2366
+ 414: 'URI Too Long',
2367
+ 415: 'Unsupported Media Type',
2368
+ 416: 'Range Not Satisfiable',
2369
+ 417: 'Expectation Failed',
2370
+ 418: "I'm a teapot",
2371
+ 421: 'Misdirected Request',
2372
+ 422: 'Unprocessable Entity',
2373
+ 423: 'Locked',
2374
+ 424: 'Failed Dependency',
2375
+ 425: 'Too Early',
2376
+ 426: 'Upgrade Required',
2377
+ 428: 'Precondition Required',
2378
+ 429: 'Too Many Requests',
2379
+ 431: 'Request Header Fields Too Large',
2380
+ 451: 'Unavailable For Legal Reasons',
2381
+ // 5xx Server Errors
2382
+ 500: 'Internal Server Error',
2383
+ 501: 'Not Implemented',
2384
+ 502: 'Bad Gateway',
2385
+ 503: 'Service Unavailable',
2386
+ 504: 'Gateway Timeout',
2387
+ 505: 'HTTP Version Not Supported',
2388
+ 506: 'Variant Also Negotiates',
2389
+ 507: 'Insufficient Storage',
2390
+ 508: 'Loop Detected',
2391
+ 510: 'Not Extended',
2392
+ 511: 'Network Authentication Required'
2393
+ };
2394
+ const prepareDownloadUrl = (apiUrl, guid)=>new URL(`${apiUrl}/data/download/${guid}`);
2395
+ class HTTPError extends Error {
2396
+ constructor(status, message, responseData){
2397
+ super(message), this.status = status, this.responseData = responseData;
2398
+ this.name = 'HTTPError';
2399
+ }
2400
+ }
2401
+ const fetchFencePresignedURL = async ({ guid, method = 'GET', onAbort = ()=>null, signal = undefined })=>{
2402
+ const csrfToken = selectCSRFToken(coreStore.getState());
2403
+ const headers = new Headers();
2404
+ headers.set('Content-Type', 'application/json');
2405
+ let accessToken = undefined;
2406
+ if (process.env.NODE_ENV === 'development') {
2407
+ // NOTE: This cookie can only be accessed from the client side
2408
+ // in development mode. Otherwise, the cookie is set as httpOnly
2409
+ accessToken = cookiesNext.getCookie('credentials_token');
2410
+ }
2411
+ if (csrfToken) headers.set('X-CSRF-Token', csrfToken);
2412
+ if (accessToken) headers.set('Authorization', `Bearer ${accessToken}`);
2413
+ const url = prepareDownloadUrl(`${GEN3_FENCE_API}/user`, guid);
2414
+ try {
2415
+ const response = await fetch(url.toString(), {
2416
+ method: method,
2417
+ headers: headers,
2418
+ ...signal ? {
2419
+ signal: signal
2420
+ } : {}
2421
+ });
2422
+ // Check if the response is ok before proceeding
2423
+ if (!response.ok) {
2424
+ let errorMessage;
2425
+ let errorData;
2426
+ try {
2427
+ // Attempt to parse error response as JSON
2428
+ errorData = await response.json();
2429
+ errorMessage = errorData.message || response.statusText;
2430
+ } catch {
2431
+ // If JSON parsing fails, use status text
2432
+ errorMessage = response.statusText;
2433
+ }
2434
+ throw new HTTPError(response.status, errorMessage, errorData);
1551
2435
  }
1552
- });
1553
- if (!res.ok) throw new Error('An error occurred while fetching the data.');
1554
- return await res.json();
2436
+ const jsonData = await response.json();
2437
+ return jsonData;
2438
+ } catch (error) {
2439
+ if (error instanceof Error) {
2440
+ if (error.name === 'AbortError') {
2441
+ onAbort?.();
2442
+ }
2443
+ }
2444
+ throw error;
2445
+ }
1555
2446
  };
1556
- const useGetStatus = ()=>{
1557
- const fetcher = ()=>fetchJson(`${GEN3_GUPPY_API}${statusEndpoint}`);
1558
- return useSWR('explorerStatus', fetcher);
2447
+
2448
+ const CoreProvider = ({ children })=>{
2449
+ return /*#__PURE__*/ React.createElement(reactRedux.Provider, {
2450
+ store: coreStore
2451
+ }, children);
1559
2452
  };
2453
+
1560
2454
  /**
1561
- * The main endpoint used in templating Exploration page queries.
1562
- * Includes table, filter and aggregation query types and leverages guppyApi defined in ./gupplApi.ts
1563
- * Query templates support filters where applicable
2455
+ * Creates the authzApi for checking arborist permissions for a selected user
2456
+ * @see https://petstore.swagger.io/?url=https://raw.githubusercontent.com/uc-cdis/arborist/master/docs/openapi.yaml#/auth/get_auth_mapping
2457
+ * @see https://github.com/uc-cdis/arborist/blob/master/docs/relationships.simplified.png
2458
+ * @returns: An arborist response dict of user permissions {method, service} for each resource path.
2459
+ */ const authzApi = gen3Api.injectEndpoints({
2460
+ endpoints: (builder)=>({
2461
+ getAuthzMappings: builder.query({
2462
+ query: ()=>`${GEN3_AUTHZ_API}/mapping`
2463
+ })
2464
+ })
2465
+ });
2466
+ const { useGetAuthzMappingsQuery } = authzApi;
2467
+ const selectAuthzMapping = authzApi.endpoints.getAuthzMappings.select();
2468
+ const selectAuthzMappingData = toolkit.createSelector(selectAuthzMapping, (authzMapping)=>authzMapping?.data ?? {
2469
+ mappings: []
2470
+ });
2471
+
2472
+ const HasEnoughData = (data, keys, limit)=>{
2473
+ const numEmptyKeys = keys.filter((k)=>Object.hasOwn(data, k) && typeof data[k] === 'string' && data[k].trim() === '').length;
2474
+ return numEmptyKeys < limit;
2475
+ };
2476
+ /**
2477
+ * Defines metadataApi service using a base URL and expected endpoints. Derived from gen3Api core API.
1564
2478
  *
1565
- * @param endpoints - Defines endpoints used in Exploration page:
1566
- * @param getAllFieldsForType - A mapping query that returns all property key names vertex types specified.
1567
- * @see https://github.com/uc-cdis/guppy/blob/master/doc/queries.md#mapping-query
1568
- * @param getAccessibleData - An aggregation histogram counts query that filters based on access type
1569
- * @see https://github.com/uc-cdis/guppy/blob/master/doc/queries.md#accessibility-argument-for-regular-tier-access-level
1570
- * @param getRawDataAndTotalCounts - Queries both _totalCount for selected vertex types and
1571
- * tabular results containing the raw data in the rows of selected vertex types
1572
- * @see https://github.com/uc-cdis/guppy/blob/master/doc/queries.md#1-total-count-aggregation
1573
- * @param getAggs - An aggregated histogram counts query which outputs vertex property frequencies
1574
- * @param getSubAggs - TODO: not sure what this one does. Looks like nested aggregation
1575
- * @param getCounts - Returns total counts of a vertex type
1576
- * @returns: A guppy API endpoint for templating queriable data displayed on the exploration page
1577
- */ const explorerApi = guppyApi.injectEndpoints({
2479
+ * @param endpoints - Defines endpoints used in discovery page
2480
+ * @param getAggMDS - Queries aggregate metadata service
2481
+ * @see https://github.com/uc-cdis/metadata-service/blob/master/docs/agg_mds.md
2482
+ * @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
2483
+ * @param getMDS - Queries normal metadata service
2484
+ * @see https://petstore.swagger.io/?url=https://raw.githubusercontent.com/uc-cdis/metadata-service/master/docs/openapi.yaml#/Query/search_metadata_metadata_get
2485
+ * @param getIndexAggMDS - queries the Aggregate Metadata service and returns all common passed in indexKeys
2486
+ * @param getTags - Probably refering to Aggregate metadata service summary statistics query
2487
+ * @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
2488
+ * @param getData - Looks like a duplicate of getMDS handler. unused in ./frontend package
2489
+ * @param getCrosswalkData - Maps ids from one source to another
2490
+ * @returns: A guppy download API for fetching bulk metadata
2491
+ */ const metadataApi = gen3Api.injectEndpoints({
1578
2492
  endpoints: (builder)=>({
1579
- getAllFieldsForType: builder.query({
1580
- query: (type)=>({
1581
- query: `{ _mapping ${type} } }`
1582
- }),
2493
+ getAggMDS: builder.query({
2494
+ query: ({ offset, pageSize })=>{
2495
+ return `${GEN3_MDS_API}/aggregate/metadata?flatten=true&pagination=true&offset=${offset}&limit=${pageSize}`;
2496
+ },
1583
2497
  transformResponse: (response, _meta, params)=>{
1584
- return response[params.type];
1585
- }
1586
- }),
1587
- getAccessibleData: builder.query({
1588
- query: ({ type, fields, accessType })=>{
1589
- const fieldParts = fields.map((field)=>`${field} { histogram { key count } }`);
1590
- return {
1591
- query: `_aggregation {
1592
- ${type} (accessibility: ${accessType}) {
1593
- ${fieldParts.join(',')}
1594
- }
1595
- }`
1596
- };
1597
- }
1598
- }),
1599
- getRawDataAndTotalCounts: builder.query({
1600
- query: ({ type, fields, filters, sort, offset = 0, size = 20, accessibility = exports.Accessibility.ALL, format = undefined })=>{
1601
- const gqlFilter = convertFilterSetToGqlFilter(filters);
1602
- const params = [
1603
- ...sort ? [
1604
- '$sort: JSON'
1605
- ] : [],
1606
- ...gqlFilter ? [
1607
- '$filter: JSON'
1608
- ] : [],
1609
- ...format ? [
1610
- '$format: Format'
1611
- ] : []
1612
- ].join(',');
1613
- const queryLine = `query getRawDataAndTotalCounts (${params}) {`;
1614
- const dataParams = [
1615
- ...format ? [
1616
- 'format: $format'
1617
- ] : [],
1618
- ...sort ? [
1619
- 'sort: $sort'
1620
- ] : [],
1621
- ...gqlFilter ? [
1622
- 'filter: $filter'
1623
- ] : []
1624
- ].join(',');
1625
- const dataTypeLine = `${type} (accessibility: ${accessibility}, offset: ${offset}, first: ${size},
1626
- ${dataParams}) {`;
1627
- const typeAggsLine = `${type} (${gqlFilter && 'filter: $filter,'} accessibility: ${accessibility}) {`;
1628
- const processedFields = fields.map((field)=>rawDataQueryStrForEachField(field));
1629
- const query = `${queryLine}
1630
- ${dataTypeLine}
1631
- ${processedFields.join(' ')}
1632
- }
1633
- _aggregation {
1634
- ${typeAggsLine}
1635
- _totalCount
1636
- }
1637
- }
1638
- }`;
1639
- const variables = {
1640
- ...sort && {
1641
- sort
1642
- },
1643
- ...gqlFilter && {
1644
- filter: gqlFilter
1645
- },
1646
- ...format && {
1647
- format
1648
- }
1649
- };
1650
2498
  return {
1651
- query,
1652
- variables
1653
- };
1654
- }
1655
- }),
1656
- getAggs: builder.query({
1657
- query: ({ type, fields, filters, accessibility = exports.Accessibility.ALL })=>{
1658
- const queryStart = isFilterEmpty(filters) ? `
1659
- query getAggs {
1660
- _aggregation {
1661
- ${type} (accessibility: ${accessibility}) {` : `query getAggs ($filter: JSON) {
1662
- _aggregation {
1663
- ${type} (filter: $filter, filterSelf: false, accessibility: ${accessibility}) {`;
1664
- const query = `${queryStart}
1665
- ${fields.map((field)=>histogramQueryStrForEachField(field))}
1666
- }
1667
- }
1668
- }`;
1669
- const queryBody = {
1670
- query: query,
1671
- variables: {
1672
- filter: convertFilterSetToGqlFilter(filters)
1673
- }
2499
+ data: response.results.map((x)=>{
2500
+ const objValues = Object.values(x);
2501
+ const firstValue = objValues ? objValues.at(0) : undefined;
2502
+ return firstValue ? firstValue[params.studyField] : undefined;
2503
+ }),
2504
+ hits: response.pagination.hits
1674
2505
  };
1675
- return queryBody;
1676
- },
1677
- transformResponse: (response, _meta, args)=>{
1678
- return processHistogramResponse(response.data._aggregation[args.type]);
1679
2506
  }
1680
2507
  }),
1681
- getSubAggs: builder.query({
1682
- query: ({ type, mainField, termsFields = undefined, missingFields = undefined, numericAggAsText = false, gqlFilter = undefined, accessibility = exports.Accessibility.ALL })=>{
1683
- const nestedAggFields = {
1684
- termsFields: termsFields,
1685
- missingFields: missingFields
1686
- };
1687
- const query = `query getSubAggs ( ${gqlFilter ?? '$filter: JSON,'} $nestedAggFields: JSON) {
1688
- _aggregation {
1689
- ${type} ( ${gqlFilter ?? 'filter: $filter, filterSelf: false,'} nestedAggFields: $nestedAggFields, accessibility: ${accessibility}) {
1690
- ${nestedHistogramQueryStrForEachField(mainField, numericAggAsText)}
1691
- }`;
1692
- return {
1693
- query: query,
1694
- variables: {
1695
- ...gqlFilter && {
1696
- filter: convertFilterSetToGqlFilter(gqlFilter)
1697
- },
1698
- nestedAggFields: nestedAggFields
1699
- }
1700
- };
2508
+ getIndexAggMDS: builder.query({
2509
+ query: ({ pageSize })=>{
2510
+ return `${GEN3_MDS_API}/aggregate/metadata?limit=${pageSize}`;
1701
2511
  },
1702
- transformResponse: (response, _meta, args)=>{
1703
- return processHistogramResponse(response.data._aggregation[args.type]);
1704
- }
1705
- }),
1706
- getCounts: builder.query({
1707
- query: ({ type, filters, accessibility = exports.Accessibility.ALL })=>{
1708
- const gqlFilters = convertFilterSetToGqlFilter(filters);
1709
- const queryLine = `query totalCounts ${gqlFilters ? '($filter: JSON)' : ''}{`;
1710
- const typeAggsLine = `${type} ${gqlFilters ? '(filter: $filter, ' : '('} accessibility: ${accessibility}) {`;
1711
- const query = `${queryLine} _aggregation {
1712
- ${typeAggsLine}
1713
- _totalCount
1714
- }
1715
- }
1716
- }`;
1717
- return {
1718
- query: query,
1719
- variables: {
1720
- ...gqlFilters && {
1721
- filter: gqlFilters
1722
- }
2512
+ transformResponse: (response, _meta, params)=>{
2513
+ const dataFromIndexes = params.indexKeys.reduce((acc, key)=>{
2514
+ if (response[key]) {
2515
+ acc.push(...response[key]);
1723
2516
  }
1724
- };
1725
- },
1726
- transformResponse: (response, _meta, args)=>{
1727
- return response.data._aggregation[args.type]._totalCount;
1728
- }
1729
- }),
1730
- getFieldCountSummary: builder.query({
1731
- query: ({ type, field, filters, accessibility = exports.Accessibility.ALL })=>{
1732
- const gqlFilters = convertFilterSetToGqlFilter(filters);
1733
- const query = `query summary ($filter: JSON) {
1734
- _aggregation {
1735
- ${type} (filter: $filter, accessibility: ${accessibility}) {
1736
- ${field} {
1737
- histogram {
1738
- sum,
1739
- }
1740
- }
1741
- }
1742
- }
1743
- }`;
2517
+ return acc;
2518
+ }, []);
1744
2519
  return {
1745
- query: query,
1746
- variables: {
1747
- ...gqlFilters && {
1748
- filter: gqlFilters
2520
+ data: dataFromIndexes.map((x)=>{
2521
+ const objValues = Object.values(x);
2522
+ const objIds = Object.keys(x);
2523
+ let firstValue = objValues ? objValues.at(0) : undefined;
2524
+ if (params?.filterEmpty) {
2525
+ // remove any data that has < limit if defined
2526
+ if (firstValue && !HasEnoughData(firstValue[params.studyField], params.filterEmpty.keys, params.filterEmpty.limit)) firstValue = undefined;
1749
2527
  }
1750
- }
2528
+ return firstValue ? {
2529
+ gen3MDSGUID: objIds.at(0),
2530
+ ...firstValue[params.studyField]
2531
+ } : undefined;
2532
+ }).filter((x)=>x !== undefined),
2533
+ hits: dataFromIndexes.length
1751
2534
  };
1752
2535
  }
1753
2536
  }),
1754
- getFieldsForIndex: builder.query({
1755
- query: (index)=>{
2537
+ getMDS: builder.query({
2538
+ query: ({ guidType, offset, pageSize })=>{
2539
+ return `${GEN3_MDS_API}/metadata?data=True&_guid_type=${guidType}&limit=${pageSize}&offset=${offset}`;
2540
+ },
2541
+ transformResponse: (response, _meta)=>{
1756
2542
  return {
1757
- query: `{
1758
- _mapping ${index}
1759
- }`
2543
+ data: Object.keys(response).map((guid)=>response[guid]),
2544
+ hits: Object.keys(response).length
1760
2545
  };
1761
- },
1762
- transformResponse: (response)=>{
1763
- return response['_mapping'];
1764
2546
  }
1765
2547
  }),
1766
- generalGQL: builder.query({
1767
- query: ({ query, variables })=>{
2548
+ getTags: builder.query({
2549
+ query: ()=>'tags'
2550
+ }),
2551
+ getData: builder.query({
2552
+ query: (params)=>({
2553
+ url: `metadata?${params}`
2554
+ })
2555
+ }),
2556
+ // TODO: Move this to own slice
2557
+ getCrosswalkData: builder.query({
2558
+ queryFn: async (arg, _queryApi, _extraOptions, fetchWithBQ)=>{
2559
+ const queryMultiple = async ()=>{
2560
+ let result = [];
2561
+ const queue = Queue({
2562
+ concurrency: 15
2563
+ });
2564
+ for (const id of arg.ids){
2565
+ queue.push(async (callback)=>{
2566
+ const response = await fetchWithBQ({
2567
+ url: `${GEN3_CROSSWALK_API}/metadata/${id}`
2568
+ });
2569
+ if (response.error) {
2570
+ return {
2571
+ error: response.error
2572
+ };
2573
+ }
2574
+ const toData = arg.toPaths.reduce((acc, path)=>{
2575
+ acc[path.id] = jsonpathPlus.JSONPath({
2576
+ json: response.data,
2577
+ path: `$.[${path.dataPath}]`,
2578
+ resultType: 'value'
2579
+ })?.[0] ?? 'n/a';
2580
+ return acc;
2581
+ }, {});
2582
+ result = [
2583
+ ...result,
2584
+ {
2585
+ from: id,
2586
+ to: toData
2587
+ }
2588
+ ];
2589
+ if (callback) callback();
2590
+ return result;
2591
+ });
2592
+ }
2593
+ return new Promise((resolve, reject)=>{
2594
+ queue.start((err)=>{
2595
+ if (err) {
2596
+ reject(err);
2597
+ } else {
2598
+ resolve(result);
2599
+ }
2600
+ });
2601
+ });
2602
+ };
2603
+ const result = await queryMultiple();
1768
2604
  return {
1769
- query: query,
1770
- variables: variables
2605
+ data: result
1771
2606
  };
1772
2607
  }
1773
2608
  })
1774
2609
  })
1775
2610
  });
1776
- // query for aggregate data
1777
- // convert the function below to typescript
1778
- const histogramQueryStrForEachField = (field)=>{
1779
- const splittedFieldArray = field.split('.');
1780
- const splittedField = splittedFieldArray.shift();
1781
- if (splittedFieldArray.length === 0) {
1782
- return `
1783
- ${splittedField} {
1784
- histogram {
1785
- key
1786
- count
1787
- }
1788
- }`;
1789
- }
1790
- return `
1791
- ${splittedField} {
1792
- ${histogramQueryStrForEachField(splittedFieldArray.join('.'))}
1793
- }`;
1794
- };
1795
- const nestedHistogramQueryStrForEachField = (mainField, numericAggAsText)=>`
1796
- ${mainField} {
1797
- ${numericAggAsText ? 'asTextHistogram' : 'histogram'} {
1798
- key
1799
- count
1800
- missingFields {
1801
- field
1802
- count
1803
- }
1804
- termsFields {
1805
- field
1806
- count
1807
- terms {
1808
- key
1809
- count
1810
- }
1811
- }
1812
- }
1813
- }`;
1814
- const rawDataQueryStrForEachField = (field)=>{
1815
- const splitFieldArray = field.split('.');
1816
- const splitField = splitFieldArray.shift();
1817
- if (splitFieldArray.length === 0) {
1818
- return `
1819
- ${splitField}
1820
- `;
1821
- }
1822
- return `
1823
- ${splitField} {
1824
- ${rawDataQueryStrForEachField(splitFieldArray.join('.'))}
1825
- }`;
1826
- };
1827
- const useGetArrayTypes = ()=>{
1828
- {
1829
- const { data, error } = useGetStatus();
1830
- if (error) {
1831
- return {};
1832
- }
1833
- return data ? data['indices'] : {};
1834
- }
1835
- };
1836
- const { useGetRawDataAndTotalCountsQuery, useGetAccessibleDataQuery, useGetAllFieldsForTypeQuery, useGetAggsQuery, useLazyGetAggsQuery, useGetSubAggsQuery, useGetCountsQuery, useGetFieldCountSummaryQuery, useGetFieldsForIndexQuery, useGeneralGQLQuery, useLazyGeneralGQLQuery } = explorerApi;
2611
+ const { useGetAggMDSQuery, useGetMDSQuery, useGetTagsQuery, useGetDataQuery, useGetCrosswalkDataQuery, useLazyGetCrosswalkDataQuery, useGetIndexAggMDSQuery } = metadataApi;
1837
2612
 
1838
- /**
1839
- * Flattens a deep nested JSON object skipping
1840
- * the first level to avoid potentially flattening
1841
- * non-nested data.
1842
- * @param {JSON} json
1843
- */ function flattenJson(json) {
1844
- const flattenedJson = [];
1845
- Object.keys(json).forEach((key)=>{
1846
- flattenedJson.push(flat.flatten(json[key], {
1847
- delimiter: '_'
1848
- }));
1849
- });
1850
- return flattenedJson;
1851
- }
1852
- /**
1853
- * Converts JSON based on a config.
1854
- * @param {JSON} json
1855
- * @param {Object} config
1856
- */ async function conversion(json, config) {
1857
- return Papa.unparse(json, config);
1858
- }
1859
- /**
1860
- * Converts JSON to a specified file format.
1861
- * Defaults to JSON if file format is not supported.
1862
- * @param {JSON} json
1863
- * @param {string} format
1864
- */ async function jsonToFormat(json, format) {
1865
- if (Object.keys(FILE_DELIMITERS).includes(format)) {
1866
- const flatJson = await flattenJson(json);
1867
- const data = await conversion(flatJson, {
1868
- delimiter: FILE_DELIMITERS[format]
1869
- });
1870
- return data;
1871
- }
1872
- return json;
1873
- }
2613
+ // using a random uuid v4 as the namespace
2614
+ const GEN3_APP_NAMESPACE = '7bfaa818-c69c-457e-8d87-413cf60c25f0';
1874
2615
 
2616
+ const getGen3AppId = (name, version)=>{
2617
+ const nameVersion = `${name}::${version}`;
2618
+ return uuid.v5(nameVersion, GEN3_APP_NAMESPACE);
2619
+ };
1875
2620
  /**
1876
- * Prepares a URL for downloading by appending '/download' to the provided apiUrl.
1877
- *
1878
- * @param {string} apiUrl - The base URL to be used for preparing the download URL.
1879
- * @returns {URL} - The prepared download URL as a URL object.
1880
- */ const prepareUrl = (apiUrl)=>new URL(apiUrl + '/download');
1881
- /**
1882
- * Prepares a fetch configuration object for downloading files from Guppy.
1883
- *
1884
- * @param {GuppyFileDownloadRequestParams} parameters - The parameters to include in the request body.
1885
- * @param {string} csrfToken - The CSRF token to include in the request headers.
1886
- * @returns {FetchConfig} - The prepared fetch configuration object.
1887
- */ const prepareFetchConfig = (parameters, csrfToken)=>{
2621
+ * Creates a Gen3App that is dynamically loaded
2622
+ */ const createGen3App = ({ App, name, version, requiredEntityTypes })=>{
2623
+ // create a stable id for this app
2624
+ const nameVersion = `${name}::${version}`;
2625
+ const id = uuid.v5(nameVersion, GEN3_APP_NAMESPACE);
2626
+ // need to create store and provider.
2627
+ // return a component representing this app
2628
+ // if component gets added to a list, then the list can be iterated in index.js and each provider component can be added
2629
+ // a route can be setup for the app
2630
+ // need to register its name, category, path, data requirements
2631
+ // this will be used to build page3
2632
+ // click app link
2633
+ // const store = configureStore({
2634
+ // // TODO allow user to pass in a reducer in CreateGen3AppOptions?
2635
+ // reducer: (state) => state,
2636
+ // devTools: {
2637
+ // name: `${nameVersion}::${id}`,
2638
+ // },
2639
+ // });
2640
+ const Gen3AppWrapper = (props)=>{
2641
+ React.useEffect(()=>{
2642
+ document.title = `GEN3 - ${name}`;
2643
+ });
2644
+ return /*#__PURE__*/ React.createElement(App, props);
2645
+ };
2646
+ // add the app to the store
2647
+ coreStore.dispatch(addGen3AppMetadata({
2648
+ id,
2649
+ name,
2650
+ version,
2651
+ requiredEntityTypes
2652
+ }));
2653
+ registerGen3App(name, Gen3AppWrapper);
2654
+ return Gen3AppWrapper;
2655
+ };
2656
+ // ----------------------------------------------------------------------------------------
2657
+ // Apps with Local Storage
2658
+ //
2659
+ const createAppStore = (options)=>{
2660
+ const { name, version, reducers, middleware } = options;
2661
+ const nameVersion = `${name}::${version}`;
2662
+ const id = uuid.v5(nameVersion, GEN3_APP_NAMESPACE);
2663
+ const store = toolkit.configureStore({
2664
+ reducer: reducers,
2665
+ devTools: {
2666
+ name: `${nameVersion}::${id}`
2667
+ },
2668
+ middleware: (getDefaultMiddleware)=>middleware ? getDefaultMiddleware({
2669
+ serializableCheck: {
2670
+ ignoredActions: [
2671
+ reduxPersist.FLUSH,
2672
+ reduxPersist.REHYDRATE,
2673
+ reduxPersist.PAUSE,
2674
+ reduxPersist.PERSIST,
2675
+ reduxPersist.PURGE,
2676
+ reduxPersist.REGISTER
2677
+ ]
2678
+ }
2679
+ }).concat(middleware) : getDefaultMiddleware({
2680
+ serializableCheck: {
2681
+ ignoredActions: [
2682
+ reduxPersist.FLUSH,
2683
+ reduxPersist.REHYDRATE,
2684
+ reduxPersist.PAUSE,
2685
+ reduxPersist.PERSIST,
2686
+ reduxPersist.PURGE,
2687
+ reduxPersist.REGISTER
2688
+ ]
2689
+ }
2690
+ })
2691
+ });
2692
+ const context = /*#__PURE__*/ React.createContext(null);
2693
+ const useAppSelector = reactRedux.createSelectorHook(context);
2694
+ const useAppDispatch = reactRedux.createDispatchHook(context);
2695
+ const useAppStore = reactRedux.createStoreHook(context);
1888
2696
  return {
1889
- method: 'POST',
1890
- headers: {
1891
- 'Content-Type': 'application/json',
1892
- ...csrfToken !== undefined && {
1893
- 'X-CSRF-Token': csrfToken
1894
- }
1895
- },
1896
- body: JSON.stringify({
1897
- type: parameters.type,
1898
- filter: convertFilterSetToGqlFilter(parameters.filter),
1899
- accessibility: parameters.accessibility,
1900
- fields: parameters?.fields,
1901
- sort: parameters?.sort
1902
- })
2697
+ id: id,
2698
+ AppStore: store,
2699
+ AppContext: context,
2700
+ useAppSelector: useAppSelector,
2701
+ useAppDispatch: useAppDispatch,
2702
+ useAppStore: useAppStore
1903
2703
  };
1904
2704
  };
1905
- /**
1906
- * Downloads a file from Guppy using the provided parameters.
1907
- * It will optionally convert the data to the specified format.
1908
- *
1909
- * @param {DownloadFromGuppyParams} parameters - The parameters to use for the download request.
1910
- * @param onStart - The function to call when the download starts.
1911
- * @param onDone - The function to call when the download is done.
1912
- * @param onError - The function to call when the download fails.
1913
- * @param onAbort - The function to call when the download is aborted.
1914
- * @param signal - AbortSignal to use for the request.
1915
- */ const downloadFromGuppyToBlob = async ({ parameters, onStart = ()=>null, onDone = (_)=>null, onError = (_)=>null, onAbort = ()=>null, signal = undefined })=>{
1916
- const csrfToken = selectCSRFToken(coreStore.getState());
1917
- onStart?.();
1918
- const url = prepareUrl(GEN3_GUPPY_API);
1919
- const fetchConfig = prepareFetchConfig(parameters, csrfToken);
1920
- fetch(url.toString(), {
1921
- ...fetchConfig,
1922
- ...signal ? {
1923
- signal: signal
1924
- } : {}
1925
- }).then(async (response)=>{
1926
- if (!response.ok) {
1927
- throw new Error(response.statusText);
1928
- }
1929
- let jsonData = await response.json();
1930
- if (parameters?.rootPath && parameters.rootPath) {
1931
- // if rootPath is provided, extract the data from the rootPath
1932
- jsonData = jsonpathPlus.JSONPath({
1933
- json: jsonData,
1934
- path: `$.[${parameters.rootPath}]`,
1935
- resultType: 'value'
1936
- });
1937
- }
1938
- // convert the data to the specified format and return a Blob
1939
- let str = '';
1940
- if (parameters.format === 'json') {
1941
- str = JSON.stringify(jsonData);
1942
- } else {
1943
- const convertedData = await jsonToFormat(jsonData, parameters.format);
1944
- if (isJSONObject(convertedData)) {
1945
- str = JSON.stringify(convertedData, null, 2);
1946
- } else {
1947
- str = convertedData;
1948
- }
1949
- }
1950
- return new Blob([
1951
- str
1952
- ], {
1953
- type: 'application/json'
2705
+ const createGen3AppWithOwnStore = (options)=>{
2706
+ const { App, id, name, version, requiredEntityTypes, store, context } = options;
2707
+ // need to create store and provider.
2708
+ // return a component representing this app
2709
+ // if component gets added to a list, then the list can be iterated in index.js and each provider component can be added
2710
+ // a route can be setup for the app
2711
+ // need to register its name, category, path, data requirements
2712
+ // this will be used to build page3
2713
+ // click app link
2714
+ const Gen3AppWrapper = (props)=>{
2715
+ React.useEffect(()=>{
2716
+ document.title = `GEN3 - ${name}`;
1954
2717
  });
1955
- }).then((blob)=>onDone?.(blob)).catch((error)=>{
1956
- // Abort is handle as an exception
1957
- if (error.name == 'AbortError') {
1958
- // handle abort()
1959
- onAbort?.();
1960
- }
1961
- onError?.(error);
1962
- });
2718
+ return /*#__PURE__*/ React.createElement(reactRedux.Provider, {
2719
+ store: store,
2720
+ context: context
2721
+ }, /*#__PURE__*/ React.createElement(reactCookie.CookiesProvider, null, /*#__PURE__*/ React.createElement(App, props)));
2722
+ };
2723
+ // add the app to the store
2724
+ coreStore.dispatch(addGen3AppMetadata({
2725
+ id,
2726
+ name,
2727
+ version,
2728
+ requiredEntityTypes
2729
+ }));
2730
+ registerGen3App(name, Gen3AppWrapper);
2731
+ return Gen3AppWrapper;
1963
2732
  };
1964
- const downloadJSONDataFromGuppy = async ({ parameters, onAbort = ()=>null, signal = undefined })=>{
1965
- const csrfToken = selectCSRFToken(coreStore.getState());
1966
- const url = prepareUrl(GEN3_GUPPY_API);
1967
- const fetchConfig = prepareFetchConfig(parameters, csrfToken);
1968
- try {
1969
- const response = await fetch(url.toString(), {
1970
- ...fetchConfig,
1971
- ...signal ? {
1972
- signal: signal
1973
- } : {}
1974
- });
1975
- let jsonData = await response.json();
1976
- if (parameters?.rootPath && parameters.rootPath) {
1977
- // if rootPath is provided, extract the data from the rootPath
1978
- jsonData = jsonpathPlus.JSONPath({
1979
- json: jsonData,
1980
- path: `$.[${parameters.rootPath}]`,
1981
- resultType: 'value'
1982
- });
1983
- }
1984
- // convert the data to the specified format and return a Blob
1985
- return jsonData;
1986
- } catch (error) {
1987
- // Abort is handle as an exception
1988
- if (error.name == 'AbortError') {
1989
- // handle abort()
1990
- onAbort?.();
2733
+
2734
+ const createAppApiForRTKQ = (reducerPath, baseQuery)=>{
2735
+ const appContext = React__namespace.createContext(null);
2736
+ const useAppSelector = reactRedux.useSelector.withTypes();
2737
+ const useAppDispatch = reactRedux.createDispatchHook(appContext);
2738
+ const useAppStore = reactRedux.createStoreHook(appContext);
2739
+ const appCreateApi = react.buildCreateApi(react.coreModule(), react.reactHooksModule({
2740
+ hooks: {
2741
+ useDispatch: useAppDispatch,
2742
+ useSelector: useAppSelector,
2743
+ useStore: useAppStore
1991
2744
  }
1992
- throw new Error(error);
1993
- }
1994
- };
1995
- const useGetIndexFields = (index)=>{
1996
- const { data } = useGetFieldsForIndexQuery(index);
1997
- return data ?? [];
2745
+ }));
2746
+ const appRTKQApi = appCreateApi({
2747
+ reducerPath: reducerPath,
2748
+ baseQuery: baseQuery ?? react.fetchBaseQuery({
2749
+ baseUrl: `${GEN3_API}`,
2750
+ prepareHeaders: (headers)=>{
2751
+ headers.set('Content-Type', 'application/json');
2752
+ let accessToken = undefined;
2753
+ if (process.env.NODE_ENV === 'development') {
2754
+ // NOTE: This cookie can only be accessed from the client side
2755
+ // in development mode. Otherwise, the cookie is set as httpOnly
2756
+ accessToken = cookiesNext.getCookie('credentials_token');
2757
+ }
2758
+ if (accessToken) headers.set('Authorization', `Bearer ${accessToken}`);
2759
+ return headers;
2760
+ }
2761
+ }),
2762
+ endpoints: ()=>({})
2763
+ });
2764
+ const appMiddleware = appRTKQApi.middleware;
2765
+ const appStore = toolkit.configureStore({
2766
+ reducer: {
2767
+ [appRTKQApi.reducerPath]: appRTKQApi.reducer
2768
+ },
2769
+ middleware: (getDefaultMiddleware)=>getDefaultMiddleware({
2770
+ serializableCheck: {
2771
+ ignoredActions: [
2772
+ reduxPersist.FLUSH,
2773
+ reduxPersist.REHYDRATE,
2774
+ reduxPersist.PAUSE,
2775
+ reduxPersist.PERSIST,
2776
+ reduxPersist.PURGE,
2777
+ reduxPersist.REGISTER
2778
+ ]
2779
+ }
2780
+ }).concat(appMiddleware)
2781
+ });
2782
+ return {
2783
+ useAppSelector: useAppSelector,
2784
+ useAppDispatch: useAppDispatch,
2785
+ useAppStore: useAppStore,
2786
+ AppContext: appContext,
2787
+ appApi: appRTKQApi,
2788
+ appContext: appContext,
2789
+ appStore: appStore
2790
+ };
1998
2791
  };
1999
2792
 
2793
+ const graphQLWithTags = gen3Api.enhanceEndpoints({
2794
+ addTagTypes: [
2795
+ 'graphQL'
2796
+ ]
2797
+ });
2000
2798
  /**
2001
- * Creates a Guppy API for fetching bulk (> 10K rows) elasticsearch data
2002
- * @see https://github.com/uc-cdis/guppy/blob/master/doc/download.md
2003
- * @param endpoints - Resolver function which configures the query with
2004
- * type, filter, accessibility, fields, and sort arguments
2005
- * @returns: A guppy download API for fetching bulk metadata
2006
- */ const downloadRequestApi = gen3Api.injectEndpoints({
2799
+ * Creates a graphQLAPI for graphql queries to elasticsearch indices via guppy
2800
+ * @see https://github.com/uc-cdis/guppy/blob/master/doc/queries.md
2801
+ * @param query - Resolver function which configures the graphql query with graphQLParams argument
2802
+ * @returns: A guppy search API for fetching metadata
2803
+ */ const graphQLAPI = graphQLWithTags.injectEndpoints({
2007
2804
  endpoints: (builder)=>({
2008
- downloadFromGuppy: builder.mutation({
2009
- query: ({ type, filter, accessibility, fields, sort })=>{
2010
- const queryBody = {
2011
- filter: convertFilterSetToGqlFilter(filter),
2012
- ...{
2013
- type,
2014
- accessibility,
2015
- fields,
2016
- sort
2017
- }
2018
- };
2019
- return {
2020
- url: `${GEN3_GUPPY_API}/download`,
2805
+ graphQL: builder.query({
2806
+ query: (graphQLParams)=>({
2807
+ url: `${GEN3_GUPPY_API}/graphql`,
2021
2808
  method: 'POST',
2022
- queryBody,
2023
- cache: 'no-cache'
2024
- };
2025
- },
2026
- transformResponse: (response)=>{
2027
- return response;
2028
- }
2809
+ credentials: 'include',
2810
+ body: JSON.stringify(graphQLParams)
2811
+ })
2029
2812
  })
2030
2813
  })
2031
2814
  });
2032
- const { useDownloadFromGuppyMutation } = downloadRequestApi;
2815
+ const { useGraphQLQuery } = graphQLAPI;
2033
2816
 
2034
2817
  /**
2035
2818
  * returns a response from the AI search service
@@ -2474,8 +3257,11 @@ exports.GEN3_REDIRECT_URL = GEN3_REDIRECT_URL;
2474
3257
  exports.GEN3_SOWER_API = GEN3_SOWER_API;
2475
3258
  exports.GEN3_SUBMISSION_API = GEN3_SUBMISSION_API;
2476
3259
  exports.GEN3_WORKSPACE_API = GEN3_WORKSPACE_API;
3260
+ exports.HTTPError = HTTPError;
3261
+ exports.HTTPErrorMessages = HTTPErrorMessages;
2477
3262
  exports.clearActiveWorkspaceId = clearActiveWorkspaceId;
2478
3263
  exports.clearCohortFilters = clearCohortFilters;
3264
+ exports.clearDataLibrarySelection = clearDataLibrarySelection;
2479
3265
  exports.cohortReducer = cohortReducer;
2480
3266
  exports.convertFilterSetToGqlFilter = convertFilterSetToGqlFilter;
2481
3267
  exports.coreStore = coreStore;
@@ -2484,6 +3270,7 @@ exports.createAppStore = createAppStore;
2484
3270
  exports.createGen3App = createGen3App;
2485
3271
  exports.createGen3AppWithOwnStore = createGen3AppWithOwnStore;
2486
3272
  exports.createUseCoreDataHook = createUseCoreDataHook;
3273
+ exports.dataLibrarySelectionReducer = dataLibrarySelectionReducer;
2487
3274
  exports.downloadFromGuppyToBlob = downloadFromGuppyToBlob;
2488
3275
  exports.downloadJSONDataFromGuppy = downloadJSONDataFromGuppy;
2489
3276
  exports.drsHostnamesReducer = drsHostnamesReducer;
@@ -2493,11 +3280,14 @@ exports.extractFilterValue = extractFilterValue;
2493
3280
  exports.extractIndexAndFieldNameFromFullFieldName = extractIndexAndFieldNameFromFullFieldName;
2494
3281
  exports.extractIndexFromFullFieldName = extractIndexFromFullFieldName;
2495
3282
  exports.fetchFence = fetchFence;
3283
+ exports.fetchFencePresignedURL = fetchFencePresignedURL;
2496
3284
  exports.fetchJson = fetchJson;
2497
3285
  exports.fetchUserState = fetchUserState;
2498
3286
  exports.fieldNameToTitle = fieldNameToTitle;
2499
3287
  exports.gen3Api = gen3Api;
2500
3288
  exports.getGen3AppId = getGen3AppId;
3289
+ exports.getNumberOfItemsInDatalist = getNumberOfItemsInDatalist;
3290
+ exports.getTimestamp = getTimestamp;
2501
3291
  exports.graphQLAPI = graphQLAPI;
2502
3292
  exports.graphQLWithTags = graphQLWithTags;
2503
3293
  exports.guppyAPISliceMiddleware = guppyAPISliceMiddleware;
@@ -2506,11 +3296,16 @@ exports.guppyApiReducer = guppyApiReducer;
2506
3296
  exports.guppyApiSliceReducerPath = guppyApiSliceReducerPath;
2507
3297
  exports.handleOperation = handleOperation;
2508
3298
  exports.hideModal = hideModal;
3299
+ exports.isAdditionalDataItem = isAdditionalDataItem;
3300
+ exports.isArray = isArray;
2509
3301
  exports.isAuthenticated = isAuthenticated;
3302
+ exports.isCohortItem = isCohortItem;
2510
3303
  exports.isErrorWithMessage = isErrorWithMessage;
2511
3304
  exports.isFetchBaseQueryError = isFetchBaseQueryError;
2512
3305
  exports.isFetchParseError = isFetchParseError;
3306
+ exports.isFileItem = isFileItem;
2513
3307
  exports.isFilterEmpty = isFilterEmpty;
3308
+ exports.isFilterSet = isFilterSet;
2514
3309
  exports.isGuppyAggregationData = isGuppyAggregationData;
2515
3310
  exports.isHistogramData = isHistogramData;
2516
3311
  exports.isHistogramDataAArray = isHistogramDataAArray;
@@ -2524,15 +3319,20 @@ exports.isHttpStatusError = isHttpStatusError;
2524
3319
  exports.isJSONObject = isJSONObject;
2525
3320
  exports.isJSONValue = isJSONValue;
2526
3321
  exports.isJSONValueArray = isJSONValueArray;
3322
+ exports.isNotDefined = isNotDefined;
3323
+ exports.isObject = isObject;
2527
3324
  exports.isOperationWithField = isOperationWithField;
2528
3325
  exports.isPending = isPending;
2529
3326
  exports.isProgramUrl = isProgramUrl;
2530
3327
  exports.isRootUrl = isRootUrl;
3328
+ exports.isString = isString;
2531
3329
  exports.isWorkspaceActive = isWorkspaceActive;
2532
3330
  exports.isWorkspaceRunningOrStopping = isWorkspaceRunningOrStopping;
2533
3331
  exports.listifyMethodsFromMapping = listifyMethodsFromMapping;
2534
3332
  exports.logoutFence = logoutFence;
3333
+ exports.prepareUrl = prepareUrl;
2535
3334
  exports.prependIndexToFieldName = prependIndexToFieldName;
3335
+ exports.processHistogramResponse = processHistogramResponse;
2536
3336
  exports.projectCodeFromResourcePath = projectCodeFromResourcePath;
2537
3337
  exports.rawDataQueryStrForEachField = rawDataQueryStrForEachField;
2538
3338
  exports.removeCohortFilter = removeCohortFilter;
@@ -2567,16 +3367,20 @@ exports.setActiveWorkspaceId = setActiveWorkspaceId;
2567
3367
  exports.setActiveWorkspaceStatus = setActiveWorkspaceStatus;
2568
3368
  exports.setCohortFilter = setCohortFilter;
2569
3369
  exports.setDRSHostnames = setDRSHostnames;
3370
+ exports.setDataLibraryListSelection = setDataLibraryListSelection;
2570
3371
  exports.setRequestedWorkspaceStatus = setRequestedWorkspaceStatus;
2571
3372
  exports.showModal = showModal;
2572
3373
  exports.submissionApi = submissionApi;
2573
3374
  exports.trimFirstFieldNameToTitle = trimFirstFieldNameToTitle;
2574
3375
  exports.updateCohortFilter = updateCohortFilter;
3376
+ exports.useAddDataLibraryListMutation = useAddDataLibraryListMutation;
2575
3377
  exports.useAddNewCredentialMutation = useAddNewCredentialMutation;
2576
3378
  exports.useAskQuestionMutation = useAskQuestionMutation;
2577
3379
  exports.useAuthorizeFromCredentialsMutation = useAuthorizeFromCredentialsMutation;
2578
3380
  exports.useCoreDispatch = useCoreDispatch;
2579
3381
  exports.useCoreSelector = useCoreSelector;
3382
+ exports.useDataLibrary = useDataLibrary;
3383
+ exports.useDeleteDataLibraryListMutation = useDeleteDataLibraryListMutation;
2580
3384
  exports.useDownloadFromGuppyMutation = useDownloadFromGuppyMutation;
2581
3385
  exports.useFetchUserDetailsQuery = useFetchUserDetailsQuery;
2582
3386
  exports.useGeneralGQLQuery = useGeneralGQLQuery;
@@ -2593,8 +3397,11 @@ exports.useGetCSRFQuery = useGetCSRFQuery;
2593
3397
  exports.useGetCountsQuery = useGetCountsQuery;
2594
3398
  exports.useGetCredentialsQuery = useGetCredentialsQuery;
2595
3399
  exports.useGetCrosswalkDataQuery = useGetCrosswalkDataQuery;
3400
+ exports.useGetDataLibraryListQuery = useGetDataLibraryListQuery;
3401
+ exports.useGetDataLibraryListsQuery = useGetDataLibraryListsQuery;
2596
3402
  exports.useGetDataQuery = useGetDataQuery;
2597
3403
  exports.useGetDictionaryQuery = useGetDictionaryQuery;
3404
+ exports.useGetDownloadQuery = useGetDownloadQuery;
2598
3405
  exports.useGetExternalLoginsQuery = useGetExternalLoginsQuery;
2599
3406
  exports.useGetFieldCountSummaryQuery = useGetFieldCountSummaryQuery;
2600
3407
  exports.useGetFieldsForIndexQuery = useGetFieldsForIndexQuery;
@@ -2626,6 +3433,7 @@ exports.useLazyFetchUserDetailsQuery = useLazyFetchUserDetailsQuery;
2626
3433
  exports.useLazyGeneralGQLQuery = useLazyGeneralGQLQuery;
2627
3434
  exports.useLazyGetAggsQuery = useLazyGetAggsQuery;
2628
3435
  exports.useLazyGetCrosswalkDataQuery = useLazyGetCrosswalkDataQuery;
3436
+ exports.useLazyGetDownloadQuery = useLazyGetDownloadQuery;
2629
3437
  exports.useLazyGetExternalLoginsQuery = useLazyGetExternalLoginsQuery;
2630
3438
  exports.useLazyGetProjectsQuery = useLazyGetProjectsQuery;
2631
3439
  exports.useLazyGetSowerJobListQuery = useLazyGetSowerJobListQuery;
@@ -2636,6 +3444,7 @@ exports.useRemoveCredentialMutation = useRemoveCredentialMutation;
2636
3444
  exports.useSetCurrentPayModelMutation = useSetCurrentPayModelMutation;
2637
3445
  exports.useSubmitSowerJobMutation = useSubmitSowerJobMutation;
2638
3446
  exports.useTerminateWorkspaceMutation = useTerminateWorkspaceMutation;
3447
+ exports.useUpdateDataLibraryListMutation = useUpdateDataLibraryListMutation;
2639
3448
  exports.useUser = useUser;
2640
3449
  exports.useUserAuth = useUserAuth;
2641
3450
  exports.userHasCreateOrUpdateOnAnyProject = userHasCreateOrUpdateOnAnyProject;