@gen3/core 0.11.21 → 0.11.23

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 (47) hide show
  1. package/dist/cjs/index.js +1902 -1364
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/dts/features/cohort/cohortManagerSelector.d.ts +193 -0
  4. package/dist/dts/features/cohort/cohortManagerSelector.d.ts.map +1 -0
  5. package/dist/dts/features/cohort/cohortManagerSlice.d.ts +112 -0
  6. package/dist/dts/features/cohort/cohortManagerSlice.d.ts.map +1 -0
  7. package/dist/dts/features/cohort/index.d.ts +9 -6
  8. package/dist/dts/features/cohort/index.d.ts.map +1 -1
  9. package/dist/dts/features/cohort/reducers.d.ts +2 -2
  10. package/dist/dts/features/cohort/storage/CohortStorage.d.ts +70 -0
  11. package/dist/dts/features/cohort/storage/CohortStorage.d.ts.map +1 -0
  12. package/dist/dts/features/cohort/tests/cohortManager.unit.test.d.ts +2 -0
  13. package/dist/dts/features/cohort/tests/cohortManager.unit.test.d.ts.map +1 -0
  14. package/dist/dts/features/cohort/types.d.ts +28 -0
  15. package/dist/dts/features/cohort/types.d.ts.map +1 -1
  16. package/dist/dts/features/cohort/utils.d.ts +3 -0
  17. package/dist/dts/features/cohort/utils.d.ts.map +1 -1
  18. package/dist/dts/features/dataLibrary/index.d.ts +3 -2
  19. package/dist/dts/features/dataLibrary/index.d.ts.map +1 -1
  20. package/dist/dts/features/dataLibrary/storage/LocalStorageService.d.ts +1 -1
  21. package/dist/dts/features/dataLibrary/storage/LocalStorageService.d.ts.map +1 -1
  22. package/dist/dts/features/dataLibrary/utils.d.ts +1 -2
  23. package/dist/dts/features/dataLibrary/utils.d.ts.map +1 -1
  24. package/dist/dts/features/facets/index.d.ts +2 -0
  25. package/dist/dts/features/facets/index.d.ts.map +1 -0
  26. package/dist/dts/features/facets/types.d.ts +20 -0
  27. package/dist/dts/features/facets/types.d.ts.map +1 -0
  28. package/dist/dts/features/filters/index.d.ts +1 -2
  29. package/dist/dts/features/filters/index.d.ts.map +1 -1
  30. package/dist/dts/features/filters/types.d.ts +4 -6
  31. package/dist/dts/features/filters/types.d.ts.map +1 -1
  32. package/dist/dts/features/user/userSliceRTK.d.ts +3 -3
  33. package/dist/dts/hooks.d.ts +4 -4
  34. package/dist/dts/index.d.ts +2 -1
  35. package/dist/dts/index.d.ts.map +1 -1
  36. package/dist/dts/reducers.d.ts +3 -3
  37. package/dist/dts/store.d.ts +4 -4
  38. package/dist/dts/utils/index.d.ts +4 -3
  39. package/dist/dts/utils/index.d.ts.map +1 -1
  40. package/dist/dts/utils/time.d.ts +1 -0
  41. package/dist/dts/utils/time.d.ts.map +1 -1
  42. package/dist/esm/index.js +1885 -1362
  43. package/dist/esm/index.js.map +1 -1
  44. package/dist/index.d.ts +265 -58
  45. package/package.json +2 -2
  46. package/dist/dts/features/cohort/cohortSlice.d.ts +0 -204
  47. package/dist/dts/features/cohort/cohortSlice.d.ts.map +0 -1
package/dist/esm/index.js CHANGED
@@ -9,9 +9,9 @@ import { persistReducer, FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER, pers
9
9
  import createWebStorage from 'redux-persist/lib/storage/createWebStorage';
10
10
  import { isEqual } from 'lodash';
11
11
  import { PersistGate } from 'redux-persist/integration/react';
12
+ import { openDB } from 'idb';
12
13
  import { useDeepCompareMemo } from 'use-deep-compare';
13
14
  import { parse } from 'graphql';
14
- import { openDB } from 'idb';
15
15
  import { v5 } from 'uuid';
16
16
  import { CookiesProvider } from 'react-cookie';
17
17
  import useSWR from 'swr';
@@ -168,7 +168,7 @@ const userAuthApi = createApi({
168
168
  } catch (error) {
169
169
  if (error instanceof Error) {
170
170
  return {
171
- error: error
171
+ error: error.message
172
172
  };
173
173
  } else {
174
174
  return {
@@ -697,6 +697,9 @@ const isTimeGreaterThan = (startTime, minutes)=>{
697
697
  const diffMinutes = diffMs / (1000 * 60);
698
698
  return diffMinutes > minutes;
699
699
  };
700
+ const getTimestamp = ()=>{
701
+ return new Date(Date.now()).toLocaleString();
702
+ };
700
703
 
701
704
  const NO_WORKSPACE_ID = 'none';
702
705
  const initialState$4 = {
@@ -797,15 +800,39 @@ const guppyAPISliceMiddleware = guppyApi.middleware;
797
800
  const guppyApiSliceReducerPath = guppyApi.reducerPath;
798
801
  const guppyApiReducer = guppyApi.reducer;
799
802
 
803
+ const defaultCohortNameGenerator = ()=>`Custom cohort ${new Date().toLocaleString('en-CA', {
804
+ timeZone: 'America/Chicago',
805
+ hour12: false
806
+ }).replace(',', '')}`;
807
+ const isNameUnique = (entities, name, excludeId)=>{
808
+ const trimmedName = name.trim();
809
+ if (!trimmedName) return false;
810
+ return !entities.some((cohort)=>cohort && cohort.id !== excludeId && cohort.name.trim().toLowerCase() === trimmedName.toLowerCase());
811
+ };
812
+ const generateUniqueName = (entities, baseName)=>{
813
+ const trimmedBaseName = baseName.trim();
814
+ // If base name is unique, use it
815
+ if (isNameUnique(entities, trimmedBaseName)) {
816
+ return trimmedBaseName;
817
+ }
818
+ // Find a unique name by appending numbers
819
+ let counter = 1;
820
+ let uniqueName;
821
+ do {
822
+ uniqueName = `${trimmedBaseName} (${counter})`;
823
+ counter++;
824
+ }while (!isNameUnique(entities, uniqueName))
825
+ return uniqueName;
826
+ };
827
+
800
828
  /**
801
829
  * Cohorts in Gen3 are defined as a set of filters for each index in the data.
802
830
  * This means one cohort id defined for all "tabs" in CohortBuilder (explorer)
803
831
  * Switching a cohort is means that all the cohorts for the index changes.
804
- */ const UNSAVED_COHORT_NAME = 'Cohort';
805
- const NULL_COHORT_ID = 'null_cohort_id';
832
+ */ const DEFAULT_COHORT_NAME = 'Cohort';
806
833
  const newCohort = ({ filters = {}, customName })=>{
807
- const ts = new Date();
808
- const newName = customName;
834
+ const ts = new Date().toISOString();
835
+ const newName = customName ?? defaultCohortNameGenerator();
809
836
  const newId = createCohortId();
810
837
  return {
811
838
  name: newName,
@@ -813,73 +840,92 @@ const newCohort = ({ filters = {}, customName })=>{
813
840
  filters: filters ?? {},
814
841
  modified: false,
815
842
  saved: false,
816
- modified_datetime: ts.toISOString(),
843
+ createdDatetime: ts,
844
+ modifiedDatetime: ts,
817
845
  counts: {}
818
846
  };
819
847
  };
820
848
  const createCohortId = ()=>nanoid();
821
849
  const cohortsAdapter = createEntityAdapter({
822
850
  sortComparer: (a, b)=>{
823
- if (a.modified_datetime <= b.modified_datetime) return 1;
851
+ if (a.modifiedDatetime <= b.modifiedDatetime) return 1;
824
852
  else return -1;
825
853
  },
826
854
  selectId: (cohort)=>cohort.id
827
855
  });
828
856
  // Create an initial unsaved cohort
829
857
  const initialCohort = newCohort({
830
- customName: UNSAVED_COHORT_NAME
858
+ customName: DEFAULT_COHORT_NAME
831
859
  });
832
860
  const emptyInitialState = cohortsAdapter.getInitialState({
833
- currentCohort: initialCohort.id,
861
+ currentCohortId: initialCohort.id,
834
862
  message: undefined
835
863
  });
836
864
  // Set the initial cohort in the adapter state
837
865
  const initialState$3 = cohortsAdapter.setOne(emptyInitialState, initialCohort);
838
- const getCurrentCohort = (state)=>{
839
- if (state.currentCohort) {
840
- return state.currentCohort;
841
- }
842
- return NULL_COHORT_ID;
843
- };
866
+ const getCurrentCohortId = (state)=>state.currentCohortId;
844
867
  /**
845
868
  * Redux slice for cohort filters
846
- */ const cohortSlice = createSlice({
869
+ */ const cohortManagerSlice = createSlice({
847
870
  name: 'cohort',
848
871
  initialState: initialState$3,
849
872
  reducers: {
850
- addNewDefaultUnsavedCohort: (state)=>{
873
+ createNewCohort: (state, action)=>{
874
+ const baseName = action.payload.name || `Cohort`;
875
+ const uniqueName = generateUniqueName(Object.values(state.entities), baseName);
851
876
  const cohort = newCohort({
852
- customName: UNSAVED_COHORT_NAME
877
+ filters: action.payload.filters,
878
+ customName: uniqueName
853
879
  });
854
880
  cohortsAdapter.addOne(state, cohort);
855
- state.currentCohort = cohort.id;
856
- state.message = [
857
- `newCohort|${cohort.name}|${cohort.id}`
858
- ];
881
+ state.currentCohortId = cohort.id;
859
882
  },
860
883
  updateCohortName: (state, action)=>{
884
+ const { id, name } = action.payload;
861
885
  cohortsAdapter.updateOne(state, {
862
- id: getCurrentCohort(state),
886
+ id: id,
863
887
  changes: {
864
- name: action.payload,
888
+ name: name,
865
889
  modified: true,
866
- modified_datetime: new Date().toISOString()
890
+ modifiedDatetime: new Date().toISOString()
867
891
  }
868
892
  });
869
893
  },
870
894
  removeCohort: (state, action)=>{
871
- const removedCohort = state.entities[action?.payload?.id || getCurrentCohort(state)];
872
- cohortsAdapter.removeOne(state, action?.payload?.id || getCurrentCohort(state));
895
+ const { id: cohortId } = action.payload;
896
+ const removedCohortName = state.entities[cohortId].name;
897
+ const totalCohorts = Object.keys(state.entities).length;
898
+ if (totalCohorts <= 1) {
899
+ cohortsAdapter.removeAll(state);
900
+ const defaultCohort = newCohort({
901
+ filters: {},
902
+ customName: DEFAULT_COHORT_NAME
903
+ });
904
+ cohortsAdapter.addOne(state, defaultCohort);
905
+ state.currentCohortId = defaultCohort.id;
906
+ if (action?.payload.shouldShowMessage) {
907
+ state.message = [
908
+ `deleteCohort|${removedCohortName}|${state.currentCohortId}`
909
+ ];
910
+ }
911
+ return;
912
+ }
913
+ cohortsAdapter.removeOne(state, cohortId);
914
+ // deleted the current cohort so set to most recent cohort
915
+ if (state.currentCohortId === cohortId) {
916
+ const remainingIds = Object.keys(state.entities);
917
+ state.currentCohortId = remainingIds[0];
918
+ }
873
919
  if (action?.payload.shouldShowMessage) {
874
920
  state.message = [
875
- `deleteCohort|${removedCohort?.name}|${state.currentCohort}`
921
+ `deleteCohort|${removedCohortName}|${state.currentCohortId}`
876
922
  ];
877
923
  }
878
924
  },
879
925
  // adds a filter to the cohort filter set at the given index
880
926
  updateCohortFilter: (state, action)=>{
881
927
  const { index, field, filter } = action.payload;
882
- const currentCohortId = getCurrentCohort(state);
928
+ const currentCohortId = getCurrentCohortId(state);
883
929
  if (!state.entities[currentCohortId]) {
884
930
  return;
885
931
  }
@@ -897,13 +943,13 @@ const getCurrentCohort = (state)=>{
897
943
  }
898
944
  },
899
945
  modified: true,
900
- modified_datetime: new Date().toISOString()
946
+ modifiedDatetime: new Date().toISOString()
901
947
  }
902
948
  });
903
949
  },
904
950
  setCohortFilter: (state, action)=>{
905
951
  const { index, filters } = action.payload;
906
- const currentCohortId = getCurrentCohort(state);
952
+ const currentCohortId = getCurrentCohortId(state);
907
953
  if (!state.entities[currentCohortId]) {
908
954
  console.error(`no cohort with id=${currentCohortId} defined`);
909
955
  return;
@@ -916,12 +962,12 @@ const getCurrentCohort = (state)=>{
916
962
  [index]: filters
917
963
  },
918
964
  modified: true,
919
- modified_datetime: new Date().toISOString()
965
+ modifiedDatetime: new Date().toISOString()
920
966
  }
921
967
  });
922
968
  },
923
969
  setCohortIndexFilters: (state, action)=>{
924
- const currentCohortId = getCurrentCohort(state);
970
+ const currentCohortId = getCurrentCohortId(state);
925
971
  if (!state.entities[currentCohortId]) {
926
972
  console.error(`no cohort with id=${currentCohortId} defined`);
927
973
  return;
@@ -931,14 +977,14 @@ const getCurrentCohort = (state)=>{
931
977
  changes: {
932
978
  filters: action.payload.filters,
933
979
  modified: true,
934
- modified_datetime: new Date().toISOString()
980
+ modifiedDatetime: new Date().toISOString()
935
981
  }
936
982
  });
937
983
  },
938
984
  // removes a filter to the cohort filter set at the given index
939
985
  removeCohortFilter: (state, action)=>{
940
986
  const { index, field } = action.payload;
941
- const currentCohortId = getCurrentCohort(state);
987
+ const currentCohortId = getCurrentCohortId(state);
942
988
  if (!state.entities[currentCohortId]) {
943
989
  console.error(`no cohort with id=${currentCohortId} defined`);
944
990
  return;
@@ -960,14 +1006,25 @@ const getCurrentCohort = (state)=>{
960
1006
  }
961
1007
  },
962
1008
  modified: true,
963
- modified_datetime: new Date().toISOString()
1009
+ modifiedDatetime: new Date().toISOString()
964
1010
  }
965
1011
  });
966
1012
  },
1013
+ duplicateCohort: (state)=>{
1014
+ const currentCohortId = getCurrentCohortId(state);
1015
+ const currentCohort = state.entities[currentCohortId];
1016
+ const newName = generateUniqueName(Object.values(state.entities), currentCohort.name);
1017
+ const duplicatedCohort = newCohort({
1018
+ filters: currentCohort.filters,
1019
+ customName: newName
1020
+ });
1021
+ cohortsAdapter.addOne(state, duplicatedCohort);
1022
+ state.currentCohortId = duplicatedCohort.id;
1023
+ },
967
1024
  // removes all filters from the cohort filter set at the given index
968
1025
  clearCohortFilters: (state, action)=>{
969
1026
  const { index } = action.payload;
970
- const currentCohortId = getCurrentCohort(state);
1027
+ const currentCohortId = getCurrentCohortId(state);
971
1028
  if (!state.entities[currentCohortId]) {
972
1029
  console.error(`no cohort with id=${currentCohortId} defined`);
973
1030
  return;
@@ -987,30 +1044,12 @@ const getCurrentCohort = (state)=>{
987
1044
  }
988
1045
  },
989
1046
  modified: true,
990
- modified_datetime: new Date().toISOString()
991
- }
992
- });
993
- },
994
- discardCohortChanges: (state, action)=>{
995
- const { index, id, filters } = action.payload;
996
- const cohortId = id ?? getCurrentCohort(state);
997
- cohortsAdapter.updateOne(state, {
998
- id: cohortId,
999
- changes: {
1000
- filters: {
1001
- ...state.entities[cohortId].filters,
1002
- [index]: filters || {
1003
- mode: 'and',
1004
- root: {}
1005
- }
1006
- },
1007
- modified: false,
1008
- modified_datetime: new Date().toISOString()
1047
+ modifiedDatetime: new Date().toISOString()
1009
1048
  }
1010
1049
  });
1011
1050
  },
1012
1051
  setCurrentCohortId: (state, action)=>{
1013
- state.currentCohort = action.payload;
1052
+ state.currentCohortId = action.payload;
1014
1053
  },
1015
1054
  /** @hidden */ setCohortList: (state, action)=>{
1016
1055
  if (!action.payload) {
@@ -1028,104 +1067,10 @@ const getCurrentCohort = (state)=>{
1028
1067
  * @param state - the CoreState
1029
1068
  *
1030
1069
  * @hidden
1031
- */ const cohortSelectors = cohortsAdapter.getSelectors((state)=>state.cohorts.cohort);
1032
- /**
1033
- * Returns an array of all the cohorts
1034
- * @param state - the CoreState
1035
- * @category Cohort
1036
- * @category Selectors
1037
- */ const selectAllCohorts = (state)=>cohortSelectors.selectEntities(state);
1038
- const getCurrentCohortFromCoreState = (state)=>{
1039
- if (state.cohorts.cohort.currentCohort) {
1040
- return state.cohorts.cohort.currentCohort;
1041
- }
1042
- return NULL_COHORT_ID;
1043
- };
1070
+ */ const cohortSelectors = cohortsAdapter.getSelectors((state)=>state.cohorts.cohortManager);
1044
1071
  // Filter actions: addFilter, removeFilter, updateFilter
1045
- const { updateCohortFilter, setCohortFilter, setCohortIndexFilters, removeCohortFilter, clearCohortFilters, removeCohort, discardCohortChanges, addNewDefaultUnsavedCohort, setCurrentCohortId, setCohortList } = cohortSlice.actions;
1046
- const selectCohortFilters = (state)=>{
1047
- const currentCohortId = getCurrentCohortFromCoreState(state);
1048
- return state.cohorts.cohort.entities[currentCohortId]?.filters;
1049
- };
1050
- const selectCurrentCohortFilters = (state)=>{
1051
- const currentCohortId = getCurrentCohortFromCoreState(state);
1052
- return state.cohorts.cohort.entities[currentCohortId]?.filters;
1053
- };
1054
- const selectCurrentCohortId = (state)=>{
1055
- return getCurrentCohort(state.cohorts.cohort);
1056
- };
1057
- const selectCurrentCohort = (state)=>cohortSelectors.selectById(state, getCurrentCohortFromCoreState(state));
1058
- const selectCurrentCohortName = (state)=>cohortSelectors.selectById(state, getCurrentCohortFromCoreState(state)).name;
1059
- /**
1060
- * Select a filter by its name from the current cohort. If the filter is not found
1061
- * returns undefined.
1062
- * @param state - Core
1063
- * @param index which cohort index to select from
1064
- * @param name name of the filter to select
1065
- */ const selectIndexedFilterByName = (state, index, name)=>{
1066
- return cohortSelectors.selectById(state, getCurrentCohortFromCoreState(state)).filters[index]?.root[name];
1067
- };
1068
- /**
1069
- * a thunk to optionally create a caseSet when switching cohorts.
1070
- * Note the assumption if the caseset member has ids then the caseset has previously been created.
1071
- */ const setActiveCohort = (cohortId)=>async (dispatch /* getState */ )=>{
1072
- dispatch(setCurrentCohortId(cohortId));
1073
- };
1074
- /**
1075
- * Returns all the cohorts in the state
1076
- * @param state - the CoreState
1077
- *
1078
- * @category Cohort
1079
- * @category Selectors
1080
- */ const selectAvailableCohorts = (state)=>cohortSelectors.selectAll(state);
1081
- /**
1082
- * Returns if the current cohort is modified
1083
- * @param state - the CoreState
1084
- * @category Cohort
1085
- * @category Selectors
1086
- * @hidden
1087
- */ const selectCurrentCohortModified = (state)=>{
1088
- const cohort = cohortSelectors.selectById(state, getCurrentCohortFromCoreState(state));
1089
- return cohort?.modified;
1090
- };
1091
- /**
1092
- * Returns if the current cohort has been saved
1093
- * @param state - the CoreState
1094
- * @category Cohort
1095
- * @category Selectors
1096
- * @hidden
1097
- */ const selectCurrentCohortSaved = (state)=>{
1098
- const cohort = cohortSelectors.selectById(state, getCurrentCohortFromCoreState(state));
1099
- return cohort?.saved;
1100
- };
1101
- const EmptyFilterSet = {
1102
- mode: 'and',
1103
- root: {}
1104
- };
1105
- /**
1106
- * Select a filter from the index.
1107
- * returns undefined.
1108
- * @param state - Core
1109
- * @param index which cohort index to select from
1110
- */ const selectIndexFilters = (state, index)=>{
1111
- const cohort = cohortSelectors.selectById(state, getCurrentCohortFromCoreState(state));
1112
- if (!cohort) {
1113
- console.error('No Cohort Defined');
1114
- }
1115
- return cohort?.filters?.[index] ?? EmptyFilterSet;
1116
- };
1117
- const setActiveCohortList = (cohorts)=>async (dispatch, getState)=>{
1118
- // set the list of all cohorts
1119
- if (cohorts) {
1120
- dispatch(setCohortList(cohorts));
1121
- return;
1122
- }
1123
- const availableCohorts = selectAllCohorts(getState());
1124
- if (Object.keys(availableCohorts).length === 0) {
1125
- dispatch(addNewDefaultUnsavedCohort());
1126
- }
1127
- };
1128
- const cohortReducer = cohortSlice.reducer;
1072
+ const { createNewCohort, updateCohortFilter, setCohortFilter, setCohortIndexFilters, duplicateCohort, removeCohortFilter, clearCohortFilters, removeCohort, setCurrentCohortId, updateCohortName, setCohortList } = cohortManagerSlice.actions;
1073
+ const cohortReducer = cohortManagerSlice.reducer;
1129
1074
 
1130
1075
  const initialState$2 = {};
1131
1076
  const expandSlice$1 = createSlice({
@@ -1206,7 +1151,7 @@ const cohortReducers = combineReducers({
1206
1151
  filtersExpanded: cohortBuilderFiltersExpandedReducer,
1207
1152
  filtersCombineMode: cohortBuilderFiltersCombineModeReducer,
1208
1153
  sharedFilters: cohortSharedFiltersReducer,
1209
- cohort: cohortReducer
1154
+ cohortManager: cohortReducer
1210
1155
  });
1211
1156
 
1212
1157
  const rootReducer = combineReducers({
@@ -1859,424 +1804,212 @@ const selectAuthzMappingData = createSelector(selectAuthzMapping, (authzMapping)
1859
1804
  mappings: []
1860
1805
  });
1861
1806
 
1862
- const isFileItem = (item)=>{
1863
- return item && 'guid' in item;
1864
- };
1865
- const isAdditionalDataItem = (item)=>{
1866
- return item.itemType === 'AdditionalData'; // TODO resolve this with type from the api
1867
- };
1868
- // Type guard for CohortItem
1869
- const isCohortItem = (item)=>{
1870
- return item && 'data' in item && 'schemaVersion' in item && item.itemType === 'Gen3GraphQL';
1871
- };
1872
- // Type guard for DatalistAPI
1873
- const isDatalistAPI = (value)=>{
1874
- if (typeof value !== 'object' || value === null) {
1875
- return false;
1807
+ class CohortStorage {
1808
+ constructor(config){
1809
+ this.databaseName = config.databaseName;
1810
+ this.storeName = config.storeName;
1811
+ this.schemaVersion = config.schemaVersion || 1;
1876
1812
  }
1877
- const data = value;
1878
- // Check required properties in DataItemBaseData
1879
- if (typeof data.name !== 'string' || typeof data.created_time !== 'string' || typeof data.updated_time !== 'string' || typeof data.version !== 'number' || typeof data.authz !== 'object' || data.authz === null || !Array.isArray(data.authz.authz)) {
1880
- return false;
1813
+ getDb() {
1814
+ try {
1815
+ const storeName = this.storeName;
1816
+ return openDB(this.databaseName, this.schemaVersion, {
1817
+ upgrade (db) {
1818
+ if (!db.objectStoreNames.contains(storeName)) {
1819
+ db.createObjectStore(storeName, {
1820
+ keyPath: 'id'
1821
+ });
1822
+ }
1823
+ }
1824
+ });
1825
+ } catch (error) {
1826
+ let errorMessage = 'Unknown error';
1827
+ if (error instanceof Error) {
1828
+ errorMessage = error.message;
1829
+ }
1830
+ throw new Error(`Database initialization failed: ${errorMessage}`);
1831
+ }
1881
1832
  }
1882
- // Check required properties in DatalistAsItems
1883
- if (typeof data.items !== 'object' || data.items === null || typeof data.items !== 'object') {
1884
- return false;
1833
+ // ===== CREATE OPERATIONS =====
1834
+ /**
1835
+ * Save a single cohort to the database
1836
+ */ async saveCohort(cohort) {
1837
+ try {
1838
+ const db = await this.getDb();
1839
+ const tx = db.transaction(this.storeName, 'readwrite');
1840
+ tx.objectStore(this.storeName).put(cohort);
1841
+ await tx.done;
1842
+ return {
1843
+ status: 200,
1844
+ message: 'cohort added'
1845
+ };
1846
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1847
+ } catch (_error) {
1848
+ return {
1849
+ isError: true,
1850
+ status: 500,
1851
+ message: 'unable to save cohort'
1852
+ };
1853
+ }
1885
1854
  }
1886
- return true;
1887
- };
1888
- /**
1889
- * Type guard for DataLibraryAPIResponse
1890
- */ const isDataLibraryAPIResponse = (obj)=>{
1891
- return typeof obj === 'object' && obj !== null && 'lists' in obj && typeof obj.lists === 'object';
1892
- };
1893
- var DataLibraryStoreMode = /*#__PURE__*/ function(DataLibraryStoreMode) {
1894
- DataLibraryStoreMode["ApiOnly"] = "apiOnly";
1895
- DataLibraryStoreMode["ApiAndLocal"] = "apiAndLocal";
1896
- DataLibraryStoreMode["LocalOnly"] = "localOnly";
1897
- return DataLibraryStoreMode;
1898
- }({});
1899
-
1900
- const processItem = (id, data)=>{
1901
- if (data?.type === 'AdditionalData') {
1902
- return {
1903
- name: data.name,
1904
- itemType: 'AdditionalData',
1905
- description: data?.description,
1906
- documentationUrl: data?.documentationUrl,
1907
- url: data?.url
1855
+ /**
1856
+ * Save multiple cohorts in a single transaction (bulk operation)
1857
+ */ async saveCohorts(cohorts) {
1858
+ if (cohorts.length === 0) return {
1859
+ isError: true,
1860
+ status: 400,
1861
+ message: 'cannot add an empty array'
1908
1862
  };
1863
+ try {
1864
+ const db = await this.getDb();
1865
+ const tx = db.transaction(this.storeName, 'readwrite');
1866
+ // Batch all operations in a single transaction for better performance
1867
+ await Promise.all([
1868
+ ...cohorts.map((cohort)=>tx.store.put({
1869
+ ...cohort,
1870
+ saved: true
1871
+ })),
1872
+ tx.done
1873
+ ]);
1874
+ return {
1875
+ status: 200,
1876
+ message: 'cohorts added'
1877
+ };
1878
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1879
+ } catch (_error) {
1880
+ return {
1881
+ isError: true,
1882
+ status: 500,
1883
+ message: 'unable to save cohort'
1884
+ };
1885
+ }
1909
1886
  }
1910
- return {
1911
- ...data,
1912
- itemType: 'Data',
1913
- guid: data.id,
1914
- id: id
1915
- };
1916
- };
1917
- const buildListItemsGroupedByDataset = (listData)=>{
1918
- const items = Object.entries(listData).reduce((acc, [id, data])=>{
1919
- if (data?.type === 'Gen3GraphQL') {
1920
- const cohortData = data;
1921
- acc[id] = {
1922
- itemType: 'Gen3GraphQL',
1923
- id: data.guid,
1924
- schemaVersion: cohortData.schema_version,
1925
- data: cohortData.data,
1926
- name: data.name,
1927
- index: cohortData.index
1887
+ // ===== READ OPERATIONS =====
1888
+ /**
1889
+ * Get a specific cohort by ID
1890
+ */ async getCohort(id) {
1891
+ try {
1892
+ const db = await this.getDb();
1893
+ const tx = db.transaction(this.storeName, 'readonly');
1894
+ const store = tx.objectStore(this.storeName);
1895
+ const cohort = await store.get(id);
1896
+ return {
1897
+ status: 200,
1898
+ message: 'success',
1899
+ data: cohort
1928
1900
  };
1929
- } else {
1930
- // Dataset
1931
- if (!(data?.dataset_guid && data.dataset_guid in acc)) {
1932
- acc[data.dataset_guid] = {
1933
- id: data.dataset_guid,
1934
- name: '',
1935
- members: {
1936
- [id]: processItem(id, data)
1937
- }
1901
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1902
+ } catch (_error) {
1903
+ return {
1904
+ isError: true,
1905
+ status: 401,
1906
+ message: `cannot find cohort ${id}`
1907
+ };
1908
+ }
1909
+ }
1910
+ /**
1911
+ * Get all cohorts from the database
1912
+ */ async getAllCohorts() {
1913
+ try {
1914
+ const db = await this.getDb();
1915
+ const tx = db.transaction(this.storeName, 'readonly');
1916
+ const store = tx.objectStore(this.storeName);
1917
+ const savedCohorts = await store.getAll();
1918
+ if (!savedCohorts) {
1919
+ return {
1920
+ isError: true,
1921
+ status: 500,
1922
+ message: 'no cohorts returned'
1938
1923
  };
1939
- } else {
1940
- acc[data.dataset_guid].members[id] = processItem(id, data);
1941
1924
  }
1925
+ const cohorts = savedCohorts.reduce((acc, cohort)=>{
1926
+ const { id } = cohort;
1927
+ acc[id] = cohort;
1928
+ return acc;
1929
+ }, {});
1930
+ return {
1931
+ status: 200,
1932
+ message: 'success',
1933
+ data: cohorts
1934
+ };
1935
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1936
+ } catch (_error) {
1937
+ return {
1938
+ isError: true,
1939
+ status: 401,
1940
+ message: 'cannot return cohorts'
1941
+ };
1942
1942
  }
1943
- return acc;
1944
- }, {});
1945
- return items;
1946
- };
1947
- const BuildList = (listId, listData)=>{
1948
- if (!Object.keys(listData).includes('items')) return undefined;
1949
- const items = buildListItemsGroupedByDataset(listData?.items ?? {});
1950
- return {
1951
- items: items,
1952
- version: listData?.version ?? 0,
1953
- created_time: listData?.created_time,
1954
- updated_time: listData?.updated_time,
1955
- name: listData?.name ?? listId,
1956
- id: listId,
1957
- authz: listData?.authz
1958
- };
1959
- };
1960
- /**
1961
- * Constructs a `DataLibrary` object by transforming the input `DataLibraryAPIResponse`.
1962
- *
1963
- * This function takes an API response containing lists and processes each list entry.
1964
- * It uses `BuildList` to build individual list objects for each entry in the provided data.
1965
- * The resulting lists are accumulated and structured into a `DataLibrary` object. which
1966
- * groups File Object by dataset_guid.
1967
- *
1968
- * @param {DataLibraryAPIResponse} data - The API response containing the lists to process.
1969
- * @returns {DataLibrary} A structured `DataLibrary` object containing the processed lists.
1970
- */ const BuildLists = (data)=>{
1971
- return Object.entries(data?.lists).reduce((acc, [listId, listData])=>{
1972
- const list = BuildList(listId, listData);
1973
- if (list) acc[listId] = list;
1974
- return acc;
1975
- }, {});
1976
- };
1977
- /**
1978
- * Calculates the total number of items within a DataList object.
1979
- *
1980
- * @param {DataList} dataList - The DataList object to count items from.
1981
- * @return {number} The total number of items in the DataList.
1982
- */ const getNumberOfItemsInDatalist = (dataList)=>{
1983
- if (!dataList?.items) return 0;
1984
- return Object.values(dataList.items).reduce((count, item)=>{
1985
- if (isCohortItem(item)) {
1986
- return count + 1;
1987
- } else {
1988
- return count + Object.values(item?.members ?? {}).reduce((fileCount, x)=>{
1989
- if (isFileItem(x)) {
1990
- return fileCount + 1;
1991
- }
1992
- return fileCount;
1993
- }, 0);
1994
- }
1995
- }, 0);
1996
- };
1997
- const getTimestamp = ()=>{
1998
- return new Date(Date.now()).toLocaleString();
1999
- };
2000
- const flattenDataList = (dataList)=>{
2001
- // convert datalist into user-data-library API for for updating.
2002
- const items = Object.entries(dataList.items).reduce((acc, [id, value])=>{
2003
- if (isCohortItem(value)) {
2004
- acc[id] = value;
2005
- } else {
1943
+ }
1944
+ /**
1945
+ * Search cohorts by name (case-insensitive partial match)
1946
+ */ async searchCohortsByName(searchTerm) {
1947
+ try {
1948
+ const db = await this.getDb();
1949
+ const tx = db.transaction(this.storeName, 'readonly');
1950
+ const store = tx.objectStore(this.storeName);
1951
+ const allCohorts = await store.getAll(this.storeName);
1952
+ // Filter in memory for partial name matching
1953
+ const searchLower = searchTerm.toLowerCase();
2006
1954
  return {
2007
- ...acc,
2008
- ...value.members
2009
- }; // TODO: might need to convert this to the API version
2010
- }
2011
- return acc;
2012
- }, {});
2013
- return {
2014
- name: dataList.name,
2015
- items: items
2016
- };
2017
- };
2018
- const convertDatasetOrCohortToLibraryListItemsAPI = (list)=>{
2019
- const result = {};
2020
- // Iterate through each entry in the DatasetOrCohort object
2021
- Object.entries(list).forEach(([datasetId, item])=>{
2022
- if (isCohortItem(item)) {
2023
- // Handle cohort items
2024
- result[datasetId] = {
2025
- itemType: 'Gen3GraphQL',
2026
- id: item.id,
2027
- schemaVersion: item.schemaVersion,
2028
- data: item.data,
2029
- name: item.name,
2030
- index: item.index
1955
+ status: 200,
1956
+ message: 'success',
1957
+ data: allCohorts.filter((cohort)=>cohort.name.toLowerCase().includes(searchLower)).reduce((acc, cohort)=>{
1958
+ const { id } = cohort;
1959
+ acc[id] = cohort;
1960
+ return acc;
1961
+ }, {})
2031
1962
  };
2032
- } else {
2033
- // Handle dataset items
2034
- const members = item.members || {};
2035
- // Process each member of the dataset
2036
- Object.entries(members).forEach(([memberId, memberData])=>{
2037
- if (isFileItem(memberData)) {
2038
- result[memberId] = {
2039
- ...memberData.guid && {
2040
- guid: memberData.guid
2041
- },
2042
- ...memberData.name && {
2043
- name: memberData.name
2044
- },
2045
- ...memberData.name && {
2046
- name: memberData.name
2047
- },
2048
- ...memberData.description && {
2049
- description: memberData.description
2050
- },
2051
- ...memberData.type && {
2052
- type: memberData.type
2053
- },
2054
- dataset_guid: datasetId
2055
- };
2056
- } else if (memberData.itemType === 'AdditionalData') {
2057
- // Handle additional data items
2058
- result[memberId] = {
2059
- itemType: 'AdditionalData',
2060
- name: memberData.name,
2061
- description: memberData.description,
2062
- documentationUrl: memberData.documentationUrl,
2063
- url: memberData.url,
2064
- dataset_guid: datasetId
2065
- };
2066
- }
2067
- });
2068
- }
2069
- });
2070
- return result;
2071
- };
2072
- const convertDataLibraryToDataLibraryAPI = (dataLibrary)=>{
2073
- const result = {};
2074
- Object.entries(dataLibrary).forEach(([listId, list])=>{
2075
- result[listId] = {
2076
- name: list.name,
2077
- items: convertDatasetOrCohortToLibraryListItemsAPI(list.items),
2078
- version: list.version,
2079
- created_time: list.created_time,
2080
- updated_time: list.updated_time,
2081
- authz: list.authz
2082
- };
2083
- });
2084
- return result;
2085
- };
2086
- const extractIndexFromDataLibraryCohort = (query)=>{
2087
- try {
2088
- const parsedQuery = parse(query['query']);
2089
- const aggregationField = parsedQuery.definitions.filter((def)=>def.kind === 'OperationDefinition').flatMap((def)=>def.selectionSet.selections).find((sel)=>sel.kind === 'Field' && sel.name.value === '_aggregation');
2090
- if (aggregationField && 'selectionSet' in aggregationField) {
2091
- const indexField = aggregationField?.selectionSet?.selections.find((sel)=>sel.kind === 'Field');
2092
- return indexField ? indexField.name.value : null;
2093
- }
2094
- } catch (error) {
2095
- console.error('Invalid GraphQL query:', error);
2096
- }
2097
- return null;
2098
- };
2099
- /**
2100
- * Takes a list of file items from anb array of manifest entries
2101
- * and creates an Object of Files grouped by their dataset guid, which is
2102
- * used to add these to a Data Library List
2103
- * @param data
2104
- * @param dataFieldMapping
2105
- * @constructor
2106
- */ const extractFileDatasetsInRecords = (data, dataFieldMapping)=>{
2107
- const items = data.reduce((acc, resource)=>{
2108
- const dataObjects = resource[dataFieldMapping.dataObjectField];
2109
- // Check if dataObjects exists and is an array
2110
- if (!dataObjects || !Array.isArray(dataObjects)) {
2111
- return acc;
2112
- }
2113
- const datasetId = resource[dataFieldMapping.datasetIdField]; // Note: typo still preserved
2114
- if (datasetId === undefined) {
2115
- return acc; // Skip if dataset ID is missing
2116
- }
2117
- const datafiles = dataObjects.reduce((dataAcc, dataObject)=>{
2118
- const fileId = dataObject[dataFieldMapping.dataObjectIdField];
2119
- // Skip items without a valid ID
2120
- if (typeof fileId !== 'string' || !fileId) {
2121
- return dataAcc;
2122
- }
2123
- const name = dataObject[dataFieldMapping?.dataObjectNameField ?? 'name'] ?? 'No Name';
2124
- const size = dataObject[dataFieldMapping?.dataObjectSizeField ?? 'size'];
2125
- let sizeString = 'N/A';
2126
- if (typeof size === 'number') {
2127
- sizeString = size.toString();
2128
- }
2129
- if (typeof size === 'string') {
2130
- sizeString = size;
2131
- }
2132
- const md5Sum = dataObject[dataFieldMapping?.dataObjectMd5sumField ?? 'md5sum'] ?? 'N/A';
2133
- const url = dataObject[dataFieldMapping?.dataObjectUrlField ?? 'url'] ?? 'N/A';
2134
- let fileType = 'GA4GH_DRS';
2135
- if (dataFieldMapping?.dataObjectFileTypeValue) fileType = dataFieldMapping.dataObjectFileTypeValue;
2136
- if (dataFieldMapping?.dataObjectFileTypeField) fileType = dataObject[dataFieldMapping?.dataObjectFileTypeField];
1963
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1964
+ } catch (_error) {
2137
1965
  return {
2138
- ...dataAcc,
2139
- [fileId]: {
2140
- dataset_guid: datasetId,
2141
- id: fileId,
2142
- guid: fileId,
2143
- itemType: 'Data',
2144
- name: name,
2145
- size: sizeString,
2146
- md5sum: md5Sum,
2147
- type: fileType,
2148
- url: url
2149
- }
1966
+ isError: true,
1967
+ status: 401,
1968
+ message: 'cannot find cohorts'
2150
1969
  };
2151
- }, {});
2152
- return {
2153
- ...acc,
2154
- ...datafiles
2155
- };
2156
- }, {});
2157
- return items;
2158
- };
2159
-
2160
- const DATABASE_NAME = 'Gen3DataLibrary';
2161
- const STORE_NAME = 'DataLibraryLists';
2162
- class LocalStorageService {
2163
- getDb() {
2164
- return openDB(DATABASE_NAME, 1, {
2165
- upgrade (db) {
2166
- if (!db.objectStoreNames.contains(STORE_NAME)) {
2167
- db.createObjectStore(STORE_NAME, {
2168
- keyPath: 'id'
2169
- });
2170
- }
2171
- }
2172
- });
1970
+ }
2173
1971
  }
2174
- async getList(id) {
2175
- const db = await this.getDb();
2176
- const tx = db.transaction(STORE_NAME, 'readonly');
2177
- const store = tx.objectStore(STORE_NAME);
2178
- const lists = await store.get(id);
2179
- if (lists) {
1972
+ /**
1973
+ * Count total number of cohorts
1974
+ */ async getCohortCount() {
1975
+ try {
1976
+ const db = await this.getDb();
1977
+ const total = await db.count(this.storeName);
2180
1978
  return {
2181
1979
  status: 200,
2182
1980
  message: 'success',
2183
- lists: {
2184
- [id]: {
2185
- id: id,
2186
- ...lists,
2187
- items: lists.items
2188
- }
2189
- }
1981
+ data: total
2190
1982
  };
2191
- } else {
1983
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1984
+ } catch (_error) {
2192
1985
  return {
2193
1986
  isError: true,
2194
- status: 500,
2195
- message: `${id} does not exist`
1987
+ status: 401,
1988
+ message: 'cannot find cohort count'
2196
1989
  };
2197
1990
  }
2198
1991
  }
2199
- async getLists() {
2200
- const db = await this.getDb();
2201
- const tx = db.transaction(STORE_NAME, 'readonly');
2202
- const store = tx.objectStore(STORE_NAME);
2203
- const lists = await store.getAll();
2204
- if (!lists) {
2205
- return {
2206
- isError: true,
2207
- status: 500,
2208
- message: 'no lists returned'
2209
- };
2210
- }
2211
- const listMap = lists.reduce((acc, x)=>{
2212
- const { id } = x;
2213
- acc[id] = x;
2214
- return acc;
2215
- }, {});
2216
- const datalists = BuildLists({
2217
- lists: listMap
2218
- });
2219
- return {
2220
- status: 200,
2221
- message: 'success',
2222
- lists: datalists
2223
- };
2224
- }
2225
- async addList(list) {
2226
- const timestamp = getTimestamp();
2227
- try {
2228
- const db = await this.getDb();
2229
- const tx = db.transaction(STORE_NAME, 'readwrite');
2230
- const id = nanoid(); // Create an id for the list
2231
- tx.objectStore(STORE_NAME).put({
2232
- id,
2233
- version: 0,
2234
- items: list?.items ?? {},
2235
- creator: '{{subject_id}}',
2236
- authz: {
2237
- version: 0,
2238
- authz: [
2239
- `/users/{{subject_id}}/user-library/lists/${id}`
2240
- ]
2241
- },
2242
- name: list?.name ?? 'New List',
2243
- created_time: timestamp,
2244
- updated_time: timestamp
2245
- });
2246
- await tx.done;
2247
- return {
2248
- status: 200,
2249
- message: 'list added'
2250
- };
2251
- } catch (_error) {
2252
- return {
2253
- isError: true,
2254
- status: 500,
2255
- message: `unable to add list ${list?.name ?? 'New List'}`
2256
- };
2257
- }
2258
- }
2259
- async updateList(id, update) {
2260
- const { name, items } = update;
1992
+ // ===== UPDATE OPERATIONS =====
1993
+ /**
1994
+ * Update an existing cohort (full replacement)
1995
+ */ async updateCohort(cohort) {
2261
1996
  try {
2262
1997
  const db = await this.getDb();
2263
- const tx = db.transaction(STORE_NAME, 'readwrite');
2264
- const store = tx.objectStore(STORE_NAME);
2265
- const listData = await store.get(id);
2266
- if (!listData) {
2267
- throw new Error(`List ${id} does not exist`);
1998
+ // Verify cohort exists before updating
1999
+ const tx = db.transaction(this.storeName, 'readwrite');
2000
+ const store = tx.objectStore(this.storeName);
2001
+ const existing = await store.get(cohort.id);
2002
+ if (!existing) {
2003
+ return {
2004
+ isError: true,
2005
+ status: 401,
2006
+ message: 'cohort not found'
2007
+ };
2268
2008
  }
2269
2009
  const timestamp = getTimestamp();
2270
- const version = listData.version ? listData.version + 1 : 0;
2271
2010
  const updated = {
2272
- ...listData,
2273
- ...{
2274
- name,
2275
- items
2276
- },
2277
- version: version,
2278
- updated_time: timestamp,
2279
- created_time: listData.created_time
2011
+ ...existing,
2012
+ modifiedDatetime: timestamp
2280
2013
  };
2281
2014
  store.put(updated);
2282
2015
  await tx.done;
@@ -2292,18 +2025,26 @@ class LocalStorageService {
2292
2025
  return {
2293
2026
  isError: true,
2294
2027
  status: 500,
2295
- message: `Unable to update list: ${id}. Error: ${errorMessage}`
2028
+ message: `Unable to update cohort: ${cohort.id}. Error: ${errorMessage}`
2296
2029
  };
2297
2030
  }
2298
2031
  }
2299
- async deleteList(id) {
2032
+ // ===== DELETE OPERATIONS =====
2033
+ /**
2034
+ * Delete a specific cohort by ID
2035
+ */ async deleteCohort(id) {
2300
2036
  try {
2301
2037
  const db = await this.getDb();
2302
- const tx = db.transaction(STORE_NAME, 'readwrite');
2303
- const store = tx.objectStore(STORE_NAME);
2304
- const item = await store.get(id);
2305
- if (!item) {
2306
- throw new Error(`List ${id} does not exist`);
2038
+ const tx = db.transaction(this.storeName, 'readwrite');
2039
+ const store = tx.objectStore(this.storeName);
2040
+ // Verify cohort exists before deleting
2041
+ const existing = await db.get(this.storeName, id);
2042
+ if (!existing) {
2043
+ return {
2044
+ isError: true,
2045
+ status: 401,
2046
+ message: 'cohort not found'
2047
+ };
2307
2048
  }
2308
2049
  store.delete(id);
2309
2050
  await tx.done;
@@ -2316,926 +2057,1708 @@ class LocalStorageService {
2316
2057
  return {
2317
2058
  isError: true,
2318
2059
  status: 500,
2319
- message: `Unable to delete list: ${id}. Error: ${errorMessage}`
2060
+ message: `Unable to delete cohort: ${id}. Error: ${errorMessage}`
2320
2061
  };
2321
2062
  }
2322
2063
  }
2323
- async clearLists() {
2064
+ /**
2065
+ * Delete all cohorts from the database
2066
+ */ async deleteAllCohorts() {
2324
2067
  try {
2325
2068
  const db = await this.getDb();
2326
- const tx = db.transaction(STORE_NAME, 'readwrite');
2327
- tx.objectStore(STORE_NAME).clear();
2069
+ const tx = db.transaction(this.storeName, 'readwrite');
2070
+ const store = tx.objectStore(this.storeName);
2071
+ store.clear();
2328
2072
  await tx.done;
2329
2073
  return {
2330
2074
  status: 200,
2331
- message: 'list cleared'
2075
+ message: `all cohorts deleted`
2332
2076
  };
2333
2077
  } catch (error) {
2334
2078
  const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred';
2335
2079
  return {
2336
2080
  isError: true,
2337
2081
  status: 500,
2338
- message: `Unable to clear library. Error: ${errorMessage}`
2082
+ message: `Unable to delete all cohorts. Error: ${errorMessage}`
2339
2083
  };
2340
2084
  }
2341
2085
  }
2342
- async cacheLists(data) {
2343
- if (!data || typeof data !== 'object') {
2344
- return {
2345
- isError: true,
2346
- status: 500,
2347
- message: 'Invalid or missing lists property in request'
2348
- };
2349
- }
2350
- const allLists = Object.entries(data).reduce((acc, [id, x])=>{
2351
- if (!isDatalistAPI(x)) return acc;
2352
- acc[id] = {
2353
- ...x
2354
- };
2355
- return acc;
2356
- }, {});
2086
+ // ===== UTILITY OPERATIONS =====
2087
+ /**
2088
+ * Check if a cohort exists
2089
+ */ async cohortExists(id) {
2357
2090
  try {
2358
2091
  const db = await this.getDb();
2359
- const tx = db.transaction(STORE_NAME, 'readwrite');
2360
- for (const [id, list] of Object.entries(allLists)){
2361
- tx.objectStore(STORE_NAME).put({
2362
- id,
2363
- ...list
2364
- });
2365
- }
2366
- await tx.done;
2092
+ // Verify cohort exists before deleting
2093
+ const existing = await db.get(this.storeName, id);
2367
2094
  return {
2368
2095
  status: 200,
2369
- message: 'success'
2096
+ message: `${id}: ${existing ? 'true' : 'false'}`
2370
2097
  };
2371
2098
  } catch (error) {
2372
2099
  const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred';
2373
- return {
2374
- isError: true,
2375
- status: 200,
2376
- message: `unable to cache library to local storage. Error: ${errorMessage}`
2377
- };
2378
- }
2379
- }
2380
- async cacheList(id, data) {
2381
- if (!data || typeof data !== 'object') {
2382
2100
  return {
2383
2101
  isError: true,
2384
2102
  status: 500,
2385
- message: 'Invalid or missing lists property in request'
2103
+ message: `Unable search for cohort. Error: ${errorMessage}`
2386
2104
  };
2387
2105
  }
2106
+ }
2107
+ /**
2108
+ * Export all cohorts as JSON
2109
+ */ async exportCohorts() {
2110
+ return await this.getAllCohorts();
2111
+ }
2112
+ /**
2113
+ * Import cohorts from JSON data
2114
+ */ async importCohorts(cohorts, overwrite = false) {
2388
2115
  try {
2389
- const db = await this.getDb();
2390
- const tx = db.transaction(STORE_NAME, 'readwrite');
2391
- tx.objectStore(STORE_NAME).put({
2392
- id: id,
2393
- ...data
2394
- });
2395
- await tx.done;
2396
- return {
2397
- status: 200,
2398
- message: 'success'
2399
- };
2116
+ if (overwrite) {
2117
+ await this.deleteAllCohorts();
2118
+ }
2119
+ await this.saveCohorts(cohorts);
2400
2120
  } catch (error) {
2401
2121
  const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred';
2402
2122
  return {
2403
2123
  isError: true,
2404
2124
  status: 500,
2405
- message: `unable to clear library. Error: ${errorMessage}`
2125
+ message: `Failed to import cohorts: ${errorMessage}`
2406
2126
  };
2407
2127
  }
2408
2128
  }
2409
- constructor(){
2410
- this.setAllLists = async (data)=>{
2411
- const timestamp = getTimestamp();
2412
- const allLists = data.reduce((acc, x)=>{
2413
- if (!isJSONObject(x)) return acc;
2414
- const id = nanoid(10);
2415
- acc[id] = {
2416
- ...x,
2417
- version: 0,
2418
- created_time: timestamp,
2419
- updated_time: timestamp,
2420
- creator: '{{subject_id}}',
2421
- authz: {
2422
- version: 0,
2423
- authz: [
2424
- `/users/{{subject_id}}/user-library/lists/${id}`
2425
- ]
2426
- }
2427
- };
2428
- return acc;
2429
- }, {});
2430
- try {
2431
- const db = await this.getDb();
2432
- const tx = db.transaction(STORE_NAME, 'readwrite');
2433
- for (const [id, list] of Object.entries(allLists)){
2434
- tx.objectStore(STORE_NAME).put({
2435
- id,
2436
- ...list
2437
- });
2438
- }
2439
- await tx.done;
2440
- return {
2441
- status: 200,
2442
- message: 'success'
2443
- };
2444
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
2445
- } catch (_error) {
2446
- return {
2447
- isError: true,
2448
- status: 500,
2449
- message: 'unable to add lists'
2450
- };
2451
- }
2452
- };
2129
+ /**
2130
+ * Close the database connection
2131
+ */ async close() {
2132
+ try {
2133
+ const db = await this.getDb();
2134
+ db.close();
2135
+ } catch (error) {
2136
+ console.error('Failed to close database:', error);
2137
+ }
2453
2138
  }
2454
2139
  }
2455
2140
 
2456
- const fetchFromDataLibraryAPI = async (url, method = HttpMethod.GET, body = undefined)=>{
2457
- try {
2458
- return {
2459
- data: await fetchJSONDataFromURL(url, true, method, body)
2460
- };
2461
- } catch (error) {
2462
- if (error instanceof HTTPError) {
2463
- return {
2464
- error: {
2465
- status: error.status,
2466
- message: HTTPErrorMessages[error.status] || error.responseData?.message || 'No HTTP Error Message'
2467
- }
2468
- };
2469
- } else {
2470
- return {
2471
- error: {
2472
- status: 500,
2473
- message: 'Unknown Error'
2474
- }
2475
- };
2476
- }
2477
- }
2141
+ const isOperationWithField = (operation)=>{
2142
+ return operation?.field !== undefined;
2478
2143
  };
2479
- const responseFromMutation = (responseReceived)=>{
2480
- if (responseReceived.error) {
2481
- return {
2482
- isError: true,
2483
- ...responseReceived.error
2484
- };
2144
+ const isOperatorWithFieldAndArrayOfOperands = (operation)=>{
2145
+ if (typeof operation === 'object' && operation !== null && 'operands' in operation && Array.isArray(operation.operands) && 'field' in operation && typeof operation.field === 'string' // Assuming `field` should be a string
2146
+ ) {
2147
+ const { operator } = operation.operator;
2148
+ return operator === 'in' || operator === 'exclude' || operator === 'excludeifany';
2485
2149
  }
2486
- return {
2487
- lists: responseReceived.data,
2488
- message: 'success',
2489
- status: 200
2490
- };
2150
+ return false;
2491
2151
  };
2492
- class APIStorageService {
2493
- constructor(apiBaseUrl = `${GEN3_DATA_LIBRARY_API}`){
2494
- this.pendingRequests = new Map();
2495
- this.apiBaseUrl = apiBaseUrl;
2496
- }
2497
- async dedupedRequest(url, method = HttpMethod.GET, body = undefined) {
2498
- // Create a unique key for this request
2499
- const requestKey = `${method}:${url}:${body ? JSON.stringify(body) : ''}`;
2500
- // If this exact request is already in progress, return the pending promise
2501
- if (this.pendingRequests.has(requestKey)) {
2502
- return this.pendingRequests.get(requestKey);
2503
- }
2504
- // Otherwise, make the request and store the promise
2505
- const requestPromise = fetchFromDataLibraryAPI(url, method, body);
2506
- this.pendingRequests.set(requestKey, requestPromise);
2507
- try {
2508
- // Wait for the request to complete
2509
- const result = await requestPromise;
2510
- return result;
2511
- } finally{
2512
- // Remove the request from pending requests after it completes
2513
- this.pendingRequests.delete(requestKey);
2514
- }
2515
- }
2516
- async getLists() {
2517
- const { data, error } = await this.dedupedRequest(`${this.apiBaseUrl}`);
2518
- if (error) {
2519
- return {
2520
- isError: true,
2521
- ...error
2522
- };
2523
- }
2524
- if (data && isDataLibraryAPIResponse(data)) {
2525
- const datalists = BuildLists(data);
2152
+ const extractFilterValue = (op)=>{
2153
+ const valueExtractorHandler = new ValueExtractorHandler();
2154
+ return handleOperation(valueExtractorHandler, op);
2155
+ };
2156
+ const extractEnumFilterValue = (op)=>{
2157
+ const enumValueExtractorHandler = new EnumValueExtractorHandler();
2158
+ const results = handleOperation(enumValueExtractorHandler, op);
2159
+ return results ?? [];
2160
+ };
2161
+ const assertNever = (x)=>{
2162
+ throw Error(`Exhaustive comparison did not handle: ${x}`);
2163
+ };
2164
+ const handleOperation = (handler, op)=>{
2165
+ switch(op.operator){
2166
+ case '=':
2167
+ return handler.handleEquals(op);
2168
+ case '!=':
2169
+ return handler.handleNotEquals(op);
2170
+ case '<':
2171
+ return handler.handleLessThan(op);
2172
+ case '<=':
2173
+ return handler.handleLessThanOrEquals(op);
2174
+ case '>':
2175
+ return handler.handleGreaterThan(op);
2176
+ case '>=':
2177
+ return handler.handleGreaterThanOrEquals(op);
2178
+ case 'and':
2179
+ return handler.handleIntersection(op);
2180
+ case 'or':
2181
+ return handler.handleUnion(op);
2182
+ case 'nested':
2183
+ return handler.handleNestedFilter(op);
2184
+ case 'in':
2185
+ case 'includes':
2186
+ return handler.handleIncludes(op);
2187
+ case 'excludeifany':
2188
+ return handler.handleExcludeIfAny(op);
2189
+ case 'excludes':
2190
+ return handler.handleExcludes(op);
2191
+ case 'exists':
2192
+ return handler.handleExists(op);
2193
+ case 'missing':
2194
+ return handler.handleMissing(op);
2195
+ default:
2196
+ return assertNever(op);
2197
+ }
2198
+ };
2199
+ /**
2200
+ * Return true if a FilterSet's root value is an empty object
2201
+ * @param fs - FilterSet to test
2202
+ */ const isFilterEmpty = (fs)=>isEqual({}, fs);
2203
+ /**
2204
+ * Type guard to check if an object is a GQLIntersection
2205
+ * @param value - The value to check
2206
+ * @returns True if the value is a GQLIntersection
2207
+ */ const isGQLIntersection = (value)=>{
2208
+ return typeof value === 'object' && value !== null && 'and' in value && Array.isArray(value.and);
2209
+ };
2210
+ /**
2211
+ * Type guard to check if an object is a GQLIntersection
2212
+ * @param value - The value to check
2213
+ * @returns True if the value is a GQLIntersection
2214
+ */ const isGQLUnion = (value)=>{
2215
+ return typeof value === 'object' && value !== null && 'or' in value && Array.isArray(value.or);
2216
+ };
2217
+ class ToGqlHandler {
2218
+ constructor(){
2219
+ this.handleEquals = (op)=>({
2220
+ '=': {
2221
+ [op.field]: op.operand
2222
+ }
2223
+ });
2224
+ this.handleNotEquals = (op)=>({
2225
+ '!=': {
2226
+ [op.field]: op.operand
2227
+ }
2228
+ });
2229
+ this.handleLessThan = (op)=>({
2230
+ '<': {
2231
+ [op.field]: op.operand
2232
+ }
2233
+ });
2234
+ this.handleLessThanOrEquals = (op)=>({
2235
+ '<=': {
2236
+ [op.field]: op.operand
2237
+ }
2238
+ });
2239
+ this.handleGreaterThan = (op)=>({
2240
+ '>': {
2241
+ [op.field]: op.operand
2242
+ }
2243
+ });
2244
+ this.handleGreaterThanOrEquals = (op)=>({
2245
+ '>=': {
2246
+ [op.field]: op.operand
2247
+ }
2248
+ });
2249
+ this.handleIncludes = (op)=>({
2250
+ in: {
2251
+ [op.field]: op.operands
2252
+ }
2253
+ });
2254
+ this.handleExcludes = (op)=>({
2255
+ exclude: {
2256
+ [op.field]: op.operands
2257
+ }
2258
+ });
2259
+ this.handleExcludeIfAny = (op)=>({
2260
+ excludeifany: {
2261
+ [op.field]: op.operands
2262
+ }
2263
+ });
2264
+ this.handleIntersection = (op)=>({
2265
+ and: op.operands.map((x)=>convertFilterToGqlFilter(x))
2266
+ });
2267
+ this.handleUnion = (op)=>({
2268
+ or: op.operands.map((x)=>convertFilterToGqlFilter(x))
2269
+ });
2270
+ this.handleMissing = (op)=>({
2271
+ is: {
2272
+ [op.field]: 'MISSING'
2273
+ }
2274
+ });
2275
+ this.handleExists = (op)=>({
2276
+ not: {
2277
+ [op.field]: op?.operand ?? null
2278
+ }
2279
+ });
2280
+ this.handleNestedFilter = (op)=>{
2281
+ const child = convertFilterToGqlFilter(op.operand);
2526
2282
  return {
2527
- lists: datalists,
2528
- status: 200,
2529
- message: 'success'
2283
+ nested: {
2284
+ path: op.path,
2285
+ ...child
2286
+ }
2530
2287
  };
2531
- }
2532
- return {
2533
- lists: {},
2534
- status: 200,
2535
- message: 'no list returned'
2536
2288
  };
2537
2289
  }
2538
- async addList(list) {
2539
- const response = await fetchFromDataLibraryAPI(`${this.apiBaseUrl}`, HttpMethod.PUT, JSON.stringify({
2540
- lists: [
2541
- list
2542
- ]
2543
- }));
2544
- return responseFromMutation(response);
2290
+ }
2291
+ const convertFilterToGqlFilter = (filter)=>{
2292
+ const handler = new ToGqlHandler();
2293
+ return handleOperation(handler, filter);
2294
+ };
2295
+ const convertFilterSetToGqlFilter = (fs, toplevelOp = 'and')=>{
2296
+ const fsKeys = Object.keys(fs.root);
2297
+ // if no keys return undefined
2298
+ if (fsKeys.length === 0) return {
2299
+ and: []
2300
+ };
2301
+ return toplevelOp === 'and' ? {
2302
+ and: fsKeys.map((key)=>convertFilterToGqlFilter(fs.root[key]))
2303
+ } : {
2304
+ or: fsKeys.map((key)=>convertFilterToGqlFilter(fs.root[key]))
2305
+ };
2306
+ };
2307
+ const handleGqlOperation = (handler, op)=>{
2308
+ const operationKeys = Object.keys(op);
2309
+ if (operationKeys.includes('=')) {
2310
+ return handler.handleEquals(op);
2545
2311
  }
2546
- async updateList(id, list) {
2547
- const response = await fetchFromDataLibraryAPI(`${this.apiBaseUrl}/${id}`, HttpMethod.PUT, JSON.stringify(list));
2548
- return responseFromMutation(response);
2312
+ if (operationKeys.includes('!=')) {
2313
+ return handler.handleNotEquals(op);
2549
2314
  }
2550
- async deleteList(id) {
2551
- const response = await fetchFromDataLibraryAPI(`${this.apiBaseUrl}/${id}`, HttpMethod.DELETE);
2552
- return responseFromMutation(response);
2315
+ if (operationKeys.includes('<')) {
2316
+ return handler.handleLessThan(op);
2553
2317
  }
2554
- async clearLists() {
2555
- const response = await fetchFromDataLibraryAPI(this.apiBaseUrl, HttpMethod.DELETE);
2556
- return responseFromMutation(response);
2318
+ if (operationKeys.includes('<=')) {
2319
+ return handler.handleLessThanOrEquals(op);
2557
2320
  }
2558
- async setAllLists(lists) {
2559
- const response = await fetchFromDataLibraryAPI(this.apiBaseUrl, HttpMethod.POST, JSON.stringify({
2560
- lists: Object.values(lists)
2561
- }));
2562
- return responseFromMutation(response);
2321
+ if (operationKeys.includes('>')) {
2322
+ return handler.handleGreaterThan(op);
2563
2323
  }
2564
- async getList(id) {
2565
- const { data, error } = await fetchFromDataLibraryAPI(`${this.apiBaseUrl}/${id}`);
2566
- if (error) {
2324
+ if (operationKeys.includes('>=')) {
2325
+ return handler.handleGreaterThanOrEquals(op);
2326
+ }
2327
+ if (operationKeys.includes('in')) {
2328
+ return handler.handleIncludes(op);
2329
+ }
2330
+ if (operationKeys.includes('exclude')) {
2331
+ return handler.handleExcludes(op);
2332
+ }
2333
+ if (operationKeys.includes('excludeifany')) {
2334
+ return handler.handleExcludeIfAny(op);
2335
+ }
2336
+ if (operationKeys.includes('and')) {
2337
+ return handler.handleIntersection(op);
2338
+ }
2339
+ if (operationKeys.includes('or')) {
2340
+ return handler.handleUnion(op);
2341
+ }
2342
+ if (operationKeys.includes('nested')) {
2343
+ return handler.handleNestedFilter(op);
2344
+ }
2345
+ if (operationKeys.includes('is')) {
2346
+ return handler.handleExists(op);
2347
+ }
2348
+ if (operationKeys.includes('not')) {
2349
+ return handler.handleMissing(op);
2350
+ }
2351
+ return assertNever(op);
2352
+ };
2353
+ const convertGqlFilterToFilter = (gqlFilter)=>{
2354
+ const handler = new ToOperationHandler();
2355
+ return handleGqlOperation(handler, gqlFilter);
2356
+ };
2357
+ /**
2358
+ * Convert GQL to Filterset
2359
+ * Note assumes all GqlOperators have one field: value
2360
+ */ class ToOperationHandler {
2361
+ constructor(){
2362
+ this.handleEquals = (op)=>{
2363
+ const [field, value] = Object.entries(op['='])[0];
2567
2364
  return {
2568
- isError: true,
2569
- ...error
2365
+ operator: '=',
2366
+ field: field,
2367
+ operand: value
2570
2368
  };
2571
- }
2572
- if (isDataLibraryAPIResponse(data)) {
2573
- const datalists = BuildLists(data);
2369
+ };
2370
+ this.handleNotEquals = (op)=>{
2371
+ const [field, value] = Object.entries(op['!='])[0];
2574
2372
  return {
2575
- lists: datalists,
2576
- status: 200,
2577
- message: 'success'
2373
+ operator: '!=',
2374
+ field: field,
2375
+ operand: value
2578
2376
  };
2579
- }
2580
- return {
2581
- isError: true,
2582
- status: 500,
2583
- message: `Unknown error getting list ${id}`
2584
2377
  };
2585
- }
2586
- }
2587
-
2588
- class CachedAPIService {
2589
- constructor(){
2590
- this.localStorageDataLibrary = new LocalStorageService(); // always update local storage
2591
- this.apiDataLibrary = new APIStorageService();
2592
- }
2593
- async getLists() {
2594
- // do a network request to get the library
2595
- // get the remote list
2596
- const apiResults = await this.apiDataLibrary.getLists();
2597
- if (apiResults.isError) {
2378
+ this.handleLessThan = (op)=>{
2379
+ const [field, value] = Object.entries(op['<'])[0];
2598
2380
  return {
2599
- ...apiResults,
2600
- lists: undefined
2381
+ operator: '<',
2382
+ field: field,
2383
+ operand: value
2601
2384
  };
2602
- }
2603
- const dataLibrary = convertDataLibraryToDataLibraryAPI(apiResults?.lists ?? {});
2604
- await this.localStorageDataLibrary.cacheLists(dataLibrary);
2605
- return apiResults;
2606
- }
2607
- async getList(id) {
2608
- return await this.localStorageDataLibrary.getList(id);
2609
- }
2610
- async getCachedLists(id) {
2611
- return await this.localStorageDataLibrary.getList(id);
2612
- }
2613
- async setAllLists(lists) {
2614
- const apiResults = await this.apiDataLibrary.setAllLists(lists);
2615
- if (apiResults.isError) {
2385
+ };
2386
+ this.handleLessThanOrEquals = (op)=>{
2387
+ const [field, value] = Object.entries(op['<='])[0];
2616
2388
  return {
2617
- ...apiResults,
2618
- lists: undefined
2389
+ operator: '<=',
2390
+ field: field,
2391
+ operand: value
2619
2392
  };
2620
- }
2621
- const dataLibrary = convertDataLibraryToDataLibraryAPI(apiResults?.lists ?? {});
2622
- await this.localStorageDataLibrary.cacheLists(dataLibrary);
2623
- return apiResults;
2624
- }
2625
- async addList(list) {
2626
- // update the API list
2627
- const apiResults = await this.apiDataLibrary.addList(list);
2628
- if (apiResults.isError) {
2393
+ };
2394
+ this.handleGreaterThan = (op)=>{
2395
+ const [field, value] = Object.entries(op['>'])[0];
2629
2396
  return {
2630
- ...apiResults,
2631
- lists: undefined
2397
+ operator: '>',
2398
+ field: field,
2399
+ operand: value
2632
2400
  };
2633
- }
2634
- const cacheResults = await this.localStorageDataLibrary.addList(list);
2635
- return {
2636
- ...cacheResults,
2637
- lists: undefined
2638
2401
  };
2639
- }
2640
- async updateList(id, list) {
2641
- const apiResults = await this.apiDataLibrary.updateList(id, list);
2642
- if (apiResults.isError) {
2402
+ this.handleGreaterThanOrEquals = (op)=>{
2403
+ const [field, value] = Object.entries(op['>='])[0];
2643
2404
  return {
2644
- ...apiResults,
2645
- lists: undefined
2405
+ operator: '>=',
2406
+ field: field,
2407
+ operand: value
2646
2408
  };
2647
- }
2648
- return await this.localStorageDataLibrary.cacheList(id, apiResults.lists ?? {});
2649
- }
2650
- async deleteList(id) {
2651
- const apiResults = await this.apiDataLibrary.deleteList(id);
2652
- if (apiResults.isError) {
2409
+ };
2410
+ this.handleIncludes = (op)=>{
2411
+ const [field, value] = Object.entries(op.in)[0];
2653
2412
  return {
2654
- ...apiResults,
2655
- lists: undefined
2413
+ operator: 'in',
2414
+ field: field,
2415
+ operands: value
2656
2416
  };
2657
- }
2658
- return await this.localStorageDataLibrary.deleteList(id);
2659
- }
2660
- async clearLists() {
2661
- const apiResults = await this.apiDataLibrary.clearLists();
2662
- if (apiResults.isError) {
2417
+ };
2418
+ this.handleExcludes = (op)=>{
2419
+ const [field, value] = Object.entries(op.exclude)[0];
2663
2420
  return {
2664
- ...apiResults,
2665
- lists: undefined
2421
+ operator: 'excludes',
2422
+ field: field,
2423
+ operands: value
2666
2424
  };
2667
- }
2668
- return await this.localStorageDataLibrary.clearLists();
2425
+ };
2426
+ this.handleExcludeIfAny = (op)=>{
2427
+ const [field, value] = Object.entries(op.excludeifany)[0];
2428
+ return {
2429
+ operator: 'excludeifany',
2430
+ field: field,
2431
+ operands: value
2432
+ };
2433
+ };
2434
+ this.handleIntersection = (op)=>({
2435
+ operator: 'and',
2436
+ operands: op.and.map(convertGqlFilterToFilter)
2437
+ });
2438
+ this.handleUnion = (op)=>({
2439
+ operator: 'or',
2440
+ operands: op.or.map(convertGqlFilterToFilter)
2441
+ });
2442
+ this.handleExists = (op)=>{
2443
+ const [field, value] = Object.entries(op.not)[0];
2444
+ return {
2445
+ operator: 'exists',
2446
+ field: field,
2447
+ operand: value
2448
+ };
2449
+ };
2450
+ this.handleMissing = (op)=>{
2451
+ const field = Object.keys(op.is)[0];
2452
+ return {
2453
+ operator: 'missing',
2454
+ field: field
2455
+ };
2456
+ };
2457
+ this.handleNestedFilter = (op)=>({
2458
+ operator: 'nested',
2459
+ path: op.nested.path,
2460
+ operand: convertGqlFilterToFilter(op.nested)
2461
+ });
2669
2462
  }
2670
2463
  }
2671
-
2672
- class DataLibraryStorageService {
2673
- constructor(mode = DataLibraryStoreMode.ApiOnly){
2674
- if (mode === DataLibraryStoreMode.ApiOnly) {
2675
- this.storageService = new APIStorageService();
2676
- } else if (mode === DataLibraryStoreMode.ApiAndLocal) this.storageService = new CachedAPIService();
2677
- else this.storageService = new LocalStorageService();
2678
- }
2679
- async setStorageMode(mode) {
2680
- if (mode === DataLibraryStoreMode.ApiOnly) {
2681
- this.storageService = new APIStorageService();
2682
- } else if (mode === DataLibraryStoreMode.ApiAndLocal) this.storageService = new CachedAPIService();
2683
- else this.storageService = new LocalStorageService();
2684
- }
2685
- // private async syncApiAndLocal() {
2686
- // const { lists: localData, isError: localError } =
2687
- // await this.localStorageDataLibrary.getLists();
2688
- // const { lists: apiData, isError: apiError } =
2689
- // await this.apiDataLibrary.getLists();
2690
- //
2691
- // if (localError || apiError) {
2692
- // return;
2693
- // }
2694
- //
2695
- // const mergedData: Record<string, Datalist> = { ...localData };
2696
- //
2697
- // // First, update any existing items with newer versions from API
2698
- // Object.values(apiData?.lists ?? {}).forEach((apiList) => {
2699
- // const id = apiList.id as keyof DataLibrary;
2700
- // const localList = localData?.[id];
2701
- // if (
2702
- // !localList ||
2703
- // storage Date(apiList.updatedTime) > storage Date(localList.updatedTime)
2704
- // ) {
2705
- // mergedData[id] = apiList;
2706
- // }
2707
- // });
2708
- //
2709
- // // Push local-only changes to API
2710
- // const syncPromises: Promise<any>[] = [];
2711
- //
2712
- // for (const [id, localList] of Object.entries(localData?.lists ?? {})) {
2713
- // if (!apiData?.[id]) {
2714
- // // This list exists locally but not in API, so push it to API
2715
- // syncPromises.push(this.apiDataLibrary.addList(localList));
2716
- // } else if (
2717
- // storage Date(localList.updatedTime) > storage Date(apiData[id].updatedTime)
2718
- // ) {
2719
- // // Local list is newer than API, so update API
2720
- // syncPromises.push(this.apiDataLibrary.updateList(localList));
2721
- // }
2722
- // }
2723
- //
2724
- // // Wait for all API operations to complete
2725
- // await Promise.all(syncPromises);
2726
- // await this.localStorageDataLibrary.cacheLists({ lists: mergedData });
2727
- // }
2728
- async getLists() {
2729
- return await this.storageService.getLists();
2730
- }
2731
- async getList(id) {
2732
- return await this.storageService.getList(id);
2733
- }
2734
- async getCachedLists(id) {
2735
- return await this.storageService.getList(id);
2464
+ /**
2465
+ * Extract the operand values, if operands themselves have values, otherwise undefined.
2466
+ */ class ValueExtractorHandler {
2467
+ constructor(){
2468
+ this.handleEquals = (op)=>op.operand;
2469
+ this.handleNotEquals = (op)=>op.operand;
2470
+ this.handleIncludes = (op)=>op.operands;
2471
+ this.handleExcludes = (op)=>op.operands;
2472
+ this.handleExcludeIfAny = (op)=>op.operands;
2473
+ this.handleGreaterThanOrEquals = (op)=>op.operand;
2474
+ this.handleGreaterThan = (op)=>op.operand;
2475
+ this.handleLessThan = (op)=>op.operand;
2476
+ this.handleLessThanOrEquals = (op)=>op.operand;
2477
+ this.handleIntersection = (_arg)=>undefined;
2478
+ this.handleUnion = (_)=>undefined;
2479
+ this.handleNestedFilter = (_)=>undefined;
2480
+ this.handleExists = (_)=>undefined;
2481
+ this.handleMissing = (_)=>undefined;
2736
2482
  }
2737
- async setAllLists(lists) {
2738
- return await this.storageService.setAllLists(lists ?? {});
2483
+ }
2484
+ /**
2485
+ * Extract the operand values, if operands themselves have values, otherwise undefined.
2486
+ */ class EnumValueExtractorHandler {
2487
+ constructor(){
2488
+ this.handleEquals = (_)=>undefined;
2489
+ this.handleNotEquals = (_)=>undefined;
2490
+ this.handleIncludes = (op)=>op.operands;
2491
+ this.handleExcludes = (op)=>op.operands;
2492
+ this.handleExcludeIfAny = (op)=>op.operands;
2493
+ this.handleGreaterThanOrEquals = (_)=>undefined;
2494
+ this.handleGreaterThan = (_)=>undefined;
2495
+ this.handleLessThan = (_)=>undefined;
2496
+ this.handleLessThanOrEquals = (_)=>undefined;
2497
+ this.handleIntersection = (_)=>undefined;
2498
+ this.handleUnion = (_)=>undefined;
2499
+ this.handleNestedFilter = (op)=>{
2500
+ return extractEnumFilterValue(op.operand);
2501
+ };
2502
+ this.handleExists = (_)=>undefined;
2503
+ this.handleMissing = (_)=>undefined;
2739
2504
  }
2740
- async addList(list) {
2741
- return await this.storageService.addList(list);
2505
+ }
2506
+ const appendFilterToOperation = (filter, addition)=>{
2507
+ if (filter === undefined && addition === undefined) return {
2508
+ operator: 'and',
2509
+ operands: []
2510
+ };
2511
+ if (addition === undefined && filter) return filter;
2512
+ if (filter === undefined && addition) return addition;
2513
+ return {
2514
+ ...filter,
2515
+ operands: [
2516
+ ...filter?.operands || [],
2517
+ addition
2518
+ ]
2519
+ };
2520
+ };
2521
+ const filterSetToOperation = (fs)=>{
2522
+ if (!fs) return undefined;
2523
+ switch(fs.mode){
2524
+ case 'and':
2525
+ return Object.keys(fs.root).length == 0 ? undefined : {
2526
+ operator: fs.mode,
2527
+ operands: Object.keys(fs.root).map((k)=>{
2528
+ return fs.root[k];
2529
+ })
2530
+ };
2742
2531
  }
2743
- async updateList(id, list) {
2744
- return await this.storageService.updateList(id, list);
2532
+ return undefined;
2533
+ };
2534
+
2535
+ const isFilterSet = (input)=>{
2536
+ if (typeof input !== 'object' || input === null) {
2537
+ return false;
2745
2538
  }
2746
- async deleteList(id) {
2747
- return await this.storageService.deleteList(id);
2539
+ const { root, mode } = input;
2540
+ if (typeof root !== 'object' || root === null) {
2541
+ return false;
2748
2542
  }
2749
- async clearLists() {
2750
- return await this.storageService.clearLists();
2543
+ if (![
2544
+ 'and',
2545
+ 'or'
2546
+ ].includes(mode)) {
2547
+ return false;
2751
2548
  }
2752
- }
2549
+ return true;
2550
+ };
2551
+ const isUnion = (value)=>{
2552
+ return typeof value === 'object' && value !== null && value.operator === 'or' && Array.isArray(value.operands);
2553
+ };
2554
+ const isIntersection = (value)=>{
2555
+ return typeof value === 'object' && value !== null && value.operator === 'and' && Array.isArray(value.operands);
2556
+ };
2557
+ const isOperandsType = (operation)=>{
2558
+ return operation?.operands !== undefined;
2559
+ };
2560
+ const isIndexedFilterSetEmpty = (filters)=>Object.values(filters).every((filterSet)=>Object.keys(filterSet).length === 0);
2561
+ const EmptyFilterSet = {
2562
+ mode: 'and',
2563
+ root: {}
2564
+ };
2753
2565
 
2754
- const EMPTY_LIST = {
2755
- items: {},
2756
- version: 0,
2757
- created_time: 'not_set',
2758
- updated_time: 'not_set',
2759
- name: '',
2760
- id: '',
2761
- authz: {
2762
- version: -1,
2763
- authz: []
2566
+ const FieldNameOverrides = {};
2567
+ const COMMON_PREPOSITIONS = [
2568
+ 'a',
2569
+ 'an',
2570
+ 'and',
2571
+ 'at',
2572
+ 'but',
2573
+ 'by',
2574
+ 'for',
2575
+ 'in',
2576
+ 'is',
2577
+ 'nor',
2578
+ 'of',
2579
+ 'on',
2580
+ 'or',
2581
+ 'out',
2582
+ 'so',
2583
+ 'the',
2584
+ 'to',
2585
+ 'up',
2586
+ 'yet'
2587
+ ];
2588
+ const capitalize = (s)=>s.length > 0 ? s[0].toUpperCase() + s.slice(1) : '';
2589
+ const trimFirstFieldNameToTitle = (fieldName, trim = false)=>{
2590
+ if (trim) {
2591
+ const source = fieldName.slice(fieldName.indexOf('.') + 1);
2592
+ return fieldNameToTitle(source ? source : fieldName, 0);
2764
2593
  }
2594
+ return fieldNameToTitle(fieldName);
2765
2595
  };
2766
- const DEFAULT_LIST_NAME = 'List';
2767
- const useDataLibrary = (options = {
2768
- storageMode: DataLibraryStoreMode.ApiOnly
2769
- })=>{
2770
- // State management
2771
- const [isLoggedIn, setIsLoggedIn] = useState(false);
2772
- const [isLoading, setIsLoading] = useState(false);
2773
- const [isUpdating, setIsUpdating] = useState(null);
2774
- const [error, setError] = useState(null);
2775
- const [lists, setLists] = useState({});
2776
- // Refs
2777
- const initialLoadRef = useRef(false);
2778
- // Services
2779
- const dataLibraryStoreAPI = useRef(new DataLibraryStorageService(options.storageMode)).current;
2780
- const handleErrorOrSetLists = useCallback(async (error)=>{
2781
- if (error.isError) {
2782
- setError(error);
2783
- } else {
2784
- const getListResults = await dataLibraryStoreAPI.getLists();
2785
- if (getListResults.isError) {
2786
- setError(getListResults);
2787
- } else {
2788
- setLists(getListResults.lists ?? {});
2789
- setError(null);
2790
- }
2791
- }
2792
- }, [
2793
- dataLibraryStoreAPI
2794
- ]);
2795
- const generateUniqueName = useCallback((baseName = DEFAULT_LIST_NAME)=>{
2796
- let uniqueName = baseName;
2797
- let counter = 1;
2798
- const existingNames = Object.values(lists).map((x)=>x.name);
2799
- while(existingNames.includes(uniqueName)){
2800
- uniqueName = `${baseName} ${counter}`;
2801
- counter++;
2802
- }
2803
- return uniqueName;
2804
- }, [
2805
- lists
2806
- ]);
2807
- const performLibraryOperation = useCallback(async (operation, updateId)=>{
2808
- setError(null);
2809
- if (updateId) {
2810
- setIsUpdating(updateId);
2811
- } else setIsLoading(true);
2812
- const operationResults = await operation();
2813
- await handleErrorOrSetLists(operationResults);
2814
- if (updateId) setIsUpdating(null);
2815
- else setIsLoading(false);
2816
- return operationResults;
2817
- }, [
2818
- handleErrorOrSetLists
2819
- ]);
2820
- // Lifecycle effects
2821
- useEffect(()=>{
2822
- const initializeData = async ()=>{
2823
- if (!initialLoadRef.current) {
2824
- setError(null);
2825
- setIsLoading(true);
2826
- const results = await dataLibraryStoreAPI.getLists(); // get the initial lists
2827
- if (results.isError) setError(results);
2828
- else setLists(results.lists ?? {});
2829
- setIsLoading(false);
2830
- initialLoadRef.current = true;
2831
- }
2832
- };
2833
- initializeData();
2834
- }, [
2835
- dataLibraryStoreAPI
2836
- ]);
2837
- useEffect(()=>{
2838
- const handleLogin = async ()=>{
2839
- // setIsLoading(true);
2840
- // await dataLibraryStoreAPI.setUseAPI(options.requiresAPI && isLoggedIn);
2841
- // setIsLoading(false);
2842
- };
2843
- handleLogin();
2844
- }, [
2845
- dataLibraryStoreAPI,
2846
- isLoggedIn
2847
- ]);
2848
- // CRUD operations
2849
- const addListToDataLibrary = useCallback(async (items, name)=>{
2850
- const apiItems = convertDatasetOrCohortToLibraryListItemsAPI(items);
2851
- const namedItems = {
2852
- items: apiItems,
2853
- name: generateUniqueName(name ?? DEFAULT_LIST_NAME)
2854
- };
2855
- return await performLibraryOperation(()=>dataLibraryStoreAPI.addList(namedItems));
2856
- }, [
2857
- dataLibraryStoreAPI,
2858
- generateUniqueName,
2859
- performLibraryOperation
2860
- ]);
2861
- const updateListInDataLibrary = useCallback(async (payload)=>{
2862
- const flattened = flattenDataList(payload);
2863
- return await performLibraryOperation(()=>dataLibraryStoreAPI.updateList(payload.id, {
2864
- name: payload.name,
2865
- items: flattened.items
2866
- }), payload.id);
2867
- }, [
2868
- dataLibraryStoreAPI,
2869
- performLibraryOperation
2870
- ]);
2871
- const deleteListFromDataLibrary = useCallback(async (id)=>{
2872
- return await performLibraryOperation(()=>dataLibraryStoreAPI.deleteList(id));
2873
- }, [
2874
- dataLibraryStoreAPI,
2875
- performLibraryOperation
2876
- ]);
2877
- const clearLibrary = useCallback(async ()=>{
2878
- return await performLibraryOperation(()=>dataLibraryStoreAPI.clearLists());
2879
- }, [
2880
- dataLibraryStoreAPI,
2881
- performLibraryOperation
2882
- ]);
2883
- const setAllListsInDataLibrary = useCallback(async (data)=>{
2884
- const flattenedLists = data.map((x)=>flattenDataList(x));
2885
- return await performLibraryOperation(()=>dataLibraryStoreAPI.setAllLists(flattenedLists));
2886
- }, [
2887
- dataLibraryStoreAPI,
2888
- performLibraryOperation
2889
- ]);
2890
- const getDatalist = useCallback((id)=>{
2891
- if (id in lists) return lists[id];
2892
- setError({
2893
- isError: true,
2894
- status: 404,
2895
- message: `List not found. Returning empty list.`
2896
- });
2897
- return EMPTY_LIST;
2898
- }, [
2899
- lists
2900
- ]);
2901
- const setLoginState = useCallback((loggedIn)=>setIsLoggedIn(loggedIn), []);
2902
- const results = useDeepCompareMemo(()=>({
2903
- dataLibrary: lists,
2904
- isLoading,
2905
- isUpdating,
2906
- error,
2907
- addListToDataLibrary,
2908
- updateListInDataLibrary,
2909
- deleteListFromDataLibrary,
2910
- clearLibrary,
2911
- setAllListsInDataLibrary,
2912
- setLoginState,
2913
- getDatalist
2914
- }), [
2915
- addListToDataLibrary,
2916
- clearLibrary,
2917
- deleteListFromDataLibrary,
2918
- error,
2919
- getDatalist,
2920
- isLoading,
2921
- isUpdating,
2922
- lists,
2923
- setAllListsInDataLibrary,
2924
- setLoginState,
2925
- updateListInDataLibrary
2926
- ]);
2927
- return results;
2928
- };
2929
-
2930
- const isOperationWithField = (operation)=>{
2931
- return operation?.field !== undefined;
2932
- };
2933
- const isOperatorWithFieldAndArrayOfOperands = (operation)=>{
2934
- if (typeof operation === 'object' && operation !== null && 'operands' in operation && Array.isArray(operation.operands) && 'field' in operation && typeof operation.field === 'string' // Assuming `field` should be a string
2935
- ) {
2936
- const { operator } = operation.operator;
2937
- return operator === 'in' || operator === 'exclude' || operator === 'excludeifany';
2596
+ /**
2597
+ * Converts a filter name to a title,
2598
+ * For example files.input.experimental_strategy will get converted to Experimental Strategy
2599
+ * if sections == 2 then the output would be Input Experimental Strategy
2600
+ * @param fieldName input filter expected to be: string.firstpart_secondpart
2601
+ * @param sections number of "sections" string.string.string to got back from the end of the field
2602
+ */ const fieldNameToTitle = (fieldName, sections = 1)=>{
2603
+ if (fieldName in FieldNameOverrides) {
2604
+ return FieldNameOverrides[fieldName];
2938
2605
  }
2939
- return false;
2606
+ if (fieldName === undefined) return 'No Title';
2607
+ return fieldName.split('.').slice(-sections).map((s)=>s.split('_')).flat().map((word)=>COMMON_PREPOSITIONS.includes(word) ? word : capitalize(word)).join(' ');
2940
2608
  };
2941
- const extractFilterValue = (op)=>{
2942
- const valueExtractorHandler = new ValueExtractorHandler();
2943
- return handleOperation(valueExtractorHandler, op);
2609
+ /**
2610
+ * Extracts the index name from the field name
2611
+ * @param fieldName
2612
+ */ const extractIndexFromFullFieldName = (fieldName)=>fieldName.split('.')[0];
2613
+ /**
2614
+ * prepend the index name to the field name
2615
+ */ const prependIndexToFieldName = (fieldName, index)=>`${index}.${fieldName}`;
2616
+ /**
2617
+ * extract the field name from the index.field name
2618
+ */ const extractFieldNameFromFullFieldName = (fieldName)=>fieldName.split('.').slice(1).join('.');
2619
+ /**
2620
+ * extract the field name and the index from the index.field name returning as a tuple
2621
+ */ const extractIndexAndFieldNameFromFullFieldName = (fieldName)=>{
2622
+ const [index, ...rest] = fieldName.split('.');
2623
+ return [
2624
+ index,
2625
+ rest.join('.')
2626
+ ];
2944
2627
  };
2945
- const extractEnumFilterValue = (op)=>{
2946
- const enumValueExtractorHandler = new EnumValueExtractorHandler();
2947
- const results = handleOperation(enumValueExtractorHandler, op);
2948
- return results ?? [];
2628
+
2629
+ const { selectAll: selectAllCohorts, selectTotal: selectTotalCohorts, selectById: selectCohortById, selectIds: selectCohortIds } = cohortsAdapter.getSelectors((state)=>state.cohorts.cohortManager);
2630
+ /**
2631
+ * Internally used selector for the exported selectora
2632
+ * @param state
2633
+ */ const getCurrentCohortFromCoreState = (state)=>{
2634
+ return state.cohorts.cohortManager.currentCohortId;
2949
2635
  };
2950
- const assertNever = (x)=>{
2951
- throw Error(`Exhaustive comparison did not handle: ${x}`);
2636
+ const selectCohortFilters = (state)=>{
2637
+ const currentCohortId = getCurrentCohortFromCoreState(state);
2638
+ return state.cohorts.cohortManager.entities[currentCohortId]?.filters;
2952
2639
  };
2953
- const handleOperation = (handler, op)=>{
2954
- switch(op.operator){
2955
- case '=':
2956
- return handler.handleEquals(op);
2957
- case '!=':
2958
- return handler.handleNotEquals(op);
2959
- case '<':
2960
- return handler.handleLessThan(op);
2961
- case '<=':
2962
- return handler.handleLessThanOrEquals(op);
2963
- case '>':
2964
- return handler.handleGreaterThan(op);
2965
- case '>=':
2966
- return handler.handleGreaterThanOrEquals(op);
2967
- case 'and':
2968
- return handler.handleIntersection(op);
2969
- case 'or':
2970
- return handler.handleUnion(op);
2971
- case 'nested':
2972
- return handler.handleNestedFilter(op);
2973
- case 'in':
2974
- case 'includes':
2975
- return handler.handleIncludes(op);
2976
- case 'excludeifany':
2977
- return handler.handleExcludeIfAny(op);
2978
- case 'excludes':
2979
- return handler.handleExcludes(op);
2980
- case 'exists':
2981
- return handler.handleExists(op);
2982
- case 'missing':
2983
- return handler.handleMissing(op);
2984
- default:
2985
- return assertNever(op);
2986
- }
2640
+ const selectCurrentCohortFilters = (state)=>{
2641
+ const currentCohortId = getCurrentCohortFromCoreState(state);
2642
+ return state.cohorts.cohortManager.entities[currentCohortId]?.filters;
2987
2643
  };
2988
- /**
2989
- * Return true if a FilterSet's root value is an empty object
2990
- * @param fs - FilterSet to test
2991
- */ const isFilterEmpty = (fs)=>isEqual({}, fs);
2992
- /**
2993
- * Type guard to check if an object is a GQLIntersection
2994
- * @param value - The value to check
2995
- * @returns True if the value is a GQLIntersection
2996
- */ const isGQLIntersection = (value)=>{
2997
- return typeof value === 'object' && value !== null && 'and' in value && Array.isArray(value.and);
2644
+ const selectCurrentCohortId = (state)=>{
2645
+ return state.cohorts.cohortManager.currentCohortId;
2998
2646
  };
2647
+ const selectCurrentCohort = (state)=>cohortSelectors.selectById(state, getCurrentCohortFromCoreState(state));
2648
+ const selectCurrentCohortName = (state)=>cohortSelectors.selectById(state, getCurrentCohortFromCoreState(state)).name;
2999
2649
  /**
3000
- * Type guard to check if an object is a GQLIntersection
3001
- * @param value - The value to check
3002
- * @returns True if the value is a GQLIntersection
3003
- */ const isGQLUnion = (value)=>{
3004
- return typeof value === 'object' && value !== null && 'or' in value && Array.isArray(value.or);
3005
- };
3006
- class ToGqlHandler {
3007
- constructor(){
3008
- this.handleEquals = (op)=>({
3009
- '=': {
3010
- [op.field]: op.operand
3011
- }
3012
- });
3013
- this.handleNotEquals = (op)=>({
3014
- '!=': {
3015
- [op.field]: op.operand
3016
- }
3017
- });
3018
- this.handleLessThan = (op)=>({
3019
- '<': {
3020
- [op.field]: op.operand
3021
- }
3022
- });
3023
- this.handleLessThanOrEquals = (op)=>({
3024
- '<=': {
3025
- [op.field]: op.operand
3026
- }
3027
- });
3028
- this.handleGreaterThan = (op)=>({
3029
- '>': {
3030
- [op.field]: op.operand
3031
- }
3032
- });
3033
- this.handleGreaterThanOrEquals = (op)=>({
3034
- '>=': {
3035
- [op.field]: op.operand
3036
- }
3037
- });
3038
- this.handleIncludes = (op)=>({
3039
- in: {
3040
- [op.field]: op.operands
3041
- }
3042
- });
3043
- this.handleExcludes = (op)=>({
3044
- exclude: {
3045
- [op.field]: op.operands
3046
- }
3047
- });
3048
- this.handleExcludeIfAny = (op)=>({
3049
- excludeifany: {
3050
- [op.field]: op.operands
3051
- }
3052
- });
3053
- this.handleIntersection = (op)=>({
3054
- and: op.operands.map((x)=>convertFilterToGqlFilter(x))
3055
- });
3056
- this.handleUnion = (op)=>({
3057
- or: op.operands.map((x)=>convertFilterToGqlFilter(x))
3058
- });
3059
- this.handleMissing = (op)=>({
3060
- is: {
3061
- [op.field]: 'MISSING'
3062
- }
3063
- });
3064
- this.handleExists = (op)=>({
3065
- not: {
3066
- [op.field]: op?.operand ?? null
3067
- }
3068
- });
3069
- this.handleNestedFilter = (op)=>{
3070
- const child = convertFilterToGqlFilter(op.operand);
3071
- return {
3072
- nested: {
3073
- path: op.path,
3074
- ...child
3075
- }
3076
- };
3077
- };
3078
- }
3079
- }
3080
- const convertFilterToGqlFilter = (filter)=>{
3081
- const handler = new ToGqlHandler();
3082
- return handleOperation(handler, filter);
2650
+ * Select a filter by its name from the current cohort. If the filter is not found
2651
+ * returns undefined.
2652
+ * @param state - Core
2653
+ * @param index which cohort index to select from
2654
+ * @param name name of the filter to select
2655
+ */ const selectIndexedFilterByName = (state, index, name)=>{
2656
+ return cohortSelectors.selectById(state, getCurrentCohortFromCoreState(state)).filters[index]?.root[name];
3083
2657
  };
3084
- const convertFilterSetToGqlFilter = (fs, toplevelOp = 'and')=>{
3085
- const fsKeys = Object.keys(fs.root);
3086
- // if no keys return undefined
3087
- if (fsKeys.length === 0) return {
3088
- and: []
3089
- };
3090
- return toplevelOp === 'and' ? {
3091
- and: fsKeys.map((key)=>convertFilterToGqlFilter(fs.root[key]))
3092
- } : {
3093
- or: fsKeys.map((key)=>convertFilterToGqlFilter(fs.root[key]))
3094
- };
2658
+ /**
2659
+ * Returns all the cohorts in the state
2660
+ * @param state - the CoreState
2661
+ *
2662
+ * @category Cohort
2663
+ * @category Selectors
2664
+ */ const selectAvailableCohorts = (state)=>cohortSelectors.selectAll(state);
2665
+ /**
2666
+ * Returns if the current cohort is modified
2667
+ * @param state - the CoreState
2668
+ * @category Cohort
2669
+ * @category Selectors
2670
+ * @hidden
2671
+ */ const selectCurrentCohortModified = (state)=>{
2672
+ const cohort = cohortSelectors.selectById(state, getCurrentCohortFromCoreState(state));
2673
+ return cohort?.modified;
3095
2674
  };
3096
2675
  /**
3097
- * Extract the operand values, if operands themselves have values, otherwise undefined.
3098
- */ class ValueExtractorHandler {
3099
- constructor(){
3100
- this.handleEquals = (op)=>op.operand;
3101
- this.handleNotEquals = (op)=>op.operand;
3102
- this.handleIncludes = (op)=>op.operands;
3103
- this.handleExcludes = (op)=>op.operands;
3104
- this.handleExcludeIfAny = (op)=>op.operands;
3105
- this.handleGreaterThanOrEquals = (op)=>op.operand;
3106
- this.handleGreaterThan = (op)=>op.operand;
3107
- this.handleLessThan = (op)=>op.operand;
3108
- this.handleLessThanOrEquals = (op)=>op.operand;
3109
- this.handleIntersection = (_arg)=>undefined;
3110
- this.handleUnion = (_)=>undefined;
3111
- this.handleNestedFilter = (_)=>undefined;
3112
- this.handleExists = (_)=>undefined;
3113
- this.handleMissing = (_)=>undefined;
3114
- }
3115
- }
2676
+ * Returns if the current cohort has been saved
2677
+ * @param state - the CoreState
2678
+ * @category Cohort
2679
+ * @category Selectors
2680
+ * @hidden
2681
+ */ const selectCurrentCohortSaved = (state)=>{
2682
+ const cohort = cohortSelectors.selectById(state, getCurrentCohortFromCoreState(state));
2683
+ return cohort?.saved;
2684
+ };
3116
2685
  /**
3117
- * Extract the operand values, if operands themselves have values, otherwise undefined.
3118
- */ class EnumValueExtractorHandler {
3119
- constructor(){
3120
- this.handleEquals = (_)=>undefined;
3121
- this.handleNotEquals = (_)=>undefined;
3122
- this.handleIncludes = (op)=>op.operands;
3123
- this.handleExcludes = (op)=>op.operands;
3124
- this.handleExcludeIfAny = (op)=>op.operands;
3125
- this.handleGreaterThanOrEquals = (_)=>undefined;
3126
- this.handleGreaterThan = (_)=>undefined;
3127
- this.handleLessThan = (_)=>undefined;
3128
- this.handleLessThanOrEquals = (_)=>undefined;
3129
- this.handleIntersection = (_)=>undefined;
3130
- this.handleUnion = (_)=>undefined;
3131
- this.handleNestedFilter = (op)=>{
3132
- return extractEnumFilterValue(op.operand);
3133
- };
3134
- this.handleExists = (_)=>undefined;
3135
- this.handleMissing = (_)=>undefined;
3136
- }
3137
- }
3138
- const filterSetToOperation = (fs)=>{
3139
- if (!fs) return undefined;
3140
- switch(fs.mode){
3141
- case 'and':
3142
- return Object.keys(fs.root).length == 0 ? undefined : {
3143
- operator: fs.mode,
3144
- operands: Object.keys(fs.root).map((k)=>{
3145
- return fs.root[k];
3146
- })
3147
- };
2686
+ * Select a filter from the index.
2687
+ * returns undefined.
2688
+ * @param state - Core
2689
+ * @param index which cohort index to select from
2690
+ */ const selectIndexFilters = (state, index)=>{
2691
+ const cohort = cohortSelectors.selectById(state, getCurrentCohortFromCoreState(state));
2692
+ if (!cohort) {
2693
+ console.error('No Cohort Defined');
3148
2694
  }
3149
- return undefined;
2695
+ return cohort?.filters?.[index] ?? EmptyFilterSet;
3150
2696
  };
3151
2697
 
3152
- const isFilterSet = (input)=>{
3153
- if (typeof input !== 'object' || input === null) {
2698
+ const isFileItem = (item)=>{
2699
+ return item && 'guid' in item;
2700
+ };
2701
+ const isAdditionalDataItem = (item)=>{
2702
+ return item.itemType === 'AdditionalData'; // TODO resolve this with type from the api
2703
+ };
2704
+ // Type guard for CohortItem
2705
+ const isCohortItem = (item)=>{
2706
+ return item && 'data' in item && 'schemaVersion' in item && item.itemType === 'Gen3GraphQL';
2707
+ };
2708
+ // Type guard for DatalistAPI
2709
+ const isDatalistAPI = (value)=>{
2710
+ if (typeof value !== 'object' || value === null) {
3154
2711
  return false;
3155
2712
  }
3156
- const { root, mode } = input;
3157
- if (typeof root !== 'object' || root === null) {
2713
+ const data = value;
2714
+ // Check required properties in DataItemBaseData
2715
+ if (typeof data.name !== 'string' || typeof data.created_time !== 'string' || typeof data.updated_time !== 'string' || typeof data.version !== 'number' || typeof data.authz !== 'object' || data.authz === null || !Array.isArray(data.authz.authz)) {
3158
2716
  return false;
3159
2717
  }
3160
- if (![
3161
- 'and',
3162
- 'or'
3163
- ].includes(mode)) {
2718
+ // Check required properties in DatalistAsItems
2719
+ if (typeof data.items !== 'object' || data.items === null || typeof data.items !== 'object') {
3164
2720
  return false;
3165
2721
  }
3166
2722
  return true;
3167
2723
  };
3168
- function isUnion(value) {
3169
- return typeof value === 'object' && value !== null && value.operator === 'or' && Array.isArray(value.operands);
3170
- }
3171
- function isIntersection(value) {
3172
- return typeof value === 'object' && value !== null && value.operator === 'and' && Array.isArray(value.operands);
3173
- }
3174
- const isOperandsType = (operation)=>{
3175
- return operation?.operands !== undefined;
2724
+ /**
2725
+ * Type guard for DataLibraryAPIResponse
2726
+ */ const isDataLibraryAPIResponse = (obj)=>{
2727
+ return typeof obj === 'object' && obj !== null && 'lists' in obj && typeof obj.lists === 'object';
3176
2728
  };
2729
+ var DataLibraryStoreMode = /*#__PURE__*/ function(DataLibraryStoreMode) {
2730
+ DataLibraryStoreMode["ApiOnly"] = "apiOnly";
2731
+ DataLibraryStoreMode["ApiAndLocal"] = "apiAndLocal";
2732
+ DataLibraryStoreMode["LocalOnly"] = "localOnly";
2733
+ return DataLibraryStoreMode;
2734
+ }({});
3177
2735
 
3178
- const FieldNameOverrides = {};
3179
- const COMMON_PREPOSITIONS = [
3180
- 'a',
3181
- 'an',
3182
- 'and',
3183
- 'at',
3184
- 'but',
3185
- 'by',
3186
- 'for',
3187
- 'in',
3188
- 'is',
3189
- 'nor',
3190
- 'of',
3191
- 'on',
3192
- 'or',
3193
- 'out',
3194
- 'so',
3195
- 'the',
3196
- 'to',
3197
- 'up',
3198
- 'yet'
3199
- ];
3200
- const capitalize = (s)=>s.length > 0 ? s[0].toUpperCase() + s.slice(1) : '';
3201
- const trimFirstFieldNameToTitle = (fieldName, trim = false)=>{
3202
- if (trim) {
3203
- const source = fieldName.slice(fieldName.indexOf('.') + 1);
3204
- return fieldNameToTitle(source ? source : fieldName, 0);
3205
- }
3206
- return fieldNameToTitle(fieldName);
3207
- };
3208
- /**
3209
- * Converts a filter name to a title,
3210
- * For example files.input.experimental_strategy will get converted to Experimental Strategy
3211
- * if sections == 2 then the output would be Input Experimental Strategy
3212
- * @param fieldName input filter expected to be: string.firstpart_secondpart
3213
- * @param sections number of "sections" string.string.string to got back from the end of the field
3214
- */ const fieldNameToTitle = (fieldName, sections = 1)=>{
3215
- if (fieldName in FieldNameOverrides) {
3216
- return FieldNameOverrides[fieldName];
2736
+ const processItem = (id, data)=>{
2737
+ if (data?.type === 'AdditionalData') {
2738
+ return {
2739
+ name: data.name,
2740
+ itemType: 'AdditionalData',
2741
+ description: data?.description,
2742
+ documentationUrl: data?.documentationUrl,
2743
+ url: data?.url
2744
+ };
3217
2745
  }
3218
- if (fieldName === undefined) return 'No Title';
3219
- return fieldName.split('.').slice(-sections).map((s)=>s.split('_')).flat().map((word)=>COMMON_PREPOSITIONS.includes(word) ? word : capitalize(word)).join(' ');
2746
+ return {
2747
+ ...data,
2748
+ itemType: 'Data',
2749
+ guid: data.id,
2750
+ id: id
2751
+ };
2752
+ };
2753
+ const buildListItemsGroupedByDataset = (listData)=>{
2754
+ const items = Object.entries(listData).reduce((acc, [id, data])=>{
2755
+ if (data?.type === 'Gen3GraphQL') {
2756
+ const cohortData = data;
2757
+ acc[id] = {
2758
+ itemType: 'Gen3GraphQL',
2759
+ id: data.guid,
2760
+ schemaVersion: cohortData.schema_version,
2761
+ data: cohortData.data,
2762
+ name: data.name,
2763
+ index: cohortData.index
2764
+ };
2765
+ } else {
2766
+ // Dataset
2767
+ if (!(data?.dataset_guid && data.dataset_guid in acc)) {
2768
+ acc[data.dataset_guid] = {
2769
+ id: data.dataset_guid,
2770
+ name: '',
2771
+ members: {
2772
+ [id]: processItem(id, data)
2773
+ }
2774
+ };
2775
+ } else {
2776
+ acc[data.dataset_guid].members[id] = processItem(id, data);
2777
+ }
2778
+ }
2779
+ return acc;
2780
+ }, {});
2781
+ return items;
2782
+ };
2783
+ const BuildList = (listId, listData)=>{
2784
+ if (!Object.keys(listData).includes('items')) return undefined;
2785
+ const items = buildListItemsGroupedByDataset(listData?.items ?? {});
2786
+ return {
2787
+ items: items,
2788
+ version: listData?.version ?? 0,
2789
+ created_time: listData?.created_time,
2790
+ updated_time: listData?.updated_time,
2791
+ name: listData?.name ?? listId,
2792
+ id: listId,
2793
+ authz: listData?.authz
2794
+ };
3220
2795
  };
3221
2796
  /**
3222
- * Extracts the index name from the field name
3223
- * @param fieldName
3224
- */ const extractIndexFromFullFieldName = (fieldName)=>fieldName.split('.')[0];
3225
- /**
3226
- * prepend the index name to the field name
3227
- */ const prependIndexToFieldName = (fieldName, index)=>`${index}.${fieldName}`;
2797
+ * Constructs a `DataLibrary` object by transforming the input `DataLibraryAPIResponse`.
2798
+ *
2799
+ * This function takes an API response containing lists and processes each list entry.
2800
+ * It uses `BuildList` to build individual list objects for each entry in the provided data.
2801
+ * The resulting lists are accumulated and structured into a `DataLibrary` object. which
2802
+ * groups File Object by dataset_guid.
2803
+ *
2804
+ * @param {DataLibraryAPIResponse} data - The API response containing the lists to process.
2805
+ * @returns {DataLibrary} A structured `DataLibrary` object containing the processed lists.
2806
+ */ const BuildLists = (data)=>{
2807
+ return Object.entries(data?.lists).reduce((acc, [listId, listData])=>{
2808
+ const list = BuildList(listId, listData);
2809
+ if (list) acc[listId] = list;
2810
+ return acc;
2811
+ }, {});
2812
+ };
3228
2813
  /**
3229
- * extract the field name from the index.field name
3230
- */ const extractFieldNameFromFullFieldName = (fieldName)=>fieldName.split('.').slice(1).join('.');
2814
+ * Calculates the total number of items within a DataList object.
2815
+ *
2816
+ * @param {DataList} dataList - The DataList object to count items from.
2817
+ * @return {number} The total number of items in the DataList.
2818
+ */ const getNumberOfItemsInDatalist = (dataList)=>{
2819
+ if (!dataList?.items) return 0;
2820
+ return Object.values(dataList.items).reduce((count, item)=>{
2821
+ if (isCohortItem(item)) {
2822
+ return count + 1;
2823
+ } else {
2824
+ return count + Object.values(item?.members ?? {}).reduce((fileCount, x)=>{
2825
+ if (isFileItem(x)) {
2826
+ return fileCount + 1;
2827
+ }
2828
+ return fileCount;
2829
+ }, 0);
2830
+ }
2831
+ }, 0);
2832
+ };
2833
+ const flattenDataList = (dataList)=>{
2834
+ // convert datalist into user-data-library API for for updating.
2835
+ const items = Object.entries(dataList.items).reduce((acc, [id, value])=>{
2836
+ if (isCohortItem(value)) {
2837
+ acc[id] = value;
2838
+ } else {
2839
+ return {
2840
+ ...acc,
2841
+ ...value.members
2842
+ }; // TODO: might need to convert this to the API version
2843
+ }
2844
+ return acc;
2845
+ }, {});
2846
+ return {
2847
+ name: dataList.name,
2848
+ items: items
2849
+ };
2850
+ };
2851
+ const convertDatasetOrCohortToLibraryListItemsAPI = (list)=>{
2852
+ const result = {};
2853
+ // Iterate through each entry in the DatasetOrCohort object
2854
+ Object.entries(list).forEach(([datasetId, item])=>{
2855
+ if (isCohortItem(item)) {
2856
+ // Handle cohort items
2857
+ result[datasetId] = {
2858
+ itemType: 'Gen3GraphQL',
2859
+ id: item.id,
2860
+ schemaVersion: item.schemaVersion,
2861
+ data: item.data,
2862
+ name: item.name,
2863
+ index: item.index
2864
+ };
2865
+ } else {
2866
+ // Handle dataset items
2867
+ const members = item.members || {};
2868
+ // Process each member of the dataset
2869
+ Object.entries(members).forEach(([memberId, memberData])=>{
2870
+ if (isFileItem(memberData)) {
2871
+ result[memberId] = {
2872
+ ...memberData.guid && {
2873
+ guid: memberData.guid
2874
+ },
2875
+ ...memberData.name && {
2876
+ name: memberData.name
2877
+ },
2878
+ ...memberData.name && {
2879
+ name: memberData.name
2880
+ },
2881
+ ...memberData.description && {
2882
+ description: memberData.description
2883
+ },
2884
+ ...memberData.type && {
2885
+ type: memberData.type
2886
+ },
2887
+ dataset_guid: datasetId
2888
+ };
2889
+ } else if (memberData.itemType === 'AdditionalData') {
2890
+ // Handle additional data items
2891
+ result[memberId] = {
2892
+ itemType: 'AdditionalData',
2893
+ name: memberData.name,
2894
+ description: memberData.description,
2895
+ documentationUrl: memberData.documentationUrl,
2896
+ url: memberData.url,
2897
+ dataset_guid: datasetId
2898
+ };
2899
+ }
2900
+ });
2901
+ }
2902
+ });
2903
+ return result;
2904
+ };
2905
+ const convertDataLibraryToDataLibraryAPI = (dataLibrary)=>{
2906
+ const result = {};
2907
+ Object.entries(dataLibrary).forEach(([listId, list])=>{
2908
+ result[listId] = {
2909
+ name: list.name,
2910
+ items: convertDatasetOrCohortToLibraryListItemsAPI(list.items),
2911
+ version: list.version,
2912
+ created_time: list.created_time,
2913
+ updated_time: list.updated_time,
2914
+ authz: list.authz
2915
+ };
2916
+ });
2917
+ return result;
2918
+ };
2919
+ const extractIndexFromDataLibraryCohort = (query)=>{
2920
+ try {
2921
+ const parsedQuery = parse(query['query']);
2922
+ const aggregationField = parsedQuery.definitions.filter((def)=>def.kind === 'OperationDefinition').flatMap((def)=>def.selectionSet.selections).find((sel)=>sel.kind === 'Field' && sel.name.value === '_aggregation');
2923
+ if (aggregationField && 'selectionSet' in aggregationField) {
2924
+ const indexField = aggregationField?.selectionSet?.selections.find((sel)=>sel.kind === 'Field');
2925
+ return indexField ? indexField.name.value : null;
2926
+ }
2927
+ } catch (error) {
2928
+ console.error('Invalid GraphQL query:', error);
2929
+ }
2930
+ return null;
2931
+ };
3231
2932
  /**
3232
- * extract the field name and the index from the index.field name returning as a tuple
3233
- */ const extractIndexAndFieldNameFromFullFieldName = (fieldName)=>{
3234
- const [index, ...rest] = fieldName.split('.');
3235
- return [
3236
- index,
3237
- rest.join('.')
3238
- ];
2933
+ * Takes a list of file items from anb array of manifest entries
2934
+ * and creates an Object of Files grouped by their dataset guid, which is
2935
+ * used to add these to a Data Library List
2936
+ * @param data
2937
+ * @param dataFieldMapping
2938
+ * @constructor
2939
+ */ const extractFileDatasetsInRecords = (data, dataFieldMapping)=>{
2940
+ const items = data.reduce((acc, resource)=>{
2941
+ const dataObjects = resource[dataFieldMapping.dataObjectField];
2942
+ // Check if dataObjects exists and is an array
2943
+ if (!dataObjects || !Array.isArray(dataObjects)) {
2944
+ return acc;
2945
+ }
2946
+ const datasetId = resource[dataFieldMapping.datasetIdField]; // Note: typo still preserved
2947
+ if (datasetId === undefined) {
2948
+ return acc; // Skip if dataset ID is missing
2949
+ }
2950
+ const datafiles = dataObjects.reduce((dataAcc, dataObject)=>{
2951
+ const fileId = dataObject[dataFieldMapping.dataObjectIdField];
2952
+ // Skip items without a valid ID
2953
+ if (typeof fileId !== 'string' || !fileId) {
2954
+ return dataAcc;
2955
+ }
2956
+ const name = dataObject[dataFieldMapping?.dataObjectNameField ?? 'name'] ?? 'No Name';
2957
+ const size = dataObject[dataFieldMapping?.dataObjectSizeField ?? 'size'];
2958
+ let sizeString = 'N/A';
2959
+ if (typeof size === 'number') {
2960
+ sizeString = size.toString();
2961
+ }
2962
+ if (typeof size === 'string') {
2963
+ sizeString = size;
2964
+ }
2965
+ const md5Sum = dataObject[dataFieldMapping?.dataObjectMd5sumField ?? 'md5sum'] ?? 'N/A';
2966
+ const url = dataObject[dataFieldMapping?.dataObjectUrlField ?? 'url'] ?? 'N/A';
2967
+ let fileType = 'GA4GH_DRS';
2968
+ if (dataFieldMapping?.dataObjectFileTypeValue) fileType = dataFieldMapping.dataObjectFileTypeValue;
2969
+ if (dataFieldMapping?.dataObjectFileTypeField) fileType = dataObject[dataFieldMapping?.dataObjectFileTypeField];
2970
+ return {
2971
+ ...dataAcc,
2972
+ [fileId]: {
2973
+ dataset_guid: datasetId,
2974
+ id: fileId,
2975
+ guid: fileId,
2976
+ itemType: 'Data',
2977
+ name: name,
2978
+ size: sizeString,
2979
+ md5sum: md5Sum,
2980
+ type: fileType,
2981
+ url: url
2982
+ }
2983
+ };
2984
+ }, {});
2985
+ return {
2986
+ ...acc,
2987
+ ...datafiles
2988
+ };
2989
+ }, {});
2990
+ return items;
2991
+ };
2992
+
2993
+ const DATABASE_NAME = 'Gen3DataLibrary';
2994
+ const STORE_NAME = 'DataLibraryLists';
2995
+ class LocalStorageService {
2996
+ getDb() {
2997
+ return openDB(DATABASE_NAME, 1, {
2998
+ // TODO add more complete upgrade
2999
+ upgrade (db) {
3000
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
3001
+ db.createObjectStore(STORE_NAME, {
3002
+ keyPath: 'id'
3003
+ });
3004
+ }
3005
+ }
3006
+ });
3007
+ }
3008
+ async getList(id) {
3009
+ const db = await this.getDb();
3010
+ const tx = db.transaction(STORE_NAME, 'readonly');
3011
+ const store = tx.objectStore(STORE_NAME);
3012
+ const lists = await store.get(id);
3013
+ if (lists) {
3014
+ return {
3015
+ status: 200,
3016
+ message: 'success',
3017
+ lists: {
3018
+ [id]: {
3019
+ id: id,
3020
+ ...lists,
3021
+ items: lists.items
3022
+ }
3023
+ }
3024
+ };
3025
+ } else {
3026
+ return {
3027
+ isError: true,
3028
+ status: 500,
3029
+ message: `${id} does not exist`
3030
+ };
3031
+ }
3032
+ }
3033
+ async getLists() {
3034
+ const db = await this.getDb();
3035
+ const tx = db.transaction(STORE_NAME, 'readonly');
3036
+ const store = tx.objectStore(STORE_NAME);
3037
+ const lists = await store.getAll();
3038
+ if (!lists) {
3039
+ return {
3040
+ isError: true,
3041
+ status: 500,
3042
+ message: 'no lists returned'
3043
+ };
3044
+ }
3045
+ const listMap = lists.reduce((acc, x)=>{
3046
+ const { id } = x;
3047
+ acc[id] = x;
3048
+ return acc;
3049
+ }, {});
3050
+ const datalists = BuildLists({
3051
+ lists: listMap
3052
+ });
3053
+ return {
3054
+ status: 200,
3055
+ message: 'success',
3056
+ lists: datalists
3057
+ };
3058
+ }
3059
+ async addList(list) {
3060
+ const timestamp = getTimestamp();
3061
+ try {
3062
+ const db = await this.getDb();
3063
+ const tx = db.transaction(STORE_NAME, 'readwrite');
3064
+ const id = nanoid(); // Create an id for the list
3065
+ tx.objectStore(STORE_NAME).put({
3066
+ id,
3067
+ version: 0,
3068
+ items: list?.items ?? {},
3069
+ creator: '{{subject_id}}',
3070
+ authz: {
3071
+ version: 0,
3072
+ authz: [
3073
+ `/users/{{subject_id}}/user-library/lists/${id}`
3074
+ ]
3075
+ },
3076
+ name: list?.name ?? 'New List',
3077
+ created_time: timestamp,
3078
+ updated_time: timestamp
3079
+ });
3080
+ await tx.done;
3081
+ return {
3082
+ status: 200,
3083
+ message: 'list added'
3084
+ };
3085
+ } catch (_error) {
3086
+ return {
3087
+ isError: true,
3088
+ status: 500,
3089
+ message: `unable to add list ${list?.name ?? 'New List'}`
3090
+ };
3091
+ }
3092
+ }
3093
+ async updateList(id, update) {
3094
+ const { name, items } = update;
3095
+ try {
3096
+ const db = await this.getDb();
3097
+ const tx = db.transaction(STORE_NAME, 'readwrite');
3098
+ const store = tx.objectStore(STORE_NAME);
3099
+ const listData = await store.get(id);
3100
+ if (!listData) {
3101
+ throw new Error(`List ${id} does not exist`);
3102
+ }
3103
+ const timestamp = getTimestamp();
3104
+ const version = listData.version ? listData.version + 1 : 0;
3105
+ const updated = {
3106
+ ...listData,
3107
+ ...{
3108
+ name,
3109
+ items
3110
+ },
3111
+ version: version,
3112
+ updated_time: timestamp,
3113
+ created_time: listData.created_time
3114
+ };
3115
+ store.put(updated);
3116
+ await tx.done;
3117
+ return {
3118
+ status: 200,
3119
+ message: 'success'
3120
+ };
3121
+ } catch (error) {
3122
+ let errorMessage = 'An unknown error occurred';
3123
+ if (error instanceof Error) {
3124
+ errorMessage = error.message;
3125
+ }
3126
+ return {
3127
+ isError: true,
3128
+ status: 500,
3129
+ message: `Unable to update list: ${id}. Error: ${errorMessage}`
3130
+ };
3131
+ }
3132
+ }
3133
+ async deleteList(id) {
3134
+ try {
3135
+ const db = await this.getDb();
3136
+ const tx = db.transaction(STORE_NAME, 'readwrite');
3137
+ const store = tx.objectStore(STORE_NAME);
3138
+ const item = await store.get(id);
3139
+ if (!item) {
3140
+ throw new Error(`List ${id} does not exist`);
3141
+ }
3142
+ store.delete(id);
3143
+ await tx.done;
3144
+ return {
3145
+ status: 200,
3146
+ message: `${id} deleted`
3147
+ };
3148
+ } catch (error) {
3149
+ const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred';
3150
+ return {
3151
+ isError: true,
3152
+ status: 500,
3153
+ message: `Unable to delete list: ${id}. Error: ${errorMessage}`
3154
+ };
3155
+ }
3156
+ }
3157
+ async clearLists() {
3158
+ try {
3159
+ const db = await this.getDb();
3160
+ const tx = db.transaction(STORE_NAME, 'readwrite');
3161
+ tx.objectStore(STORE_NAME).clear();
3162
+ await tx.done;
3163
+ return {
3164
+ status: 200,
3165
+ message: 'list cleared'
3166
+ };
3167
+ } catch (error) {
3168
+ const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred';
3169
+ return {
3170
+ isError: true,
3171
+ status: 500,
3172
+ message: `Unable to clear library. Error: ${errorMessage}`
3173
+ };
3174
+ }
3175
+ }
3176
+ async cacheLists(data) {
3177
+ if (!data || typeof data !== 'object') {
3178
+ return {
3179
+ isError: true,
3180
+ status: 500,
3181
+ message: 'Invalid or missing lists property in request'
3182
+ };
3183
+ }
3184
+ const allLists = Object.entries(data).reduce((acc, [id, x])=>{
3185
+ if (!isDatalistAPI(x)) return acc;
3186
+ acc[id] = {
3187
+ ...x
3188
+ };
3189
+ return acc;
3190
+ }, {});
3191
+ try {
3192
+ const db = await this.getDb();
3193
+ const tx = db.transaction(STORE_NAME, 'readwrite');
3194
+ for (const [id, list] of Object.entries(allLists)){
3195
+ tx.objectStore(STORE_NAME).put({
3196
+ id,
3197
+ ...list
3198
+ });
3199
+ }
3200
+ await tx.done;
3201
+ return {
3202
+ status: 200,
3203
+ message: 'success'
3204
+ };
3205
+ } catch (error) {
3206
+ const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred';
3207
+ return {
3208
+ isError: true,
3209
+ status: 200,
3210
+ message: `unable to cache library to local storage. Error: ${errorMessage}`
3211
+ };
3212
+ }
3213
+ }
3214
+ async cacheList(id, data) {
3215
+ if (!data || typeof data !== 'object') {
3216
+ return {
3217
+ isError: true,
3218
+ status: 500,
3219
+ message: 'Invalid or missing lists property in request'
3220
+ };
3221
+ }
3222
+ try {
3223
+ const db = await this.getDb();
3224
+ const tx = db.transaction(STORE_NAME, 'readwrite');
3225
+ tx.objectStore(STORE_NAME).put({
3226
+ id: id,
3227
+ ...data
3228
+ });
3229
+ await tx.done;
3230
+ return {
3231
+ status: 200,
3232
+ message: 'success'
3233
+ };
3234
+ } catch (error) {
3235
+ const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred';
3236
+ return {
3237
+ isError: true,
3238
+ status: 500,
3239
+ message: `unable to clear library. Error: ${errorMessage}`
3240
+ };
3241
+ }
3242
+ }
3243
+ constructor(){
3244
+ this.setAllLists = async (data)=>{
3245
+ const timestamp = getTimestamp();
3246
+ const allLists = data.reduce((acc, x)=>{
3247
+ if (!isJSONObject(x)) return acc;
3248
+ const id = nanoid(10);
3249
+ acc[id] = {
3250
+ ...x,
3251
+ version: 0,
3252
+ created_time: timestamp,
3253
+ updated_time: timestamp,
3254
+ creator: '{{subject_id}}',
3255
+ authz: {
3256
+ version: 0,
3257
+ authz: [
3258
+ `/users/{{subject_id}}/user-library/lists/${id}`
3259
+ ]
3260
+ }
3261
+ };
3262
+ return acc;
3263
+ }, {});
3264
+ try {
3265
+ const db = await this.getDb();
3266
+ const tx = db.transaction(STORE_NAME, 'readwrite');
3267
+ for (const [id, list] of Object.entries(allLists)){
3268
+ tx.objectStore(STORE_NAME).put({
3269
+ id,
3270
+ ...list
3271
+ });
3272
+ }
3273
+ await tx.done;
3274
+ return {
3275
+ status: 200,
3276
+ message: 'success'
3277
+ };
3278
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
3279
+ } catch (_error) {
3280
+ return {
3281
+ isError: true,
3282
+ status: 500,
3283
+ message: 'unable to add lists'
3284
+ };
3285
+ }
3286
+ };
3287
+ }
3288
+ }
3289
+
3290
+ const fetchFromDataLibraryAPI = async (url, method = HttpMethod.GET, body = undefined)=>{
3291
+ try {
3292
+ return {
3293
+ data: await fetchJSONDataFromURL(url, true, method, body)
3294
+ };
3295
+ } catch (error) {
3296
+ if (error instanceof HTTPError) {
3297
+ return {
3298
+ error: {
3299
+ status: error.status,
3300
+ message: HTTPErrorMessages[error.status] || error.responseData?.message || 'No HTTP Error Message'
3301
+ }
3302
+ };
3303
+ } else {
3304
+ return {
3305
+ error: {
3306
+ status: 500,
3307
+ message: 'Unknown Error'
3308
+ }
3309
+ };
3310
+ }
3311
+ }
3312
+ };
3313
+ const responseFromMutation = (responseReceived)=>{
3314
+ if (responseReceived.error) {
3315
+ return {
3316
+ isError: true,
3317
+ ...responseReceived.error
3318
+ };
3319
+ }
3320
+ return {
3321
+ lists: responseReceived.data,
3322
+ message: 'success',
3323
+ status: 200
3324
+ };
3325
+ };
3326
+ class APIStorageService {
3327
+ constructor(apiBaseUrl = `${GEN3_DATA_LIBRARY_API}`){
3328
+ this.pendingRequests = new Map();
3329
+ this.apiBaseUrl = apiBaseUrl;
3330
+ }
3331
+ async dedupedRequest(url, method = HttpMethod.GET, body = undefined) {
3332
+ // Create a unique key for this request
3333
+ const requestKey = `${method}:${url}:${body ? JSON.stringify(body) : ''}`;
3334
+ // If this exact request is already in progress, return the pending promise
3335
+ if (this.pendingRequests.has(requestKey)) {
3336
+ return this.pendingRequests.get(requestKey);
3337
+ }
3338
+ // Otherwise, make the request and store the promise
3339
+ const requestPromise = fetchFromDataLibraryAPI(url, method, body);
3340
+ this.pendingRequests.set(requestKey, requestPromise);
3341
+ try {
3342
+ // Wait for the request to complete
3343
+ const result = await requestPromise;
3344
+ return result;
3345
+ } finally{
3346
+ // Remove the request from pending requests after it completes
3347
+ this.pendingRequests.delete(requestKey);
3348
+ }
3349
+ }
3350
+ async getLists() {
3351
+ const { data, error } = await this.dedupedRequest(`${this.apiBaseUrl}`);
3352
+ if (error) {
3353
+ return {
3354
+ isError: true,
3355
+ ...error
3356
+ };
3357
+ }
3358
+ if (data && isDataLibraryAPIResponse(data)) {
3359
+ const datalists = BuildLists(data);
3360
+ return {
3361
+ lists: datalists,
3362
+ status: 200,
3363
+ message: 'success'
3364
+ };
3365
+ }
3366
+ return {
3367
+ lists: {},
3368
+ status: 200,
3369
+ message: 'no list returned'
3370
+ };
3371
+ }
3372
+ async addList(list) {
3373
+ const response = await fetchFromDataLibraryAPI(`${this.apiBaseUrl}`, HttpMethod.PUT, JSON.stringify({
3374
+ lists: [
3375
+ list
3376
+ ]
3377
+ }));
3378
+ return responseFromMutation(response);
3379
+ }
3380
+ async updateList(id, list) {
3381
+ const response = await fetchFromDataLibraryAPI(`${this.apiBaseUrl}/${id}`, HttpMethod.PUT, JSON.stringify(list));
3382
+ return responseFromMutation(response);
3383
+ }
3384
+ async deleteList(id) {
3385
+ const response = await fetchFromDataLibraryAPI(`${this.apiBaseUrl}/${id}`, HttpMethod.DELETE);
3386
+ return responseFromMutation(response);
3387
+ }
3388
+ async clearLists() {
3389
+ const response = await fetchFromDataLibraryAPI(this.apiBaseUrl, HttpMethod.DELETE);
3390
+ return responseFromMutation(response);
3391
+ }
3392
+ async setAllLists(lists) {
3393
+ const response = await fetchFromDataLibraryAPI(this.apiBaseUrl, HttpMethod.POST, JSON.stringify({
3394
+ lists: Object.values(lists)
3395
+ }));
3396
+ return responseFromMutation(response);
3397
+ }
3398
+ async getList(id) {
3399
+ const { data, error } = await fetchFromDataLibraryAPI(`${this.apiBaseUrl}/${id}`);
3400
+ if (error) {
3401
+ return {
3402
+ isError: true,
3403
+ ...error
3404
+ };
3405
+ }
3406
+ if (isDataLibraryAPIResponse(data)) {
3407
+ const datalists = BuildLists(data);
3408
+ return {
3409
+ lists: datalists,
3410
+ status: 200,
3411
+ message: 'success'
3412
+ };
3413
+ }
3414
+ return {
3415
+ isError: true,
3416
+ status: 500,
3417
+ message: `Unknown error getting list ${id}`
3418
+ };
3419
+ }
3420
+ }
3421
+
3422
+ class CachedAPIService {
3423
+ constructor(){
3424
+ this.localStorageDataLibrary = new LocalStorageService(); // always update local storage
3425
+ this.apiDataLibrary = new APIStorageService();
3426
+ }
3427
+ async getLists() {
3428
+ // do a network request to get the library
3429
+ // get the remote list
3430
+ const apiResults = await this.apiDataLibrary.getLists();
3431
+ if (apiResults.isError) {
3432
+ return {
3433
+ ...apiResults,
3434
+ lists: undefined
3435
+ };
3436
+ }
3437
+ const dataLibrary = convertDataLibraryToDataLibraryAPI(apiResults?.lists ?? {});
3438
+ await this.localStorageDataLibrary.cacheLists(dataLibrary);
3439
+ return apiResults;
3440
+ }
3441
+ async getList(id) {
3442
+ return await this.localStorageDataLibrary.getList(id);
3443
+ }
3444
+ async getCachedLists(id) {
3445
+ return await this.localStorageDataLibrary.getList(id);
3446
+ }
3447
+ async setAllLists(lists) {
3448
+ const apiResults = await this.apiDataLibrary.setAllLists(lists);
3449
+ if (apiResults.isError) {
3450
+ return {
3451
+ ...apiResults,
3452
+ lists: undefined
3453
+ };
3454
+ }
3455
+ const dataLibrary = convertDataLibraryToDataLibraryAPI(apiResults?.lists ?? {});
3456
+ await this.localStorageDataLibrary.cacheLists(dataLibrary);
3457
+ return apiResults;
3458
+ }
3459
+ async addList(list) {
3460
+ // update the API list
3461
+ const apiResults = await this.apiDataLibrary.addList(list);
3462
+ if (apiResults.isError) {
3463
+ return {
3464
+ ...apiResults,
3465
+ lists: undefined
3466
+ };
3467
+ }
3468
+ const cacheResults = await this.localStorageDataLibrary.addList(list);
3469
+ return {
3470
+ ...cacheResults,
3471
+ lists: undefined
3472
+ };
3473
+ }
3474
+ async updateList(id, list) {
3475
+ const apiResults = await this.apiDataLibrary.updateList(id, list);
3476
+ if (apiResults.isError) {
3477
+ return {
3478
+ ...apiResults,
3479
+ lists: undefined
3480
+ };
3481
+ }
3482
+ return await this.localStorageDataLibrary.cacheList(id, apiResults.lists ?? {});
3483
+ }
3484
+ async deleteList(id) {
3485
+ const apiResults = await this.apiDataLibrary.deleteList(id);
3486
+ if (apiResults.isError) {
3487
+ return {
3488
+ ...apiResults,
3489
+ lists: undefined
3490
+ };
3491
+ }
3492
+ return await this.localStorageDataLibrary.deleteList(id);
3493
+ }
3494
+ async clearLists() {
3495
+ const apiResults = await this.apiDataLibrary.clearLists();
3496
+ if (apiResults.isError) {
3497
+ return {
3498
+ ...apiResults,
3499
+ lists: undefined
3500
+ };
3501
+ }
3502
+ return await this.localStorageDataLibrary.clearLists();
3503
+ }
3504
+ }
3505
+
3506
+ class DataLibraryStorageService {
3507
+ constructor(mode = DataLibraryStoreMode.ApiOnly){
3508
+ if (mode === DataLibraryStoreMode.ApiOnly) {
3509
+ this.storageService = new APIStorageService();
3510
+ } else if (mode === DataLibraryStoreMode.ApiAndLocal) this.storageService = new CachedAPIService();
3511
+ else this.storageService = new LocalStorageService();
3512
+ }
3513
+ async setStorageMode(mode) {
3514
+ if (mode === DataLibraryStoreMode.ApiOnly) {
3515
+ this.storageService = new APIStorageService();
3516
+ } else if (mode === DataLibraryStoreMode.ApiAndLocal) this.storageService = new CachedAPIService();
3517
+ else this.storageService = new LocalStorageService();
3518
+ }
3519
+ // private async syncApiAndLocal() {
3520
+ // const { lists: localData, isError: localError } =
3521
+ // await this.localStorageDataLibrary.getLists();
3522
+ // const { lists: apiData, isError: apiError } =
3523
+ // await this.apiDataLibrary.getLists();
3524
+ //
3525
+ // if (localError || apiError) {
3526
+ // return;
3527
+ // }
3528
+ //
3529
+ // const mergedData: Record<string, Datalist> = { ...localData };
3530
+ //
3531
+ // // First, update any existing items with newer versions from API
3532
+ // Object.values(apiData?.lists ?? {}).forEach((apiList) => {
3533
+ // const id = apiList.id as keyof DataLibrary;
3534
+ // const localList = localData?.[id];
3535
+ // if (
3536
+ // !localList ||
3537
+ // storage Date(apiList.updatedTime) > storage Date(localList.updatedTime)
3538
+ // ) {
3539
+ // mergedData[id] = apiList;
3540
+ // }
3541
+ // });
3542
+ //
3543
+ // // Push local-only changes to API
3544
+ // const syncPromises: Promise<any>[] = [];
3545
+ //
3546
+ // for (const [id, localList] of Object.entries(localData?.lists ?? {})) {
3547
+ // if (!apiData?.[id]) {
3548
+ // // This list exists locally but not in API, so push it to API
3549
+ // syncPromises.push(this.apiDataLibrary.addList(localList));
3550
+ // } else if (
3551
+ // storage Date(localList.updatedTime) > storage Date(apiData[id].updatedTime)
3552
+ // ) {
3553
+ // // Local list is newer than API, so update API
3554
+ // syncPromises.push(this.apiDataLibrary.updateList(localList));
3555
+ // }
3556
+ // }
3557
+ //
3558
+ // // Wait for all API operations to complete
3559
+ // await Promise.all(syncPromises);
3560
+ // await this.localStorageDataLibrary.cacheLists({ lists: mergedData });
3561
+ // }
3562
+ async getLists() {
3563
+ return await this.storageService.getLists();
3564
+ }
3565
+ async getList(id) {
3566
+ return await this.storageService.getList(id);
3567
+ }
3568
+ async getCachedLists(id) {
3569
+ return await this.storageService.getList(id);
3570
+ }
3571
+ async setAllLists(lists) {
3572
+ return await this.storageService.setAllLists(lists ?? {});
3573
+ }
3574
+ async addList(list) {
3575
+ return await this.storageService.addList(list);
3576
+ }
3577
+ async updateList(id, list) {
3578
+ return await this.storageService.updateList(id, list);
3579
+ }
3580
+ async deleteList(id) {
3581
+ return await this.storageService.deleteList(id);
3582
+ }
3583
+ async clearLists() {
3584
+ return await this.storageService.clearLists();
3585
+ }
3586
+ }
3587
+
3588
+ const EMPTY_LIST = {
3589
+ items: {},
3590
+ version: 0,
3591
+ created_time: 'not_set',
3592
+ updated_time: 'not_set',
3593
+ name: '',
3594
+ id: '',
3595
+ authz: {
3596
+ version: -1,
3597
+ authz: []
3598
+ }
3599
+ };
3600
+ const DEFAULT_LIST_NAME = 'List';
3601
+ const useDataLibrary = (options = {
3602
+ storageMode: DataLibraryStoreMode.ApiOnly
3603
+ })=>{
3604
+ // State management
3605
+ const [isLoggedIn, setIsLoggedIn] = useState(false);
3606
+ const [isLoading, setIsLoading] = useState(false);
3607
+ const [isUpdating, setIsUpdating] = useState(null);
3608
+ const [error, setError] = useState(null);
3609
+ const [lists, setLists] = useState({});
3610
+ // Refs
3611
+ const initialLoadRef = useRef(false);
3612
+ // Services
3613
+ const dataLibraryStoreAPI = useRef(new DataLibraryStorageService(options.storageMode)).current;
3614
+ const handleErrorOrSetLists = useCallback(async (error)=>{
3615
+ if (error.isError) {
3616
+ setError(error);
3617
+ } else {
3618
+ const getListResults = await dataLibraryStoreAPI.getLists();
3619
+ if (getListResults.isError) {
3620
+ setError(getListResults);
3621
+ } else {
3622
+ setLists(getListResults.lists ?? {});
3623
+ setError(null);
3624
+ }
3625
+ }
3626
+ }, [
3627
+ dataLibraryStoreAPI
3628
+ ]);
3629
+ const generateUniqueName = useCallback((baseName = DEFAULT_LIST_NAME)=>{
3630
+ let uniqueName = baseName;
3631
+ let counter = 1;
3632
+ const existingNames = Object.values(lists).map((x)=>x.name);
3633
+ while(existingNames.includes(uniqueName)){
3634
+ uniqueName = `${baseName} ${counter}`;
3635
+ counter++;
3636
+ }
3637
+ return uniqueName;
3638
+ }, [
3639
+ lists
3640
+ ]);
3641
+ const performLibraryOperation = useCallback(async (operation, updateId)=>{
3642
+ setError(null);
3643
+ if (updateId) {
3644
+ setIsUpdating(updateId);
3645
+ } else setIsLoading(true);
3646
+ const operationResults = await operation();
3647
+ await handleErrorOrSetLists(operationResults);
3648
+ if (updateId) setIsUpdating(null);
3649
+ else setIsLoading(false);
3650
+ return operationResults;
3651
+ }, [
3652
+ handleErrorOrSetLists
3653
+ ]);
3654
+ // Lifecycle effects
3655
+ useEffect(()=>{
3656
+ const initializeData = async ()=>{
3657
+ if (!initialLoadRef.current) {
3658
+ setError(null);
3659
+ setIsLoading(true);
3660
+ const results = await dataLibraryStoreAPI.getLists(); // get the initial lists
3661
+ if (results.isError) setError(results);
3662
+ else setLists(results.lists ?? {});
3663
+ setIsLoading(false);
3664
+ initialLoadRef.current = true;
3665
+ }
3666
+ };
3667
+ initializeData();
3668
+ }, [
3669
+ dataLibraryStoreAPI
3670
+ ]);
3671
+ useEffect(()=>{
3672
+ const handleLogin = async ()=>{
3673
+ // setIsLoading(true);
3674
+ // await dataLibraryStoreAPI.setUseAPI(options.requiresAPI && isLoggedIn);
3675
+ // setIsLoading(false);
3676
+ };
3677
+ handleLogin();
3678
+ }, [
3679
+ dataLibraryStoreAPI,
3680
+ isLoggedIn
3681
+ ]);
3682
+ // CRUD operations
3683
+ const addListToDataLibrary = useCallback(async (items, name)=>{
3684
+ const apiItems = convertDatasetOrCohortToLibraryListItemsAPI(items);
3685
+ const namedItems = {
3686
+ items: apiItems,
3687
+ name: generateUniqueName(name ?? DEFAULT_LIST_NAME)
3688
+ };
3689
+ return await performLibraryOperation(()=>dataLibraryStoreAPI.addList(namedItems));
3690
+ }, [
3691
+ dataLibraryStoreAPI,
3692
+ generateUniqueName,
3693
+ performLibraryOperation
3694
+ ]);
3695
+ const updateListInDataLibrary = useCallback(async (payload)=>{
3696
+ const flattened = flattenDataList(payload);
3697
+ return await performLibraryOperation(()=>dataLibraryStoreAPI.updateList(payload.id, {
3698
+ name: payload.name,
3699
+ items: flattened.items
3700
+ }), payload.id);
3701
+ }, [
3702
+ dataLibraryStoreAPI,
3703
+ performLibraryOperation
3704
+ ]);
3705
+ const deleteListFromDataLibrary = useCallback(async (id)=>{
3706
+ return await performLibraryOperation(()=>dataLibraryStoreAPI.deleteList(id));
3707
+ }, [
3708
+ dataLibraryStoreAPI,
3709
+ performLibraryOperation
3710
+ ]);
3711
+ const clearLibrary = useCallback(async ()=>{
3712
+ return await performLibraryOperation(()=>dataLibraryStoreAPI.clearLists());
3713
+ }, [
3714
+ dataLibraryStoreAPI,
3715
+ performLibraryOperation
3716
+ ]);
3717
+ const setAllListsInDataLibrary = useCallback(async (data)=>{
3718
+ const flattenedLists = data.map((x)=>flattenDataList(x));
3719
+ return await performLibraryOperation(()=>dataLibraryStoreAPI.setAllLists(flattenedLists));
3720
+ }, [
3721
+ dataLibraryStoreAPI,
3722
+ performLibraryOperation
3723
+ ]);
3724
+ const getDatalist = useCallback((id)=>{
3725
+ if (id in lists) return lists[id];
3726
+ setError({
3727
+ isError: true,
3728
+ status: 404,
3729
+ message: `List not found. Returning empty list.`
3730
+ });
3731
+ return EMPTY_LIST;
3732
+ }, [
3733
+ lists
3734
+ ]);
3735
+ const setLoginState = useCallback((loggedIn)=>setIsLoggedIn(loggedIn), []);
3736
+ const results = useDeepCompareMemo(()=>({
3737
+ dataLibrary: lists,
3738
+ isLoading,
3739
+ isUpdating,
3740
+ error,
3741
+ addListToDataLibrary,
3742
+ updateListInDataLibrary,
3743
+ deleteListFromDataLibrary,
3744
+ clearLibrary,
3745
+ setAllListsInDataLibrary,
3746
+ setLoginState,
3747
+ getDatalist
3748
+ }), [
3749
+ addListToDataLibrary,
3750
+ clearLibrary,
3751
+ deleteListFromDataLibrary,
3752
+ error,
3753
+ getDatalist,
3754
+ isLoading,
3755
+ isUpdating,
3756
+ lists,
3757
+ setAllListsInDataLibrary,
3758
+ setLoginState,
3759
+ updateListInDataLibrary
3760
+ ]);
3761
+ return results;
3239
3762
  };
3240
3763
 
3241
3764
  // using a random uuid v4 as the namespace
@@ -4901,5 +5424,5 @@ const coreCreateApi = buildCreateApi(coreModule(), reactHooksModule({
4901
5424
  }
4902
5425
  }));
4903
5426
 
4904
- export { Accessibility, CoreProvider, DataLibraryStoreMode, EmptyWorkspaceStatusResponse, GEN3_API, GEN3_AUTHZ_API, GEN3_COMMONS_NAME, GEN3_CROSSWALK_API, GEN3_DOMAIN, GEN3_DOWNLOADS_ENDPOINT, GEN3_FENCE_API, GEN3_GUPPY_API, GEN3_MANIFEST_API, GEN3_MDS_API, GEN3_REDIRECT_URL, GEN3_SOWER_API, GEN3_SUBMISSION_API, GEN3_WORKSPACE_API, HTTPError, HTTPErrorMessages, HttpMethod, MissingServiceConfigurationError, Modals, PodConditionType, PodStatus, RequestedWorkspaceStatus, WorkspaceStatus, addNewDefaultUnsavedCohort, buildGetAggregationQuery, buildListItemsGroupedByDataset, calculatePercentageAsNumber, calculatePercentageAsString, clearActiveWorkspaceId, clearCohortFilters, cohortReducer, convertFilterSetToGqlFilter, convertFilterToGqlFilter, convertToHistogramDataAsStringKey, convertToQueryString, coreCreateApi, coreStore, createAppApiForRTKQ, createAppStore, createGen3App, createGen3AppWithOwnStore, createUseCoreDataHook, downloadFromGuppyToBlob, downloadJSONDataFromGuppy, drsHostnamesReducer, extractEnumFilterValue, extractFieldNameFromFullFieldName, extractFileDatasetsInRecords, extractFilterValue, extractIndexAndFieldNameFromFullFieldName, extractIndexFromDataLibraryCohort, extractIndexFromFullFieldName, fetchFence, fetchFencePresignedURL, fetchJSONDataFromURL, fetchJson, fetchUserState, fieldNameToTitle, filterSetToOperation, gen3Api, getCurrentTimestamp, getFederatedLoginStatus, getGen3AppId, getNumberOfItemsInDatalist, getRemoteSupportServiceRegistry, getTimestamp, graphQLAPI, graphQLWithTags, groupSharedFields, guppyAPISliceMiddleware, guppyApi, guppyApiReducer, guppyApiSliceReducerPath, handleOperation, hideModal, histogramQueryStrForEachField, isAdditionalDataItem, isArray, isAuthenticated, isCohortItem, isDataLibraryAPIResponse, isDatalistAPI, isErrorWithMessage, isFetchBaseQueryError, isFetchError, isFetchParseError, isFileItem, isFilterEmpty, isFilterSet, isGQLIntersection, isGQLUnion, isGuppyAggregationData, isHistogramData, isHistogramDataAArray, isHistogramDataAnEnum, isHistogramDataArray, isHistogramDataArrayARange, isHistogramDataArrayAnEnum, isHistogramDataCollection, isHistogramRangeData, isHttpStatusError, isIntersection, isJSONObject, isJSONValue, isJSONValueArray, isNotDefined, isObject, isOperandsType, isOperationWithField, isOperatorWithFieldAndArrayOfOperands, isPending, isProgramUrl, isRootUrl, isString, isTimeGreaterThan, isUnion, isWorkspaceActive, isWorkspaceRunningOrStopping, listifyMethodsFromMapping, logoutFence, manifestApi, manifestTags, nestedHistogramQueryStrForEachField, prepareUrl$1 as prepareUrl, prependIndexToFieldName, processHistogramResponse, projectCodeFromResourcePath, queryMultipleMDSRecords, rawDataQueryStrForEachField, registerDefaultRemoteSupport, removeCohort, removeCohortFilter, requestorApi, resetUserState, resourcePathFromProjectID, roundHistogramResponse, selectActiveWorkspaceId, selectActiveWorkspaceStatus, selectAllCohortFiltersCollapsed, selectAuthzMappingData, selectAvailableCohorts, selectCSRFToken, selectCSRFTokenData, selectCohortFilterCombineMode, selectCohortFilterExpanded, selectCohortFilters, selectCurrentCohort, selectCurrentCohortFilters, selectCurrentCohortId, selectCurrentCohortModified, selectCurrentCohortName, selectCurrentCohortSaved, selectCurrentMessage, selectCurrentModal, selectGen3AppByName, selectGen3AppMetadataByName, selectHeadersWithCSRFToken, selectIndexFilters, selectIndexedFilterByName, selectPaymodelStatus, selectRequestedWorkspaceStatus, selectRequestedWorkspaceStatusTimestamp, selectSharedFilters, selectSharedFiltersForFields, selectShouldShareFilters, selectUser, selectUserAuthStatus, selectUserData, selectUserDetails, selectUserLoginStatus, selectWorkspaceStatus, selectWorkspaceStatusFromService, setActiveCohort, setActiveCohortList, setActiveWorkspace, setActiveWorkspaceId, setActiveWorkspaceStatus, setCohortFilter, setCohortFilterCombineMode, setCohortIndexFilters, setDRSHostnames, setRequestedWorkspaceStatus, setSharedFilters, setShouldShareFilters, setupCoreStore, showModal, submissionApi, toggleCohortBuilderAllFilters, toggleCohortBuilderCategoryFilter, trimFirstFieldNameToTitle, updateCohortFilter, useAddCohortManifestMutation, useAddFileManifestMutation, useAddMetadataManifestMutation, useAddNewCredentialMutation, useAskQuestionMutation, useAuthorizeFromCredentialsMutation, useCoreDispatch, useCoreSelector, useCoreStore, useCreateAuthzResourceMutation, useCreateRequestMutation, useDataLibrary, useDownloadFromGuppyMutation, useFetchUserDetailsQuery, useGeneralGQLQuery, useGetAISearchStatusQuery, useGetAISearchVersionQuery, useGetAccessibleDataQuery, useGetActivePayModelQuery, useGetAggMDSQuery, useGetAggsNoFilterSelfQuery, useGetAggsQuery, useGetAllFieldsForTypeQuery, useGetArrayTypes, useGetAuthzMappingsQuery, useGetAuthzResourcesQuery, useGetCSRFQuery, useGetCohortManifestQuery, useGetCountsQuery, useGetCredentialsQuery, useGetCrosswalkDataQuery, useGetDataQuery, useGetDictionaryQuery, useGetDownloadQuery, useGetExternalLoginsQuery, useGetFederatedLoginStatus, useGetFieldCountSummaryQuery, useGetFieldsForIndexQuery, useGetFileFromManifestQuery, useGetFileManifestQuery, useGetIndexAggMDSQuery, useGetIndexFields, useGetJWKKeysQuery, useGetLoginProvidersQuery, useGetMDSQuery, useGetManifestServiceStatusQuery, useGetMetadataByIdQuery, useGetMetadataFromManifestQuery, useGetMetadataManifestQuery, useGetProjectsDetailsQuery, useGetProjectsQuery, useGetRawDataAndTotalCountsQuery, useGetSharedFieldsForIndexQuery, useGetSowerJobListQuery, useGetSowerJobStatusQuery, useGetSowerOutputQuery, useGetSowerServiceStatusQuery, useGetStatus, useGetSubAggsQuery, useGetSubmissionGraphQLQuery, useGetSubmissionsQuery, useGetTagsQuery, useGetWorkspaceOptionsQuery, useGetWorkspacePayModelsQuery, useGetWorkspaceStatusQuery, useGraphQLQuery, useIsExternalConnectedQuery, useIsUserLoggedIn, useLaunchWorkspaceMutation, useLazyFetchUserDetailsQuery, useLazyGeneralGQLQuery, useLazyGetAggsNoFilterSelfQuery, useLazyGetAggsQuery, useLazyGetAuthzMappingsQuery, useLazyGetAuthzResourcesQuery, useLazyGetCSRFQuery, useLazyGetCrosswalkDataQuery, useLazyGetDownloadQuery, useLazyGetExternalLoginsQuery, useLazyGetManifestServiceStatusQuery, useLazyGetProjectsQuery, useLazyGetSowerJobListQuery, useLazyGetSubmissionGraphQLQuery, useLazyIsExternalConnectedQuery, useLazyRequestQuery, usePrevious, useRemoveCredentialMutation, useRequestByIdQuery, useRequestQuery, useRequestorStatusQuery, useSetCurrentPayModelMutation, useSubmitSowerJobMutation, useTerminateWorkspaceMutation, useUserAuth, useUserRequestQuery, userHasCreateOrUpdateOnAnyProject, userHasDataUpload, userHasMethodForServiceOnProject, userHasMethodForServiceOnResource, userHasMethodOnAnyProject, userHasSheepdogProgramAdmin, userHasSheepdogProjectAdmin };
5427
+ export { Accessibility, CohortStorage, CoreProvider, DataLibraryStoreMode, EmptyFilterSet, EmptyWorkspaceStatusResponse, EnumValueExtractorHandler, GEN3_API, GEN3_AUTHZ_API, GEN3_COMMONS_NAME, GEN3_CROSSWALK_API, GEN3_DOMAIN, GEN3_DOWNLOADS_ENDPOINT, GEN3_FENCE_API, GEN3_GUPPY_API, GEN3_MANIFEST_API, GEN3_MDS_API, GEN3_REDIRECT_URL, GEN3_SOWER_API, GEN3_SUBMISSION_API, GEN3_WORKSPACE_API, HTTPError, HTTPErrorMessages, HttpMethod, MissingServiceConfigurationError, Modals, PodConditionType, PodStatus, RequestedWorkspaceStatus, ToGqlHandler, ValueExtractorHandler, WorkspaceStatus, appendFilterToOperation, buildGetAggregationQuery, buildListItemsGroupedByDataset, calculatePercentageAsNumber, calculatePercentageAsString, clearActiveWorkspaceId, clearCohortFilters, cohortReducer, convertFilterSetToGqlFilter, convertFilterToGqlFilter, convertGqlFilterToFilter, convertToHistogramDataAsStringKey, convertToQueryString, coreCreateApi, coreStore, createAppApiForRTKQ, createAppStore, createGen3App, createGen3AppWithOwnStore, createNewCohort, createUseCoreDataHook, defaultCohortNameGenerator, downloadFromGuppyToBlob, downloadJSONDataFromGuppy, drsHostnamesReducer, duplicateCohort, extractEnumFilterValue, extractFieldNameFromFullFieldName, extractFileDatasetsInRecords, extractFilterValue, extractIndexAndFieldNameFromFullFieldName, extractIndexFromDataLibraryCohort, extractIndexFromFullFieldName, fetchFence, fetchFencePresignedURL, fetchJSONDataFromURL, fetchJson, fetchUserState, fieldNameToTitle, filterSetToOperation, gen3Api, generateUniqueName, getCurrentTimestamp, getFederatedLoginStatus, getGen3AppId, getNumberOfItemsInDatalist, getRemoteSupportServiceRegistry, getTimestamp, graphQLAPI, graphQLWithTags, groupSharedFields, guppyAPISliceMiddleware, guppyApi, guppyApiReducer, guppyApiSliceReducerPath, handleGqlOperation, handleOperation, hideModal, histogramQueryStrForEachField, isAdditionalDataItem, isArray, isAuthenticated, isCohortItem, isDataLibraryAPIResponse, isDatalistAPI, isErrorWithMessage, isFetchBaseQueryError, isFetchError, isFetchParseError, isFileItem, isFilterEmpty, isFilterSet, isGQLIntersection, isGQLUnion, isGuppyAggregationData, isHistogramData, isHistogramDataAArray, isHistogramDataAnEnum, isHistogramDataArray, isHistogramDataArrayARange, isHistogramDataArrayAnEnum, isHistogramDataCollection, isHistogramRangeData, isHttpStatusError, isIndexedFilterSetEmpty, isIntersection, isJSONObject, isJSONValue, isJSONValueArray, isNameUnique, isNotDefined, isObject, isOperandsType, isOperationWithField, isOperatorWithFieldAndArrayOfOperands, isPending, isProgramUrl, isRootUrl, isString, isTimeGreaterThan, isUnion, isWorkspaceActive, isWorkspaceRunningOrStopping, listifyMethodsFromMapping, logoutFence, manifestApi, manifestTags, nestedHistogramQueryStrForEachField, prepareUrl$1 as prepareUrl, prependIndexToFieldName, processHistogramResponse, projectCodeFromResourcePath, queryMultipleMDSRecords, rawDataQueryStrForEachField, registerDefaultRemoteSupport, removeCohort, removeCohortFilter, requestorApi, resetUserState, resourcePathFromProjectID, roundHistogramResponse, selectActiveWorkspaceId, selectActiveWorkspaceStatus, selectAllCohortFiltersCollapsed, selectAllCohorts, selectAuthzMappingData, selectAvailableCohorts, selectCSRFToken, selectCSRFTokenData, selectCohortFilterCombineMode, selectCohortFilterExpanded, selectCohortFilters, selectCurrentCohort, selectCurrentCohortFilters, selectCurrentCohortId, selectCurrentCohortModified, selectCurrentCohortName, selectCurrentCohortSaved, selectCurrentMessage, selectCurrentModal, selectGen3AppByName, selectGen3AppMetadataByName, selectHeadersWithCSRFToken, selectIndexFilters, selectIndexedFilterByName, selectPaymodelStatus, selectRequestedWorkspaceStatus, selectRequestedWorkspaceStatusTimestamp, selectSharedFilters, selectSharedFiltersForFields, selectShouldShareFilters, selectUser, selectUserAuthStatus, selectUserData, selectUserDetails, selectUserLoginStatus, selectWorkspaceStatus, selectWorkspaceStatusFromService, setActiveWorkspace, setActiveWorkspaceId, setActiveWorkspaceStatus, setCohortFilter, setCohortFilterCombineMode, setCohortIndexFilters, setCohortList, setCurrentCohortId, setDRSHostnames, setRequestedWorkspaceStatus, setSharedFilters, setShouldShareFilters, setupCoreStore, showModal, submissionApi, toggleCohortBuilderAllFilters, toggleCohortBuilderCategoryFilter, trimFirstFieldNameToTitle, updateCohortFilter, updateCohortName, useAddCohortManifestMutation, useAddFileManifestMutation, useAddMetadataManifestMutation, useAddNewCredentialMutation, useAskQuestionMutation, useAuthorizeFromCredentialsMutation, useCoreDispatch, useCoreSelector, useCoreStore, useCreateAuthzResourceMutation, useCreateRequestMutation, useDataLibrary, useDownloadFromGuppyMutation, useFetchUserDetailsQuery, useGeneralGQLQuery, useGetAISearchStatusQuery, useGetAISearchVersionQuery, useGetAccessibleDataQuery, useGetActivePayModelQuery, useGetAggMDSQuery, useGetAggsNoFilterSelfQuery, useGetAggsQuery, useGetAllFieldsForTypeQuery, useGetArrayTypes, useGetAuthzMappingsQuery, useGetAuthzResourcesQuery, useGetCSRFQuery, useGetCohortManifestQuery, useGetCountsQuery, useGetCredentialsQuery, useGetCrosswalkDataQuery, useGetDataQuery, useGetDictionaryQuery, useGetDownloadQuery, useGetExternalLoginsQuery, useGetFederatedLoginStatus, useGetFieldCountSummaryQuery, useGetFieldsForIndexQuery, useGetFileFromManifestQuery, useGetFileManifestQuery, useGetIndexAggMDSQuery, useGetIndexFields, useGetJWKKeysQuery, useGetLoginProvidersQuery, useGetMDSQuery, useGetManifestServiceStatusQuery, useGetMetadataByIdQuery, useGetMetadataFromManifestQuery, useGetMetadataManifestQuery, useGetProjectsDetailsQuery, useGetProjectsQuery, useGetRawDataAndTotalCountsQuery, useGetSharedFieldsForIndexQuery, useGetSowerJobListQuery, useGetSowerJobStatusQuery, useGetSowerOutputQuery, useGetSowerServiceStatusQuery, useGetStatus, useGetSubAggsQuery, useGetSubmissionGraphQLQuery, useGetSubmissionsQuery, useGetTagsQuery, useGetWorkspaceOptionsQuery, useGetWorkspacePayModelsQuery, useGetWorkspaceStatusQuery, useGraphQLQuery, useIsExternalConnectedQuery, useIsUserLoggedIn, useLaunchWorkspaceMutation, useLazyFetchUserDetailsQuery, useLazyGeneralGQLQuery, useLazyGetAggsNoFilterSelfQuery, useLazyGetAggsQuery, useLazyGetAuthzMappingsQuery, useLazyGetAuthzResourcesQuery, useLazyGetCSRFQuery, useLazyGetCrosswalkDataQuery, useLazyGetDownloadQuery, useLazyGetExternalLoginsQuery, useLazyGetManifestServiceStatusQuery, useLazyGetProjectsQuery, useLazyGetSowerJobListQuery, useLazyGetSubmissionGraphQLQuery, useLazyIsExternalConnectedQuery, useLazyRequestQuery, usePrevious, useRemoveCredentialMutation, useRequestByIdQuery, useRequestQuery, useRequestorStatusQuery, useSetCurrentPayModelMutation, useSubmitSowerJobMutation, useTerminateWorkspaceMutation, useUserAuth, useUserRequestQuery, userHasCreateOrUpdateOnAnyProject, userHasDataUpload, userHasMethodForServiceOnProject, userHasMethodForServiceOnResource, userHasMethodOnAnyProject, userHasSheepdogProgramAdmin, userHasSheepdogProjectAdmin };
4905
5428
  //# sourceMappingURL=index.js.map