@gen3/core 0.10.54 → 0.10.56

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