@gen3/core 0.11.32 → 0.11.34

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 (60) hide show
  1. package/dist/cjs/index.js +887 -672
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/server.js +45 -0
  4. package/dist/cjs/server.js.map +1 -0
  5. package/dist/dts/constants.d.ts +1 -0
  6. package/dist/dts/constants.d.ts.map +1 -1
  7. package/dist/dts/features/cart/cartSelectors.d.ts +129 -0
  8. package/dist/dts/features/cart/cartSelectors.d.ts.map +1 -0
  9. package/dist/dts/features/cart/cartSlice.d.ts +24 -0
  10. package/dist/dts/features/cart/cartSlice.d.ts.map +1 -0
  11. package/dist/dts/features/cart/index.d.ts +5 -0
  12. package/dist/dts/features/cart/index.d.ts.map +1 -0
  13. package/dist/dts/features/cart/test/cartSelector.unit.test.d.ts +2 -0
  14. package/dist/dts/features/cart/test/cartSelector.unit.test.d.ts.map +1 -0
  15. package/dist/dts/features/cohort/cohortManagerSelector.d.ts +4 -0
  16. package/dist/dts/features/cohort/cohortManagerSelector.d.ts.map +1 -1
  17. package/dist/dts/features/cohort/cohortManagerSlice.d.ts +1 -0
  18. package/dist/dts/features/cohort/cohortManagerSlice.d.ts.map +1 -1
  19. package/dist/dts/features/cohort/utils.d.ts +13 -0
  20. package/dist/dts/features/cohort/utils.d.ts.map +1 -1
  21. package/dist/dts/features/filters/filters.d.ts +14 -0
  22. package/dist/dts/features/filters/filters.d.ts.map +1 -1
  23. package/dist/dts/features/filters/types.d.ts +7 -0
  24. package/dist/dts/features/filters/types.d.ts.map +1 -1
  25. package/dist/dts/features/guppy/guppyApi.d.ts.map +1 -1
  26. package/dist/dts/features/guppy/guppySlice.d.ts +0 -4
  27. package/dist/dts/features/guppy/guppySlice.d.ts.map +1 -1
  28. package/dist/dts/features/guppy/index.d.ts +6 -0
  29. package/dist/dts/features/guppy/index.d.ts.map +1 -1
  30. package/dist/dts/features/guppy/queryGenerators.d.ts +6 -0
  31. package/dist/dts/features/guppy/queryGenerators.d.ts.map +1 -0
  32. package/dist/dts/features/guppy/tests/queryGenerators.unit.test.d.ts +2 -0
  33. package/dist/dts/features/guppy/tests/queryGenerators.unit.test.d.ts.map +1 -0
  34. package/dist/dts/features/guppy/utils.d.ts +1 -1
  35. package/dist/dts/features/guppy/utils.d.ts.map +1 -1
  36. package/dist/dts/features/user/userSliceRTK.d.ts +3 -0
  37. package/dist/dts/features/user/userSliceRTK.d.ts.map +1 -1
  38. package/dist/dts/hooks.d.ts +2 -0
  39. package/dist/dts/hooks.d.ts.map +1 -1
  40. package/dist/dts/index.d.ts +3 -2
  41. package/dist/dts/index.d.ts.map +1 -1
  42. package/dist/dts/reducers.d.ts +2 -0
  43. package/dist/dts/reducers.d.ts.map +1 -1
  44. package/dist/dts/server.d.ts +4 -0
  45. package/dist/dts/server.d.ts.map +1 -0
  46. package/dist/dts/store.d.ts +4 -0
  47. package/dist/dts/store.d.ts.map +1 -1
  48. package/dist/dts/utils/conversions.d.ts +7 -0
  49. package/dist/dts/utils/conversions.d.ts.map +1 -1
  50. package/dist/dts/utils/extractvalues.d.ts +1 -0
  51. package/dist/dts/utils/extractvalues.d.ts.map +1 -1
  52. package/dist/dts/utils/index.d.ts +3 -2
  53. package/dist/dts/utils/index.d.ts.map +1 -1
  54. package/dist/esm/index.js +868 -673
  55. package/dist/esm/index.js.map +1 -1
  56. package/dist/esm/server.js +29 -0
  57. package/dist/esm/server.js.map +1 -0
  58. package/dist/index.d.ts +420 -199
  59. package/dist/server.d.ts +31 -0
  60. package/package.json +7 -2
package/dist/cjs/index.js CHANGED
@@ -6,18 +6,18 @@ var cookiesNext = require('cookies-next');
6
6
  var query = require('@reduxjs/toolkit/query');
7
7
  var reactRedux = require('react-redux');
8
8
  var React = require('react');
9
+ var graphql = require('graphql');
10
+ var jsonpathPlus = require('jsonpath-plus');
11
+ var lodash = require('lodash');
9
12
  var nanoid$1 = require('nanoid');
10
13
  var useSWR = require('swr');
11
- var lodash = require('lodash');
12
14
  var flat = require('flat');
13
15
  var Papa = require('papaparse');
14
- var jsonpathPlus = require('jsonpath-plus');
15
16
  var reduxPersist = require('redux-persist');
16
17
  var createWebStorage = require('redux-persist/lib/storage/createWebStorage');
17
18
  var react$1 = require('redux-persist/integration/react');
18
19
  var idb = require('idb');
19
20
  var useDeepCompare = require('use-deep-compare');
20
- var graphql = require('graphql');
21
21
  var uuid = require('uuid');
22
22
  var reactCookie = require('react-cookie');
23
23
  var Queue = require('queue');
@@ -72,6 +72,7 @@ const FILE_DELIMITERS = {
72
72
  tsv: '\t',
73
73
  csv: ','
74
74
  };
75
+ const CART_LIMIT = 10000;
75
76
 
76
77
  const isFetchError = (obj)=>{
77
78
  if (typeof obj !== 'object' || obj === null) {
@@ -426,7 +427,7 @@ const useCoreDispatch = reactRedux.useDispatch.withTypes();
426
427
  });
427
428
  const isAuthenticated = (loginStatus)=>loginStatus === 'authenticated';
428
429
  const isPending = (loginStatus)=>loginStatus === 'pending';
429
- const initialState$8 = {
430
+ const initialState$9 = {
430
431
  status: 'uninitialized',
431
432
  loginStatus: 'unauthenticated',
432
433
  error: undefined
@@ -437,9 +438,9 @@ const initialState$8 = {
437
438
  * @returns: status messages wrapped around fetchUserState response dict
438
439
  */ const slice$4 = toolkit.createSlice({
439
440
  name: 'fence/user',
440
- initialState: initialState$8,
441
+ initialState: initialState$9,
441
442
  reducers: {
442
- resetUserState: ()=>initialState$8
443
+ resetUserState: ()=>initialState$9
443
444
  },
444
445
  extraReducers: (builder)=>{
445
446
  builder.addCase(fetchUserState.fulfilled, (_, action)=>{
@@ -581,11 +582,11 @@ const { useGetExternalLoginsQuery, useLazyGetExternalLoginsQuery, useLazyIsExter
581
582
  }
582
583
  };
583
584
 
584
- const initialState$7 = {};
585
+ const initialState$8 = {};
585
586
  // TODO: document what this does
586
587
  const slice$3 = toolkit.createSlice({
587
588
  name: 'drsResolver',
588
- initialState: initialState$7,
589
+ initialState: initialState$8,
589
590
  reducers: {
590
591
  setDRSHostnames: (_state, action)=>{
591
592
  return action.payload;
@@ -607,12 +608,12 @@ const lookupGen3App = (id)=>{
607
608
  else return null;
608
609
  };
609
610
 
610
- const initialState$6 = {
611
+ const initialState$7 = {
611
612
  gen3Apps: {}
612
613
  };
613
614
  const slice$2 = toolkit.createSlice({
614
615
  name: 'gen3Apps',
615
- initialState: initialState$6,
616
+ initialState: initialState$7,
616
617
  reducers: {
617
618
  addGen3AppMetadata: (state, action)=>{
618
619
  const { name, requiredEntityTypes } = action.payload;
@@ -643,13 +644,13 @@ const selectGen3AppByName = (appName)=>lookupGen3App(appName); // TODO: memoize
643
644
  Modals["GeneralErrorModal"] = "GeneralErrorModal";
644
645
  return Modals;
645
646
  }({});
646
- const initialState$5 = {
647
+ const initialState$6 = {
647
648
  currentModal: null
648
649
  };
649
650
  //Creates a modal slice for tracking showModal and hideModal state.
650
651
  const slice$1 = toolkit.createSlice({
651
652
  name: 'modals',
652
- initialState: initialState$5,
653
+ initialState: initialState$6,
653
654
  reducers: {
654
655
  showModal: (state, action)=>{
655
656
  state.currentModal = action.payload.modal;
@@ -722,7 +723,7 @@ const getTimestamp = ()=>{
722
723
  };
723
724
 
724
725
  const NO_WORKSPACE_ID = 'none';
725
- const initialState$4 = {
726
+ const initialState$5 = {
726
727
  id: NO_WORKSPACE_ID,
727
728
  status: WorkspaceStatus.NotFound,
728
729
  requestedStatus: RequestedWorkspaceStatus.Unset,
@@ -730,7 +731,7 @@ const initialState$4 = {
730
731
  };
731
732
  const slice = toolkit.createSlice({
732
733
  name: 'ActiveWorkspace',
733
- initialState: initialState$4,
734
+ initialState: initialState$5,
734
735
  reducers: {
735
736
  setActiveWorkspaceId: (state, action)=>{
736
737
  state = {
@@ -772,6 +773,25 @@ const selectActiveWorkspaceStatus = (state)=>state.activeWorkspace.status;
772
773
  const selectRequestedWorkspaceStatus = (state)=>state.activeWorkspace.requestedStatus;
773
774
  const selectRequestedWorkspaceStatusTimestamp = (state)=>state.activeWorkspace.requestedStatusTimestamp;
774
775
 
776
+ const cartAdapter = toolkit.createEntityAdapter({
777
+ selectId: (item)=>item.id
778
+ });
779
+ const initialState$4 = cartAdapter.getInitialState({});
780
+ const cartSlice = toolkit.createSlice({
781
+ name: 'cart',
782
+ initialState: initialState$4,
783
+ reducers: {
784
+ addItemsToCart: cartAdapter.addMany,
785
+ removeItemsFromCart: cartAdapter.removeMany
786
+ }
787
+ });
788
+ const cartReducer = cartSlice.reducer;
789
+ const { addItemsToCart, removeItemsFromCart } = cartSlice.actions;
790
+
791
+ const { selectById: selectCartItem, selectIds: selectCartItems, selectAll: selectCart, selectTotal: selectCartCount } = cartAdapter.getSelectors((state)=>state.cart);
792
+
793
+ const cartReducerPath = 'cart';
794
+
775
795
  /**
776
796
  * Creates a base class core API for guppy API calls.
777
797
  * @returns: guppy core API with guppyAPIFetch base query
@@ -806,6 +826,15 @@ const selectRequestedWorkspaceStatusTimestamp = (state)=>state.activeWorkspace.r
806
826
  data: await response.json()
807
827
  };
808
828
  } catch (e) {
829
+ if (e instanceof graphql.GraphQLError) {
830
+ return {
831
+ error: {
832
+ message: e.message,
833
+ locations: e.locations,
834
+ path: e.path
835
+ }
836
+ };
837
+ }
809
838
  if (e instanceof Error) return {
810
839
  error: e.message
811
840
  };
@@ -820,578 +849,176 @@ const guppyAPISliceMiddleware = guppyApi.middleware;
820
849
  const guppyApiSliceReducerPath = guppyApi.reducerPath;
821
850
  const guppyApiReducer = guppyApi.reducer;
822
851
 
823
- const defaultCohortNameGenerator = ()=>`Custom cohort ${new Date().toLocaleString('en-CA', {
824
- timeZone: 'America/Chicago',
825
- hour12: false
826
- }).replace(',', '')}`;
827
- const isNameUnique = (entities, name, excludeId)=>{
828
- const trimmedName = name.trim();
829
- if (!trimmedName) return false;
830
- return !entities.some((cohort)=>cohort && cohort.id !== excludeId && cohort.name.trim().toLowerCase() === trimmedName.toLowerCase());
852
+ const isOperationWithField = (operation)=>{
853
+ return operation?.field !== undefined;
831
854
  };
832
- const generateUniqueName = (entities, baseName)=>{
833
- const trimmedBaseName = baseName.trim();
834
- // If base name is unique, use it
835
- if (isNameUnique(entities, trimmedBaseName)) {
836
- return trimmedBaseName;
855
+ const isOperatorWithFieldAndArrayOfOperands = (operation)=>{
856
+ 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
857
+ ) {
858
+ const { operator } = operation.operator;
859
+ return operator === 'in' || operator === 'exclude' || operator === 'excludeifany';
860
+ }
861
+ return false;
862
+ };
863
+ const extractFilterValue = (op)=>{
864
+ const valueExtractorHandler = new ValueExtractorHandler();
865
+ return handleOperation(valueExtractorHandler, op);
866
+ };
867
+ const extractEnumFilterValue = (op)=>{
868
+ const enumValueExtractorHandler = new EnumValueExtractorHandler();
869
+ const results = handleOperation(enumValueExtractorHandler, op);
870
+ return results ?? [];
871
+ };
872
+ const assertNever = (x)=>{
873
+ throw Error(`Exhaustive comparison did not handle: ${x}`);
874
+ };
875
+ const handleOperation = (handler, op)=>{
876
+ switch(op.operator){
877
+ case '=':
878
+ return handler.handleEquals(op);
879
+ case '!=':
880
+ return handler.handleNotEquals(op);
881
+ case '<':
882
+ return handler.handleLessThan(op);
883
+ case '<=':
884
+ return handler.handleLessThanOrEquals(op);
885
+ case '>':
886
+ return handler.handleGreaterThan(op);
887
+ case '>=':
888
+ return handler.handleGreaterThanOrEquals(op);
889
+ case 'and':
890
+ return handler.handleIntersection(op);
891
+ case 'or':
892
+ return handler.handleUnion(op);
893
+ case 'nested':
894
+ return handler.handleNestedFilter(op);
895
+ case 'in':
896
+ case 'includes':
897
+ return handler.handleIncludes(op);
898
+ case 'excludeifany':
899
+ return handler.handleExcludeIfAny(op);
900
+ case 'excludes':
901
+ return handler.handleExcludes(op);
902
+ case 'exists':
903
+ return handler.handleExists(op);
904
+ case 'missing':
905
+ return handler.handleMissing(op);
906
+ default:
907
+ return assertNever(op);
837
908
  }
838
- // Find a unique name by appending numbers
839
- let counter = 1;
840
- let uniqueName;
841
- do {
842
- uniqueName = `${trimmedBaseName} (${counter})`;
843
- counter++;
844
- }while (!isNameUnique(entities, uniqueName))
845
- return uniqueName;
846
909
  };
847
-
848
910
  /**
849
- * Cohorts in Gen3 are defined as a set of filters for each index in the data.
850
- * This means one cohort id defined for all "tabs" in CohortBuilder (explorer)
851
- * Switching a cohort id means all the cohorts for the index are changed.
852
- */ const DEFAULT_COHORT_NAME = 'Cohort';
853
- const newCohort = ({ filters = {}, customName })=>{
854
- const ts = new Date().toISOString();
855
- const newName = customName ?? defaultCohortNameGenerator();
856
- const newId = createCohortId();
857
- return {
858
- name: newName,
859
- id: newId,
860
- filters: filters ?? {},
861
- modified: false,
862
- saved: false,
863
- createdDatetime: ts,
864
- modifiedDatetime: ts,
865
- counts: {}
866
- };
911
+ * Return true if a FilterSet's root value is an empty object
912
+ * @param fs - FilterSet to test
913
+ */ const isFilterEmpty = (fs)=>lodash.isEqual({}, fs);
914
+ /**
915
+ * Type guard to check if an object is a GQLIntersection
916
+ * @param value - The value to check
917
+ * @returns True if the value is a GQLIntersection
918
+ */ const isGQLIntersection = (value)=>{
919
+ return typeof value === 'object' && value !== null && 'and' in value && Array.isArray(value.and);
867
920
  };
868
- const nanoid = nanoid$1.customAlphabet('1234567890abcdef', 16);
869
- const createCohortId = ()=>nanoid();
870
- const cohortsAdapter = toolkit.createEntityAdapter({
871
- sortComparer: (a, b)=>{
872
- if (a.modifiedDatetime <= b.modifiedDatetime) return 1;
873
- else return -1;
874
- },
875
- selectId: (cohort)=>cohort.id
876
- });
877
- // Create an initial unsaved cohort
878
- const initialCohort = newCohort({
879
- customName: DEFAULT_COHORT_NAME
880
- });
881
- const emptyInitialState = cohortsAdapter.getInitialState({
882
- currentCohortId: initialCohort.id,
883
- message: undefined
884
- });
885
- // Set the initial cohort in the adapter state
886
- const initialState$3 = cohortsAdapter.setOne(emptyInitialState, initialCohort);
887
- const getCurrentCohortId = (state)=>state.currentCohortId;
888
921
  /**
889
- * Redux slice for cohort filters
890
- */ const cohortManagerSlice = toolkit.createSlice({
891
- name: 'cohort',
892
- initialState: initialState$3,
893
- reducers: {
894
- createNewCohort: (state, action)=>{
895
- const baseName = action.payload.name || `Cohort`;
896
- const uniqueName = generateUniqueName(Object.values(state.entities), baseName);
897
- const cohort = newCohort({
898
- filters: action.payload.filters,
899
- customName: uniqueName
922
+ * Type guard to check if an object is a GQLIntersection
923
+ * @param value - The value to check
924
+ * @returns True if the value is a GQLIntersection
925
+ */ const isGQLUnion = (value)=>{
926
+ return typeof value === 'object' && value !== null && 'or' in value && Array.isArray(value.or);
927
+ };
928
+ class ToGqlHandler {
929
+ constructor(){
930
+ this.handleEquals = (op)=>({
931
+ '=': {
932
+ [op.field]: op.operand
933
+ }
900
934
  });
901
- cohortsAdapter.addOne(state, cohort);
902
- state.currentCohortId = cohort.id;
903
- },
904
- updateCohortName: (state, action)=>{
905
- const { id, name } = action.payload;
906
- cohortsAdapter.updateOne(state, {
907
- id: id,
908
- changes: {
909
- name: name,
910
- modified: true,
911
- modifiedDatetime: new Date().toISOString()
935
+ this.handleNotEquals = (op)=>({
936
+ '!=': {
937
+ [op.field]: op.operand
912
938
  }
913
939
  });
914
- },
915
- removeCohort: (state, action)=>{
916
- const { id: cohortId } = action.payload;
917
- const removedCohortName = state.entities[cohortId].name;
918
- const totalCohorts = Object.keys(state.entities).length;
919
- if (totalCohorts <= 1) {
920
- cohortsAdapter.removeAll(state);
921
- const defaultCohort = newCohort({
922
- filters: {},
923
- customName: DEFAULT_COHORT_NAME
924
- });
925
- cohortsAdapter.addOne(state, defaultCohort);
926
- state.currentCohortId = defaultCohort.id;
927
- if (action?.payload.shouldShowMessage) {
928
- state.message = [
929
- `deleteCohort|${removedCohortName}|${state.currentCohortId}`
930
- ];
940
+ this.handleLessThan = (op)=>({
941
+ '<': {
942
+ [op.field]: op.operand
931
943
  }
932
- return;
933
- }
934
- cohortsAdapter.removeOne(state, cohortId);
935
- // deleted the current cohort so set to the most recent cohort
936
- if (state.currentCohortId === cohortId) {
937
- const remainingIds = Object.keys(state.entities);
938
- state.currentCohortId = remainingIds[0];
939
- }
940
- if (action?.payload.shouldShowMessage) {
941
- state.message = [
942
- `deleteCohort|${removedCohortName}|${state.currentCohortId}`
943
- ];
944
- }
945
- },
946
- // adds a filter to the cohort filter set at the given index
947
- updateCohortFilter: (state, action)=>{
948
- const { index, field, filter } = action.payload;
949
- const currentCohortId = getCurrentCohortId(state);
950
- if (!state.entities[currentCohortId]) {
951
- return;
952
- }
953
- cohortsAdapter.updateOne(state, {
954
- id: currentCohortId,
955
- changes: {
956
- filters: {
957
- ...state.entities[currentCohortId].filters,
958
- [index]: {
959
- mode: state.entities[currentCohortId]?.filters[index]?.mode ?? 'and',
960
- root: {
961
- ...state.entities[currentCohortId]?.filters[index]?.root ?? {},
962
- [field]: filter
963
- }
964
- }
965
- },
966
- modified: true,
967
- modifiedDatetime: new Date().toISOString()
944
+ });
945
+ this.handleLessThanOrEquals = (op)=>({
946
+ '<=': {
947
+ [op.field]: op.operand
968
948
  }
969
949
  });
970
- },
971
- setCohortFilter: (state, action)=>{
972
- const { index, filters } = action.payload;
973
- const currentCohortId = getCurrentCohortId(state);
974
- if (!state.entities[currentCohortId]) {
975
- console.error(`no cohort with id=${currentCohortId} defined`);
976
- return;
977
- }
978
- cohortsAdapter.updateOne(state, {
979
- id: currentCohortId,
980
- changes: {
981
- filters: {
982
- ...state.entities[currentCohortId].filters,
983
- [index]: filters
984
- },
985
- modified: true,
986
- modifiedDatetime: new Date().toISOString()
950
+ this.handleGreaterThan = (op)=>({
951
+ '>': {
952
+ [op.field]: op.operand
987
953
  }
988
954
  });
989
- },
990
- setCohortIndexFilters: (state, action)=>{
991
- const currentCohortId = getCurrentCohortId(state);
992
- if (!state.entities[currentCohortId]) {
993
- console.error(`no cohort with id=${currentCohortId} defined`);
994
- return;
995
- }
996
- cohortsAdapter.updateOne(state, {
997
- id: currentCohortId,
998
- changes: {
999
- filters: action.payload.filters,
1000
- modified: true,
1001
- modifiedDatetime: new Date().toISOString()
955
+ this.handleGreaterThanOrEquals = (op)=>({
956
+ '>=': {
957
+ [op.field]: op.operand
1002
958
  }
1003
959
  });
1004
- },
1005
- // removes a filter to the cohort filter set at the given index
1006
- removeCohortFilter: (state, action)=>{
1007
- const { index, field } = action.payload;
1008
- const currentCohortId = getCurrentCohortId(state);
1009
- if (!state.entities[currentCohortId]) {
1010
- console.error(`no cohort with id=${currentCohortId} defined`);
1011
- return;
1012
- }
1013
- const filters = state.entities[currentCohortId]?.filters[index]?.root;
1014
- if (!filters) {
1015
- return;
1016
- }
1017
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1018
- const { [field]: _a, ...updated } = filters;
1019
- cohortsAdapter.updateOne(state, {
1020
- id: currentCohortId,
1021
- changes: {
1022
- filters: {
1023
- ...state.entities[currentCohortId]?.filters,
1024
- [index]: {
1025
- mode: state.entities[currentCohortId].filters[index].mode,
1026
- root: updated
1027
- }
1028
- },
1029
- modified: true,
1030
- modifiedDatetime: new Date().toISOString()
960
+ this.handleIncludes = (op)=>({
961
+ in: {
962
+ [op.field]: op.operands
1031
963
  }
1032
964
  });
1033
- },
1034
- duplicateCohort: (state)=>{
1035
- const currentCohortId = getCurrentCohortId(state);
1036
- const currentCohort = state.entities[currentCohortId];
1037
- const newName = generateUniqueName(Object.values(state.entities), currentCohort.name);
1038
- const duplicatedCohort = newCohort({
1039
- filters: {
1040
- ...currentCohort.filters
1041
- },
1042
- customName: newName
1043
- });
1044
- cohortsAdapter.addOne(state, {
1045
- ...duplicatedCohort,
1046
- counts: {
1047
- ...currentCohort.counts
965
+ this.handleExcludes = (op)=>({
966
+ exclude: {
967
+ [op.field]: op.operands
1048
968
  }
1049
969
  });
1050
- state.currentCohortId = duplicatedCohort.id;
1051
- },
1052
- // removes all filters from the cohort filter set at the given index
1053
- clearCohortFilters: (state, action)=>{
1054
- const { index } = action.payload;
1055
- const currentCohortId = getCurrentCohortId(state);
1056
- if (!state.entities[currentCohortId]) {
1057
- console.error(`no cohort with id=${currentCohortId} defined`);
1058
- return;
1059
- }
1060
- const filters = state.entities[currentCohortId]?.filters[index]?.root;
1061
- if (!filters) {
1062
- return;
1063
- }
1064
- cohortsAdapter.updateOne(state, {
1065
- id: currentCohortId,
1066
- changes: {
1067
- filters: {
1068
- ...state.entities[currentCohortId]?.filters,
1069
- [index]: {
1070
- mode: 'and',
1071
- root: {}
1072
- }
1073
- },
1074
- modified: true,
1075
- modifiedDatetime: new Date().toISOString()
970
+ this.handleExcludeIfAny = (op)=>({
971
+ excludeifany: {
972
+ [op.field]: op.operands
1076
973
  }
1077
974
  });
1078
- },
1079
- updateCohortCounts: (state, action)=>{
1080
- const currentCohortId = getCurrentCohortId(state);
1081
- const currentCohort = state.entities[currentCohortId];
1082
- cohortsAdapter.updateOne(state, {
1083
- id: currentCohortId,
1084
- changes: {
1085
- counts: {
1086
- ...currentCohort.counts,
1087
- ...action.payload
1088
- }
1089
- }
975
+ this.handleIntersection = (op)=>({
976
+ and: op.operands.map((x)=>convertFilterToGqlFilter(x))
1090
977
  });
1091
- },
1092
- updateCohortIndexCountById: (state, action)=>{
1093
- const { index, cohortId, counts } = action.payload;
1094
- const cohort = state.entities[cohortId];
1095
- cohortsAdapter.updateOne(state, {
1096
- id: cohortId,
1097
- changes: {
1098
- counts: {
1099
- ...cohort.counts,
1100
- ...{
1101
- [index]: counts
1102
- }
1103
- }
978
+ this.handleUnion = (op)=>({
979
+ or: op.operands.map((x)=>convertFilterToGqlFilter(x))
980
+ });
981
+ this.handleMissing = (op)=>({
982
+ is: {
983
+ [op.field]: 'MISSING'
1104
984
  }
1105
985
  });
1106
- },
1107
- setCurrentCohortId: (state, action)=>{
1108
- state.currentCohortId = action.payload;
1109
- },
1110
- /** @hidden */ setCohortList: (state, action)=>{
1111
- if (!action.payload) {
1112
- cohortsAdapter.removeMany(state, state.ids);
1113
- } else {
1114
- cohortsAdapter.upsertMany(state, [
1115
- ...action.payload
1116
- ]);
1117
- }
1118
- }
1119
- }
1120
- });
1121
- /**
1122
- * Returns the selectors for the cohorts EntityAdapter
1123
- * @param state - the CoreState
1124
- *
1125
- * @hidden
1126
- */ const cohortSelectors = cohortsAdapter.getSelectors((state)=>state.cohorts.cohortManager);
1127
- // Filter actions: addFilter, removeFilter, updateFilter
1128
- const { createNewCohort, updateCohortFilter, setCohortFilter, setCohortIndexFilters, duplicateCohort, removeCohortFilter, clearCohortFilters, removeCohort, setCurrentCohortId, updateCohortName, updateCohortCounts, updateCohortIndexCountById, setCohortList } = cohortManagerSlice.actions;
1129
- const cohortReducer = cohortManagerSlice.reducer;
1130
-
1131
- const initialState$2 = {};
1132
- const expandSlice$1 = toolkit.createSlice({
1133
- name: 'CohortBuilder/filterExpand',
1134
- initialState: initialState$2,
1135
- reducers: {
1136
- toggleCohortBuilderCategoryFilter: (state, action)=>{
1137
- return {
1138
- ...state,
1139
- [action.payload.index]: {
1140
- ...state[action.payload.index],
1141
- [action.payload.field]: action.payload.expanded
986
+ this.handleExists = (op)=>({
987
+ not: {
988
+ [op.field]: op?.operand ?? null
1142
989
  }
1143
- };
1144
- },
1145
- toggleCohortBuilderAllFilters: (state, action)=>{
1146
- return {
1147
- ...state,
1148
- [action.payload.index]: Object.keys(state[action.payload.index]).reduce((acc, k)=>{
1149
- acc[k] = action.payload.expand;
1150
- return acc;
1151
- }, {})
1152
- };
1153
- }
1154
- }
1155
- });
1156
- const cohortBuilderFiltersExpandedReducer = expandSlice$1.reducer;
1157
- const { toggleCohortBuilderCategoryFilter, toggleCohortBuilderAllFilters } = expandSlice$1.actions;
1158
- const selectCohortFilterExpanded = (state, index, field)=>state.cohorts.filtersExpanded?.[index]?.[field];
1159
- const selectAllCohortFiltersCollapsed = (state, index)=>index in state.cohorts.filtersExpanded ? Object.values(state.cohorts.filtersExpanded?.[index]).every((e)=>!e) : false;
1160
-
1161
- const initialState$1 = {};
1162
- const expandSlice = toolkit.createSlice({
1163
- name: 'CohortBuilder/filterCombineMode',
1164
- initialState: initialState$1,
1165
- reducers: {
1166
- setCohortFilterCombineMode: (state, action)=>{
990
+ });
991
+ this.handleNestedFilter = (op)=>{
992
+ const child = convertFilterToGqlFilter(op.operand);
1167
993
  return {
1168
- ...state,
1169
- [action.payload.index]: {
1170
- ...state[action.payload.index],
1171
- [action.payload.field]: action.payload.mode
994
+ nested: {
995
+ path: op.path,
996
+ ...child
1172
997
  }
1173
998
  };
1174
- }
999
+ };
1175
1000
  }
1176
- });
1177
- const cohortBuilderFiltersCombineModeReducer = expandSlice.reducer;
1178
- const { setCohortFilterCombineMode } = expandSlice.actions;
1179
- const selectCohortFilterCombineMode = (state, index, field)=>state.cohorts.filtersCombineMode?.[index]?.[field] ?? 'or';
1180
-
1181
- const initialState = {
1182
- shouldShareFilters: false,
1183
- sharedFiltersMap: {}
1001
+ }
1002
+ const convertFilterToGqlFilter = (filter)=>{
1003
+ const handler = new ToGqlHandler();
1004
+ return handleOperation(handler, filter);
1184
1005
  };
1185
- const cohortSharedFiltersSlice = toolkit.createSlice({
1186
- name: 'cohortSharedFilters',
1187
- initialState: initialState,
1188
- reducers: {
1189
- setShouldShareFilters: (state, action)=>{
1190
- state.shouldShareFilters = action.payload;
1191
- return state;
1192
- },
1193
- setSharedFilters: (state, action)=>{
1194
- state.sharedFiltersMap = action.payload;
1195
- }
1196
- }
1197
- });
1198
- const selectShouldShareFilters = (state)=>state.cohorts.sharedFilters.shouldShareFilters;
1199
- const selectSharedFilters = (state)=>state.cohorts.sharedFilters.sharedFiltersMap;
1200
- const selectSharedFiltersForFields = (state, field)=>state.cohorts.sharedFilters.sharedFiltersMap?.[field] ?? [
1201
- field
1202
- ];
1203
- const { setShouldShareFilters, setSharedFilters } = cohortSharedFiltersSlice.actions;
1204
- const cohortSharedFiltersReducer = cohortSharedFiltersSlice.reducer;
1205
-
1206
- const cohortReducers = toolkit.combineReducers({
1207
- filtersExpanded: cohortBuilderFiltersExpandedReducer,
1208
- filtersCombineMode: cohortBuilderFiltersCombineModeReducer,
1209
- sharedFilters: cohortSharedFiltersReducer,
1210
- cohortManager: cohortReducer
1211
- });
1212
-
1213
- const rootReducer = toolkit.combineReducers({
1214
- gen3Services: gen3ServicesReducer,
1215
- user: userReducer,
1216
- gen3Apps: gen3AppReducer,
1217
- drsHostnames: drsHostnamesReducer,
1218
- modals: modalReducer,
1219
- cohorts: cohortReducers,
1220
- activeWorkspace: activeWorkspaceReducer,
1221
- [guppyApiSliceReducerPath]: guppyApiReducer,
1222
- [userAuthApiReducerPath]: userAuthApiReducer
1223
- });
1224
-
1225
- const isOperationWithField = (operation)=>{
1226
- return operation?.field !== undefined;
1227
- };
1228
- const isOperatorWithFieldAndArrayOfOperands = (operation)=>{
1229
- 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
1230
- ) {
1231
- const { operator } = operation.operator;
1232
- return operator === 'in' || operator === 'exclude' || operator === 'excludeifany';
1233
- }
1234
- return false;
1235
- };
1236
- const extractFilterValue = (op)=>{
1237
- const valueExtractorHandler = new ValueExtractorHandler();
1238
- return handleOperation(valueExtractorHandler, op);
1239
- };
1240
- const extractEnumFilterValue = (op)=>{
1241
- const enumValueExtractorHandler = new EnumValueExtractorHandler();
1242
- const results = handleOperation(enumValueExtractorHandler, op);
1243
- return results ?? [];
1244
- };
1245
- const assertNever = (x)=>{
1246
- throw Error(`Exhaustive comparison did not handle: ${x}`);
1247
- };
1248
- const handleOperation = (handler, op)=>{
1249
- switch(op.operator){
1250
- case '=':
1251
- return handler.handleEquals(op);
1252
- case '!=':
1253
- return handler.handleNotEquals(op);
1254
- case '<':
1255
- return handler.handleLessThan(op);
1256
- case '<=':
1257
- return handler.handleLessThanOrEquals(op);
1258
- case '>':
1259
- return handler.handleGreaterThan(op);
1260
- case '>=':
1261
- return handler.handleGreaterThanOrEquals(op);
1262
- case 'and':
1263
- return handler.handleIntersection(op);
1264
- case 'or':
1265
- return handler.handleUnion(op);
1266
- case 'nested':
1267
- return handler.handleNestedFilter(op);
1268
- case 'in':
1269
- case 'includes':
1270
- return handler.handleIncludes(op);
1271
- case 'excludeifany':
1272
- return handler.handleExcludeIfAny(op);
1273
- case 'excludes':
1274
- return handler.handleExcludes(op);
1275
- case 'exists':
1276
- return handler.handleExists(op);
1277
- case 'missing':
1278
- return handler.handleMissing(op);
1279
- default:
1280
- return assertNever(op);
1281
- }
1282
- };
1283
- /**
1284
- * Return true if a FilterSet's root value is an empty object
1285
- * @param fs - FilterSet to test
1286
- */ const isFilterEmpty = (fs)=>lodash.isEqual({}, fs);
1287
- /**
1288
- * Type guard to check if an object is a GQLIntersection
1289
- * @param value - The value to check
1290
- * @returns True if the value is a GQLIntersection
1291
- */ const isGQLIntersection = (value)=>{
1292
- return typeof value === 'object' && value !== null && 'and' in value && Array.isArray(value.and);
1293
- };
1294
- /**
1295
- * Type guard to check if an object is a GQLIntersection
1296
- * @param value - The value to check
1297
- * @returns True if the value is a GQLIntersection
1298
- */ const isGQLUnion = (value)=>{
1299
- return typeof value === 'object' && value !== null && 'or' in value && Array.isArray(value.or);
1300
- };
1301
- class ToGqlHandler {
1302
- constructor(){
1303
- this.handleEquals = (op)=>({
1304
- '=': {
1305
- [op.field]: op.operand
1306
- }
1307
- });
1308
- this.handleNotEquals = (op)=>({
1309
- '!=': {
1310
- [op.field]: op.operand
1311
- }
1312
- });
1313
- this.handleLessThan = (op)=>({
1314
- '<': {
1315
- [op.field]: op.operand
1316
- }
1317
- });
1318
- this.handleLessThanOrEquals = (op)=>({
1319
- '<=': {
1320
- [op.field]: op.operand
1321
- }
1322
- });
1323
- this.handleGreaterThan = (op)=>({
1324
- '>': {
1325
- [op.field]: op.operand
1326
- }
1327
- });
1328
- this.handleGreaterThanOrEquals = (op)=>({
1329
- '>=': {
1330
- [op.field]: op.operand
1331
- }
1332
- });
1333
- this.handleIncludes = (op)=>({
1334
- in: {
1335
- [op.field]: op.operands
1336
- }
1337
- });
1338
- this.handleExcludes = (op)=>({
1339
- exclude: {
1340
- [op.field]: op.operands
1341
- }
1342
- });
1343
- this.handleExcludeIfAny = (op)=>({
1344
- excludeifany: {
1345
- [op.field]: op.operands
1346
- }
1347
- });
1348
- this.handleIntersection = (op)=>({
1349
- and: op.operands.map((x)=>convertFilterToGqlFilter(x))
1350
- });
1351
- this.handleUnion = (op)=>({
1352
- or: op.operands.map((x)=>convertFilterToGqlFilter(x))
1353
- });
1354
- this.handleMissing = (op)=>({
1355
- is: {
1356
- [op.field]: 'MISSING'
1357
- }
1358
- });
1359
- this.handleExists = (op)=>({
1360
- not: {
1361
- [op.field]: op?.operand ?? null
1362
- }
1363
- });
1364
- this.handleNestedFilter = (op)=>{
1365
- const child = convertFilterToGqlFilter(op.operand);
1366
- return {
1367
- nested: {
1368
- path: op.path,
1369
- ...child
1370
- }
1371
- };
1372
- };
1373
- }
1374
- }
1375
- const convertFilterToGqlFilter = (filter)=>{
1376
- const handler = new ToGqlHandler();
1377
- return handleOperation(handler, filter);
1378
- };
1379
- const convertFilterSetToGqlFilter = (fs, toplevelOp = 'and')=>{
1380
- const fsKeys = Object.keys(fs.root);
1381
- // if no keys return undefined
1382
- if (fsKeys.length === 0) return {
1383
- and: []
1384
- };
1385
- return toplevelOp === 'and' ? {
1386
- and: fsKeys.map((key)=>convertFilterToGqlFilter(fs.root[key]))
1387
- } : {
1388
- or: fsKeys.map((key)=>convertFilterToGqlFilter(fs.root[key]))
1389
- };
1390
- };
1391
- const handleGqlOperation = (handler, op)=>{
1392
- const operationKeys = Object.keys(op);
1393
- if (operationKeys.includes('=')) {
1394
- return handler.handleEquals(op);
1006
+ const convertFilterSetToGqlFilter = (fs, toplevelOp = 'and')=>{
1007
+ const fsKeys = Object.keys(fs.root);
1008
+ // if no keys return undefined
1009
+ if (fsKeys.length === 0) return {
1010
+ and: []
1011
+ };
1012
+ return toplevelOp === 'and' ? {
1013
+ and: fsKeys.map((key)=>convertFilterToGqlFilter(fs.root[key]))
1014
+ } : {
1015
+ or: fsKeys.map((key)=>convertFilterToGqlFilter(fs.root[key]))
1016
+ };
1017
+ };
1018
+ const handleGqlOperation = (handler, op)=>{
1019
+ const operationKeys = Object.keys(op);
1020
+ if (operationKeys.includes('=')) {
1021
+ return handler.handleEquals(op);
1395
1022
  }
1396
1023
  if (operationKeys.includes('!=')) {
1397
1024
  return handler.handleNotEquals(op);
@@ -1615,6 +1242,36 @@ const filterSetToOperation = (fs)=>{
1615
1242
  }
1616
1243
  return undefined;
1617
1244
  };
1245
+ /**
1246
+ * Constructs a nested operation object based on the provided field and leaf operand.
1247
+ * If the field does not contain a dot '.', it either assigns the field to the leaf operand (if applicable)
1248
+ * or returns the leaf operand as is. When the field contains dots, it splits the field into parts,
1249
+ * creates a "nested" operation for the root field, and recursively constructs the nested structure
1250
+ * for the remaining portion of the field.
1251
+ *
1252
+ * @param {string} field - The hierarchical field path, with segments separated by dots (e.g., "root.child").
1253
+ * @param {Operation} leafOperand - The operation to be nested within the specified path.
1254
+ * @param parentPath - The parent path of the current field. Guppy nested filters require a parent path.
1255
+ * @param depth
1256
+ * @returns {Operation} A nested operation object that represents the structured path and operand.
1257
+ */ const buildNestedGQLFilter = (field, leafOperand, parentPath = undefined)=>{
1258
+ if (!field.includes('.')) {
1259
+ return leafOperand;
1260
+ }
1261
+ const splitFieldArray = field.split('.');
1262
+ const nextField = splitFieldArray.shift();
1263
+ if (!nextField) {
1264
+ console.warn('Invalid field path:', field);
1265
+ return leafOperand;
1266
+ }
1267
+ const currentPath = parentPath ? `${parentPath}.${nextField}` : nextField;
1268
+ return {
1269
+ nested: {
1270
+ path: currentPath,
1271
+ ...buildNestedGQLFilter(splitFieldArray.join('.'), leafOperand, currentPath)
1272
+ }
1273
+ };
1274
+ };
1618
1275
 
1619
1276
  const isFilterSet = (input)=>{
1620
1277
  if (typeof input !== 'object' || input === null) {
@@ -1638,9 +1295,17 @@ const isUnion = (value)=>{
1638
1295
  const isIntersection = (value)=>{
1639
1296
  return typeof value === 'object' && value !== null && value.operator === 'and' && Array.isArray(value.operands);
1640
1297
  };
1298
+ /**
1299
+ * Type guard for Union or Intersection
1300
+ * @param o - operator to check
1301
+ * @category Filters
1302
+ */ const isIntersectionOrUnion = (o)=>o.operator === 'and' || o.operator === 'or';
1641
1303
  const isOperandsType = (operation)=>{
1642
1304
  return operation?.operands !== undefined;
1643
1305
  };
1306
+ const isNestedFilter = (operation)=>{
1307
+ return operation.operator === 'nested';
1308
+ };
1644
1309
  const isIndexedFilterSetEmpty = (filters)=>Object.values(filters).every((filterSet)=>Object.keys(filterSet).length === 0);
1645
1310
  const EmptyFilterSet = {
1646
1311
  mode: 'and',
@@ -1669,7 +1334,7 @@ const COMMON_PREPOSITIONS = [
1669
1334
  'up',
1670
1335
  'yet'
1671
1336
  ];
1672
- const capitalize = (s)=>s.length > 0 ? s[0].toUpperCase() + s.slice(1) : '';
1337
+ const capitalize$1 = (s)=>s.length > 0 ? s[0].toUpperCase() + s.slice(1) : '';
1673
1338
  const trimFirstFieldNameToTitle = (fieldName, trim = false)=>{
1674
1339
  if (trim) {
1675
1340
  const source = fieldName.slice(fieldName.indexOf('.') + 1);
@@ -1687,28 +1352,459 @@ const trimFirstFieldNameToTitle = (fieldName, trim = false)=>{
1687
1352
  if (fieldName in FieldNameOverrides) {
1688
1353
  return FieldNameOverrides[fieldName];
1689
1354
  }
1690
- if (fieldName === undefined) return 'No Title';
1691
- return fieldName.split('.').slice(-sections).map((s)=>s.split('_')).flat().map((word)=>COMMON_PREPOSITIONS.includes(word) ? word : capitalize(word)).join(' ');
1692
- };
1693
- /**
1694
- * Extracts the index name from the field name
1695
- * @param fieldName
1696
- */ const extractIndexFromFullFieldName = (fieldName)=>fieldName.split('.')[0];
1697
- /**
1698
- * prepend the index name to the field name
1699
- */ const prependIndexToFieldName = (fieldName, index)=>`${index}.${fieldName}`;
1700
- /**
1701
- * extract the field name from the index.field name
1702
- */ const extractFieldNameFromFullFieldName = (fieldName)=>fieldName.split('.').slice(1).join('.');
1703
- /**
1704
- * extract the field name and the index from the index.field name returning as a tuple
1705
- */ const extractIndexAndFieldNameFromFullFieldName = (fieldName)=>{
1706
- const [index, ...rest] = fieldName.split('.');
1707
- return [
1708
- index,
1709
- rest.join('.')
1355
+ if (fieldName === undefined) return 'No Title';
1356
+ return fieldName.split('.').slice(-sections).map((s)=>s.split('_')).flat().map((word)=>COMMON_PREPOSITIONS.includes(word) ? word : capitalize$1(word)).join(' ');
1357
+ };
1358
+ /**
1359
+ * Extracts the index name from the field name
1360
+ * @param fieldName
1361
+ */ const extractIndexFromFullFieldName = (fieldName)=>fieldName.split('.')[0];
1362
+ /**
1363
+ * prepend the index name to the field name
1364
+ */ const prependIndexToFieldName = (fieldName, index)=>`${index}.${fieldName}`;
1365
+ /**
1366
+ * extract the field name from the index.field name
1367
+ */ const extractFieldNameFromFullFieldName = (fieldName)=>fieldName.split('.').slice(1).join('.');
1368
+ /**
1369
+ * extract the field name and the index from the index.field name returning as a tuple
1370
+ */ const extractIndexAndFieldNameFromFullFieldName = (fieldName)=>{
1371
+ const [index, ...rest] = fieldName.split('.');
1372
+ return [
1373
+ index,
1374
+ rest.join('.')
1375
+ ];
1376
+ };
1377
+
1378
+ const defaultCohortNameGenerator = ()=>`Custom cohort ${new Date().toLocaleString('en-CA', {
1379
+ timeZone: 'America/Chicago',
1380
+ hour12: false
1381
+ }).replace(',', '')}`;
1382
+ const isNameUnique = (entities, name, excludeId)=>{
1383
+ const trimmedName = name.trim();
1384
+ if (!trimmedName) return false;
1385
+ return !entities.some((cohort)=>cohort && cohort.id !== excludeId && cohort.name.trim().toLowerCase() === trimmedName.toLowerCase());
1386
+ };
1387
+ const generateUniqueName = (entities, baseName)=>{
1388
+ const trimmedBaseName = baseName.trim();
1389
+ // If base name is unique, use it
1390
+ if (isNameUnique(entities, trimmedBaseName)) {
1391
+ return trimmedBaseName;
1392
+ }
1393
+ // Find a unique name by appending numbers
1394
+ let counter = 1;
1395
+ let uniqueName;
1396
+ do {
1397
+ uniqueName = `${trimmedBaseName} (${counter})`;
1398
+ counter++;
1399
+ }while (!isNameUnique(entities, uniqueName))
1400
+ return uniqueName;
1401
+ };
1402
+ /**
1403
+ * This function takes a FilterSet object and a prefix string as input.
1404
+ * It filters the root property of the FilterSet object and returns a
1405
+ * new FilterSet object that only contains filters with field names
1406
+ * that start with the specified prefix.
1407
+ *
1408
+ * @param fs - The FilterSet object to filter
1409
+ * @param prefix - The prefix to filter by
1410
+ * @returns - A new FilterSet object that only contains filters with field names that start with the specified prefix
1411
+ * @category Filters
1412
+ */ const extractFiltersWithPrefixFromFilterSet = (fs, prefix)=>{
1413
+ if (fs === undefined || fs.root === undefined) {
1414
+ return {
1415
+ mode: 'and',
1416
+ root: {}
1417
+ };
1418
+ }
1419
+ return Object.values(fs.root).reduce((acc, filter)=>{
1420
+ if (isIntersectionOrUnion(filter) || isNestedFilter(filter)) return acc;
1421
+ if (filter.field.startsWith(prefix)) {
1422
+ acc.root[filter.field] = filter;
1423
+ }
1424
+ return acc;
1425
+ }, {
1426
+ mode: 'and',
1427
+ root: {}
1428
+ });
1429
+ };
1430
+
1431
+ /**
1432
+ * Cohorts in Gen3 are defined as a set of filters for each index in the data.
1433
+ * This means one cohort id defined for all "tabs" in CohortBuilder (explorer)
1434
+ * Switching a cohort id means all the cohorts for the index are changed.
1435
+ */ const DEFAULT_COHORT_NAME = 'Cohort';
1436
+ const newCohort = ({ filters = {}, customName })=>{
1437
+ const ts = new Date().toISOString();
1438
+ const newName = customName ?? defaultCohortNameGenerator();
1439
+ const newId = createCohortId();
1440
+ return {
1441
+ name: newName,
1442
+ id: newId,
1443
+ filters: filters ?? {},
1444
+ modified: false,
1445
+ saved: false,
1446
+ createdDatetime: ts,
1447
+ modifiedDatetime: ts,
1448
+ counts: {}
1449
+ };
1450
+ };
1451
+ const nanoid = nanoid$1.customAlphabet('1234567890abcdef', 16);
1452
+ const createCohortId = ()=>nanoid();
1453
+ const cohortsAdapter = toolkit.createEntityAdapter({
1454
+ sortComparer: (a, b)=>{
1455
+ if (a.modifiedDatetime <= b.modifiedDatetime) return 1;
1456
+ else return -1;
1457
+ },
1458
+ selectId: (cohort)=>cohort.id
1459
+ });
1460
+ // Create an initial unsaved cohort
1461
+ const initialCohort = newCohort({
1462
+ customName: DEFAULT_COHORT_NAME
1463
+ });
1464
+ const emptyInitialState = cohortsAdapter.getInitialState({
1465
+ currentCohortId: initialCohort.id,
1466
+ message: undefined
1467
+ });
1468
+ // Set the initial cohort in the adapter state
1469
+ const initialState$3 = cohortsAdapter.setOne(emptyInitialState, initialCohort);
1470
+ const getCurrentCohortId = (state)=>state.currentCohortId;
1471
+ /**
1472
+ * Redux slice for cohort filters
1473
+ */ const cohortManagerSlice = toolkit.createSlice({
1474
+ name: 'cohort',
1475
+ initialState: initialState$3,
1476
+ reducers: {
1477
+ createNewCohort: (state, action)=>{
1478
+ const baseName = action.payload.name || `Cohort`;
1479
+ const uniqueName = generateUniqueName(Object.values(state.entities), baseName);
1480
+ const cohort = newCohort({
1481
+ filters: action.payload.filters,
1482
+ customName: uniqueName
1483
+ });
1484
+ cohortsAdapter.addOne(state, cohort);
1485
+ state.currentCohortId = cohort.id;
1486
+ },
1487
+ updateCohortName: (state, action)=>{
1488
+ const { id, name } = action.payload;
1489
+ cohortsAdapter.updateOne(state, {
1490
+ id: id,
1491
+ changes: {
1492
+ name: name,
1493
+ modified: true,
1494
+ modifiedDatetime: new Date().toISOString()
1495
+ }
1496
+ });
1497
+ },
1498
+ removeCohort: (state, action)=>{
1499
+ const { id: cohortId } = action.payload;
1500
+ const removedCohortName = state.entities[cohortId].name;
1501
+ const totalCohorts = Object.keys(state.entities).length;
1502
+ if (totalCohorts <= 1) {
1503
+ cohortsAdapter.removeAll(state);
1504
+ const defaultCohort = newCohort({
1505
+ filters: {},
1506
+ customName: DEFAULT_COHORT_NAME
1507
+ });
1508
+ cohortsAdapter.addOne(state, defaultCohort);
1509
+ state.currentCohortId = defaultCohort.id;
1510
+ if (action?.payload.shouldShowMessage) {
1511
+ state.message = [
1512
+ `deleteCohort|${removedCohortName}|${state.currentCohortId}`
1513
+ ];
1514
+ }
1515
+ return;
1516
+ }
1517
+ cohortsAdapter.removeOne(state, cohortId);
1518
+ // deleted the current cohort so set to the most recent cohort
1519
+ if (state.currentCohortId === cohortId) {
1520
+ const remainingIds = Object.keys(state.entities);
1521
+ state.currentCohortId = remainingIds[0];
1522
+ }
1523
+ if (action?.payload.shouldShowMessage) {
1524
+ state.message = [
1525
+ `deleteCohort|${removedCohortName}|${state.currentCohortId}`
1526
+ ];
1527
+ }
1528
+ },
1529
+ // adds a filter to the cohort filter set at the given index
1530
+ updateCohortFilter: (state, action)=>{
1531
+ const { index, field, filter } = action.payload;
1532
+ const currentCohortId = getCurrentCohortId(state);
1533
+ if (!state.entities[currentCohortId]) {
1534
+ return;
1535
+ }
1536
+ cohortsAdapter.updateOne(state, {
1537
+ id: currentCohortId,
1538
+ changes: {
1539
+ filters: {
1540
+ ...state.entities[currentCohortId].filters,
1541
+ [index]: {
1542
+ mode: state.entities[currentCohortId]?.filters[index]?.mode ?? 'and',
1543
+ root: {
1544
+ ...state.entities[currentCohortId]?.filters[index]?.root ?? {},
1545
+ [field]: filter
1546
+ }
1547
+ }
1548
+ },
1549
+ modified: true,
1550
+ modifiedDatetime: new Date().toISOString()
1551
+ }
1552
+ });
1553
+ },
1554
+ setCohortFilter: (state, action)=>{
1555
+ const { index, filters } = action.payload;
1556
+ const currentCohortId = getCurrentCohortId(state);
1557
+ if (!state.entities[currentCohortId]) {
1558
+ console.error(`no cohort with id=${currentCohortId} defined`);
1559
+ return;
1560
+ }
1561
+ cohortsAdapter.updateOne(state, {
1562
+ id: currentCohortId,
1563
+ changes: {
1564
+ filters: {
1565
+ ...state.entities[currentCohortId].filters,
1566
+ [index]: filters
1567
+ },
1568
+ modified: true,
1569
+ modifiedDatetime: new Date().toISOString()
1570
+ }
1571
+ });
1572
+ },
1573
+ setCohortIndexFilters: (state, action)=>{
1574
+ const currentCohortId = getCurrentCohortId(state);
1575
+ if (!state.entities[currentCohortId]) {
1576
+ console.error(`no cohort with id=${currentCohortId} defined`);
1577
+ return;
1578
+ }
1579
+ cohortsAdapter.updateOne(state, {
1580
+ id: currentCohortId,
1581
+ changes: {
1582
+ filters: action.payload.filters,
1583
+ modified: true,
1584
+ modifiedDatetime: new Date().toISOString()
1585
+ }
1586
+ });
1587
+ },
1588
+ // removes a filter to the cohort filter set at the given index
1589
+ removeCohortFilter: (state, action)=>{
1590
+ const { index, field } = action.payload;
1591
+ const currentCohortId = getCurrentCohortId(state);
1592
+ if (!state.entities[currentCohortId]) {
1593
+ console.error(`no cohort with id=${currentCohortId} defined`);
1594
+ return;
1595
+ }
1596
+ const filters = state.entities[currentCohortId]?.filters[index]?.root;
1597
+ if (!filters) {
1598
+ return;
1599
+ }
1600
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1601
+ const { [field]: _a, ...updated } = filters;
1602
+ cohortsAdapter.updateOne(state, {
1603
+ id: currentCohortId,
1604
+ changes: {
1605
+ filters: {
1606
+ ...state.entities[currentCohortId]?.filters,
1607
+ [index]: {
1608
+ mode: state.entities[currentCohortId].filters[index].mode,
1609
+ root: updated
1610
+ }
1611
+ },
1612
+ modified: true,
1613
+ modifiedDatetime: new Date().toISOString()
1614
+ }
1615
+ });
1616
+ },
1617
+ duplicateCohort: (state)=>{
1618
+ const currentCohortId = getCurrentCohortId(state);
1619
+ const currentCohort = state.entities[currentCohortId];
1620
+ const newName = generateUniqueName(Object.values(state.entities), currentCohort.name);
1621
+ const duplicatedCohort = newCohort({
1622
+ filters: {
1623
+ ...currentCohort.filters
1624
+ },
1625
+ customName: newName
1626
+ });
1627
+ cohortsAdapter.addOne(state, {
1628
+ ...duplicatedCohort,
1629
+ counts: {
1630
+ ...currentCohort.counts
1631
+ }
1632
+ });
1633
+ state.currentCohortId = duplicatedCohort.id;
1634
+ },
1635
+ // removes all filters from the cohort filter set at the given index
1636
+ clearCohortFilters: (state, action)=>{
1637
+ const { index } = action.payload;
1638
+ const currentCohortId = getCurrentCohortId(state);
1639
+ if (!state.entities[currentCohortId]) {
1640
+ console.error(`no cohort with id=${currentCohortId} defined`);
1641
+ return;
1642
+ }
1643
+ const filters = state.entities[currentCohortId]?.filters[index]?.root;
1644
+ if (!filters) {
1645
+ return;
1646
+ }
1647
+ cohortsAdapter.updateOne(state, {
1648
+ id: currentCohortId,
1649
+ changes: {
1650
+ filters: {
1651
+ ...state.entities[currentCohortId]?.filters,
1652
+ [index]: {
1653
+ mode: 'and',
1654
+ root: {}
1655
+ }
1656
+ },
1657
+ modified: true,
1658
+ modifiedDatetime: new Date().toISOString()
1659
+ }
1660
+ });
1661
+ },
1662
+ updateCohortCounts: (state, action)=>{
1663
+ const currentCohortId = getCurrentCohortId(state);
1664
+ const currentCohort = state.entities[currentCohortId];
1665
+ cohortsAdapter.updateOne(state, {
1666
+ id: currentCohortId,
1667
+ changes: {
1668
+ counts: {
1669
+ ...currentCohort.counts,
1670
+ ...action.payload
1671
+ }
1672
+ }
1673
+ });
1674
+ },
1675
+ updateCohortIndexCountById: (state, action)=>{
1676
+ const { index, cohortId, counts } = action.payload;
1677
+ const cohort = state.entities[cohortId];
1678
+ cohortsAdapter.updateOne(state, {
1679
+ id: cohortId,
1680
+ changes: {
1681
+ counts: {
1682
+ ...cohort.counts,
1683
+ ...{
1684
+ [index]: counts
1685
+ }
1686
+ }
1687
+ }
1688
+ });
1689
+ },
1690
+ setCurrentCohortId: (state, action)=>{
1691
+ state.currentCohortId = action.payload;
1692
+ },
1693
+ /** @hidden */ setCohortList: (state, action)=>{
1694
+ if (!action.payload) {
1695
+ cohortsAdapter.removeMany(state, state.ids);
1696
+ } else {
1697
+ cohortsAdapter.upsertMany(state, [
1698
+ ...action.payload
1699
+ ]);
1700
+ }
1701
+ }
1702
+ }
1703
+ });
1704
+ /**
1705
+ * Returns the selectors for the cohorts EntityAdapter
1706
+ * @param state - the CoreState
1707
+ *
1708
+ * @hidden
1709
+ */ const cohortSelectors = cohortsAdapter.getSelectors((state)=>state.cohorts.cohortManager);
1710
+ // Filter actions: addFilter, removeFilter, updateFilter
1711
+ const { createNewCohort, updateCohortFilter, setCohortFilter, setCohortIndexFilters, duplicateCohort, removeCohortFilter, clearCohortFilters, removeCohort, setCurrentCohortId, updateCohortName, updateCohortCounts, updateCohortIndexCountById, setCohortList } = cohortManagerSlice.actions;
1712
+ const cohortReducer = cohortManagerSlice.reducer;
1713
+
1714
+ const initialState$2 = {};
1715
+ const expandSlice$1 = toolkit.createSlice({
1716
+ name: 'CohortBuilder/filterExpand',
1717
+ initialState: initialState$2,
1718
+ reducers: {
1719
+ toggleCohortBuilderCategoryFilter: (state, action)=>{
1720
+ return {
1721
+ ...state,
1722
+ [action.payload.index]: {
1723
+ ...state[action.payload.index],
1724
+ [action.payload.field]: action.payload.expanded
1725
+ }
1726
+ };
1727
+ },
1728
+ toggleCohortBuilderAllFilters: (state, action)=>{
1729
+ return {
1730
+ ...state,
1731
+ [action.payload.index]: Object.keys(state[action.payload.index]).reduce((acc, k)=>{
1732
+ acc[k] = action.payload.expand;
1733
+ return acc;
1734
+ }, {})
1735
+ };
1736
+ }
1737
+ }
1738
+ });
1739
+ const cohortBuilderFiltersExpandedReducer = expandSlice$1.reducer;
1740
+ const { toggleCohortBuilderCategoryFilter, toggleCohortBuilderAllFilters } = expandSlice$1.actions;
1741
+ const selectCohortFilterExpanded = (state, index, field)=>state.cohorts.filtersExpanded?.[index]?.[field];
1742
+ const selectAllCohortFiltersCollapsed = (state, index)=>index in state.cohorts.filtersExpanded ? Object.values(state.cohorts.filtersExpanded?.[index]).every((e)=>!e) : false;
1743
+
1744
+ const initialState$1 = {};
1745
+ const expandSlice = toolkit.createSlice({
1746
+ name: 'CohortBuilder/filterCombineMode',
1747
+ initialState: initialState$1,
1748
+ reducers: {
1749
+ setCohortFilterCombineMode: (state, action)=>{
1750
+ return {
1751
+ ...state,
1752
+ [action.payload.index]: {
1753
+ ...state[action.payload.index],
1754
+ [action.payload.field]: action.payload.mode
1755
+ }
1756
+ };
1757
+ }
1758
+ }
1759
+ });
1760
+ const cohortBuilderFiltersCombineModeReducer = expandSlice.reducer;
1761
+ const { setCohortFilterCombineMode } = expandSlice.actions;
1762
+ const selectCohortFilterCombineMode = (state, index, field)=>state.cohorts.filtersCombineMode?.[index]?.[field] ?? 'or';
1763
+
1764
+ const initialState = {
1765
+ shouldShareFilters: false,
1766
+ sharedFiltersMap: {}
1767
+ };
1768
+ const cohortSharedFiltersSlice = toolkit.createSlice({
1769
+ name: 'cohortSharedFilters',
1770
+ initialState: initialState,
1771
+ reducers: {
1772
+ setShouldShareFilters: (state, action)=>{
1773
+ state.shouldShareFilters = action.payload;
1774
+ return state;
1775
+ },
1776
+ setSharedFilters: (state, action)=>{
1777
+ state.sharedFiltersMap = action.payload;
1778
+ }
1779
+ }
1780
+ });
1781
+ const selectShouldShareFilters = (state)=>state.cohorts.sharedFilters.shouldShareFilters;
1782
+ const selectSharedFilters = (state)=>state.cohorts.sharedFilters.sharedFiltersMap;
1783
+ const selectSharedFiltersForFields = (state, field)=>state.cohorts.sharedFilters.sharedFiltersMap?.[field] ?? [
1784
+ field
1710
1785
  ];
1711
- };
1786
+ const { setShouldShareFilters, setSharedFilters } = cohortSharedFiltersSlice.actions;
1787
+ const cohortSharedFiltersReducer = cohortSharedFiltersSlice.reducer;
1788
+
1789
+ const cohortReducers = toolkit.combineReducers({
1790
+ filtersExpanded: cohortBuilderFiltersExpandedReducer,
1791
+ filtersCombineMode: cohortBuilderFiltersCombineModeReducer,
1792
+ sharedFilters: cohortSharedFiltersReducer,
1793
+ cohortManager: cohortReducer
1794
+ });
1795
+
1796
+ const rootReducer = toolkit.combineReducers({
1797
+ gen3Services: gen3ServicesReducer,
1798
+ user: userReducer,
1799
+ gen3Apps: gen3AppReducer,
1800
+ drsHostnames: drsHostnamesReducer,
1801
+ modals: modalReducer,
1802
+ cohorts: cohortReducers,
1803
+ activeWorkspace: activeWorkspaceReducer,
1804
+ [guppyApiSliceReducerPath]: guppyApiReducer,
1805
+ [userAuthApiReducerPath]: userAuthApiReducer,
1806
+ [cartReducerPath]: cartReducer
1807
+ });
1712
1808
 
1713
1809
  /**
1714
1810
  * Flattens a deep nested JSON object skipping
@@ -1870,14 +1966,22 @@ function isHttpStatusError(error) {
1870
1966
  * @param {string} csrfToken - The CSRF token to include in the request headers.
1871
1967
  * @returns {FetchConfig} - The prepared fetch configuration object.
1872
1968
  */ const prepareFetchConfig = (parameters, csrfToken)=>{
1969
+ const headers = new Headers({
1970
+ Accept: 'application/json',
1971
+ 'Content-Type': 'application/json',
1972
+ ...csrfToken !== undefined && {
1973
+ 'X-CSRF-Token': csrfToken
1974
+ }
1975
+ });
1976
+ if (process.env.NODE_ENV === 'development') {
1977
+ // NOTE: This cookie can only be accessed from the client side
1978
+ // in development mode. Otherwise, the cookie is set as httpOnly
1979
+ const accessToken = cookiesNext.getCookie('credentials_token');
1980
+ if (accessToken) headers.set('Authorization', `Bearer ${accessToken}`);
1981
+ }
1873
1982
  return {
1874
1983
  method: 'POST',
1875
- headers: {
1876
- 'Content-Type': 'application/json',
1877
- ...csrfToken !== undefined && {
1878
- 'X-CSRF-Token': csrfToken
1879
- }
1880
- },
1984
+ headers: headers,
1881
1985
  body: JSON.stringify({
1882
1986
  type: parameters.type,
1883
1987
  filter: convertFilterSetToGqlFilter(parameters.filter),
@@ -2062,6 +2166,88 @@ const groupSharedFields = (data)=>{
2062
2166
  return data;
2063
2167
  };
2064
2168
 
2169
+ const customQueryStrForField = (field, query, depth = 0)=>{
2170
+ const indent = ' '.repeat(depth);
2171
+ const splittedFieldArray = field.split('.');
2172
+ const splittedField = splittedFieldArray.shift();
2173
+ if (splittedFieldArray.length === 0) {
2174
+ return `${indent}${splittedField} ${query}`;
2175
+ }
2176
+ return `${indent}${splittedField} {
2177
+ ${customQueryStrForField(splittedFieldArray.join('.'), query, depth + 1)}
2178
+ ${indent}}`;
2179
+ };
2180
+ // TODO: refactor the function below using customQueryStrForEachField and a wrapper function that passes the query
2181
+ const histogramQueryStrForEachField = (field)=>{
2182
+ const splittedFieldArray = field.split('.');
2183
+ const splittedField = splittedFieldArray.shift();
2184
+ if (splittedFieldArray.length === 0) {
2185
+ return `
2186
+ ${splittedField} {
2187
+ histogram {
2188
+ key
2189
+ count
2190
+ }
2191
+ }`;
2192
+ }
2193
+ return `
2194
+ ${splittedField} {
2195
+ ${histogramQueryStrForEachField(splittedFieldArray.join('.'))}
2196
+ }`;
2197
+ };
2198
+ const statsQueryStrForEachField = (field)=>{
2199
+ const splittedFieldArray = field.split('.');
2200
+ const splittedField = splittedFieldArray.shift();
2201
+ if (splittedFieldArray.length === 0) {
2202
+ return `
2203
+ ${splittedField} {
2204
+ histogram {
2205
+ count
2206
+ min
2207
+ max
2208
+ avg
2209
+ sum
2210
+ }
2211
+ }`;
2212
+ }
2213
+ return `
2214
+ ${splittedField} {
2215
+ ${statsQueryStrForEachField(splittedFieldArray.join('.'))}
2216
+ }`;
2217
+ };
2218
+ const nestedHistogramQueryStrForEachField = (mainField, numericAggAsText)=>`
2219
+ ${mainField} {
2220
+ ${numericAggAsText ? 'asTextHistogram' : 'histogram'} {
2221
+ key
2222
+ count
2223
+ missingFields {
2224
+ field
2225
+ count
2226
+ }
2227
+ termsFields {
2228
+ field
2229
+ count
2230
+ terms {
2231
+ key
2232
+ count
2233
+ }
2234
+ }
2235
+ }
2236
+ }`;
2237
+ const rawDataQueryStrForEachField = (field)=>{
2238
+ const splitFieldArray = field.split('.');
2239
+ const splitField = splitFieldArray.shift();
2240
+ if (splitFieldArray.length === 0) {
2241
+ return `
2242
+ ${splitField}
2243
+ `;
2244
+ }
2245
+ return `
2246
+ ${splitField} {
2247
+ ${rawDataQueryStrForEachField(splitFieldArray.join('.'))}
2248
+ }`;
2249
+ };
2250
+
2065
2251
  const statusEndpoint = '/_status';
2066
2252
  const fetchJson = async (url)=>{
2067
2253
  const res = await fetch(url, {
@@ -2342,75 +2528,6 @@ const explorerTags = guppyApi.enhanceEndpoints({
2342
2528
  })
2343
2529
  })
2344
2530
  });
2345
- const histogramQueryStrForEachField = (field)=>{
2346
- const splittedFieldArray = field.split('.');
2347
- const splittedField = splittedFieldArray.shift();
2348
- if (splittedFieldArray.length === 0) {
2349
- return `
2350
- ${splittedField} {
2351
- histogram {
2352
- key
2353
- count
2354
- }
2355
- }`;
2356
- }
2357
- return `
2358
- ${splittedField} {
2359
- ${histogramQueryStrForEachField(splittedFieldArray.join('.'))}
2360
- }`;
2361
- };
2362
- const statsQueryStrForEachField = (field)=>{
2363
- const splittedFieldArray = field.split('.');
2364
- const splittedField = splittedFieldArray.shift();
2365
- if (splittedFieldArray.length === 0) {
2366
- return `
2367
- ${splittedField} {
2368
- histogram {
2369
- count
2370
- min
2371
- max
2372
- avg
2373
- sum
2374
- }
2375
- }`;
2376
- }
2377
- return `
2378
- ${splittedField} {
2379
- ${statsQueryStrForEachField(splittedFieldArray.join('.'))}
2380
- }`;
2381
- };
2382
- const nestedHistogramQueryStrForEachField = (mainField, numericAggAsText)=>`
2383
- ${mainField} {
2384
- ${numericAggAsText ? 'asTextHistogram' : 'histogram'} {
2385
- key
2386
- count
2387
- missingFields {
2388
- field
2389
- count
2390
- }
2391
- termsFields {
2392
- field
2393
- count
2394
- terms {
2395
- key
2396
- count
2397
- }
2398
- }
2399
- }
2400
- }`;
2401
- const rawDataQueryStrForEachField = (field)=>{
2402
- const splitFieldArray = field.split('.');
2403
- const splitField = splitFieldArray.shift();
2404
- if (splitFieldArray.length === 0) {
2405
- return `
2406
- ${splitField}
2407
- `;
2408
- }
2409
- return `
2410
- ${splitField} {
2411
- ${rawDataQueryStrForEachField(splitFieldArray.join('.'))}
2412
- }`;
2413
- };
2414
2531
  const useGetArrayTypes = ()=>{
2415
2532
  {
2416
2533
  const { data, error } = useGetStatus();
@@ -2514,7 +2631,8 @@ const persistConfig = {
2514
2631
  storage,
2515
2632
  whitelist: [
2516
2633
  'cohorts',
2517
- 'activeWorkspace'
2634
+ 'activeWorkspace',
2635
+ 'cart'
2518
2636
  ]
2519
2637
  };
2520
2638
  const persistedReducer = reduxPersist.persistReducer(persistConfig, rootReducer);
@@ -2762,6 +2880,28 @@ const fetchFencePresignedURL = async ({ guid, method = 'GET', onAbort = ()=>null
2762
2880
  return await response.json();
2763
2881
  };
2764
2882
 
2883
+ const extractValuesFromObject = (jsonPathMappings, obj)=>{
2884
+ const result = {};
2885
+ const extractObjectValue = (jsonPath, obj)=>{
2886
+ const extractedValues = jsonpathPlus.JSONPath({
2887
+ path: jsonPath,
2888
+ json: obj
2889
+ });
2890
+ return extractedValues.length > 0 ? extractedValues[0] : undefined;
2891
+ };
2892
+ for(const key in jsonPathMappings){
2893
+ if (key in Object.keys(jsonPathMappings)) {
2894
+ // Extract value from an object and store it in the result.
2895
+ result[key] = extractObjectValue(jsonPathMappings[key], obj);
2896
+ }
2897
+ }
2898
+ return result;
2899
+ };
2900
+ const ExtractValueFromObject = (obj, key, valueIfNotFound)=>{
2901
+ return obj?.[key] ?? valueIfNotFound;
2902
+ };
2903
+
2904
+ const DAYS_IN_YEAR = 365.25;
2765
2905
  /**
2766
2906
  * Converts HistogramData to HistogramDataAsStringKey by ensuring the key is a string.
2767
2907
  * If the key is already a string, it's used as is.
@@ -2777,6 +2917,79 @@ const fetchFencePresignedURL = async ({ guid, method = 'GET', onAbort = ()=>null
2777
2917
  };
2778
2918
  const calculatePercentageAsNumber = (count, total)=>count ? count / total * 100 : 0;
2779
2919
  const calculatePercentageAsString = (count, total)=>`${(count / total * 100).toFixed(2)}%`;
2920
+ const capitalize = (original)=>{
2921
+ const customCapitalizations = {
2922
+ id: 'ID',
2923
+ uuid: 'UUID',
2924
+ dna: 'DNA',
2925
+ dbsnp: 'dbSNP',
2926
+ cosmic: 'COSMIC',
2927
+ civic: 'CIViC',
2928
+ dbgap: 'dbGaP',
2929
+ ecog: 'ECOG',
2930
+ bmi: 'BMI',
2931
+ gdc: 'GDC',
2932
+ cnv: 'CNV',
2933
+ ssm: 'SSM',
2934
+ aa: 'AA'
2935
+ };
2936
+ return original.split(' ').map((word)=>customCapitalizations[word.toLowerCase()] || `${word.charAt(0).toUpperCase()}${word.slice(1)}`).join(' ');
2937
+ };
2938
+ const humanify = ({ term = '', capitalize: cap = true, facetTerm = false })=>{
2939
+ let original;
2940
+ let humanified;
2941
+ if (facetTerm) {
2942
+ // Splits on capital letters followed by lowercase letters to find
2943
+ // words squished together in a string.
2944
+ original = term?.split(/(?=[A-Z][a-z])/).join(' ');
2945
+ humanified = term?.replace(/\./g, ' ').replace(/_/g, ' ').trim();
2946
+ } else {
2947
+ const split = (original || term)?.split('.');
2948
+ humanified = split[split.length - 1]?.replace(/_/g, ' ').trim();
2949
+ // Special case 'name' to include any parent nested for sake of
2950
+ // specificity in the UI
2951
+ if (humanified === 'name' && split?.length > 1) {
2952
+ humanified = `${split[split?.length - 2]} ${humanified}`;
2953
+ }
2954
+ }
2955
+ return cap ? capitalize(humanified) : humanified;
2956
+ };
2957
+ /*https://github.com/NCI-GDC/portal-ui/blob/develop/src/packages/%40ncigdc/utils/ageDisplay.js*/ /**
2958
+ * Converts age in days into a human-readable format.
2959
+ *
2960
+ * @param ageInDays - The age in days.
2961
+ * @param yearsOnly - If true, only display years.
2962
+ * @defaultValue false
2963
+ * @param defaultValue - The default value to return if ageInDays is falsy.
2964
+ * @defaultValue "--"
2965
+ * @returns The formatted age string.
2966
+ */ const ageDisplay = (ageInDays, yearsOnly = false, defaultValue = '--')=>{
2967
+ if (ageInDays !== 0 && !ageInDays) {
2968
+ return defaultValue;
2969
+ }
2970
+ const calculateYearsAndDays = (years, days)=>days === 365 ? [
2971
+ years + 1,
2972
+ 0
2973
+ ] : [
2974
+ years,
2975
+ days
2976
+ ];
2977
+ const ABS_AGE_DAYS = Math.abs(ageInDays);
2978
+ const [years, remainingDays] = calculateYearsAndDays(Math.floor(ABS_AGE_DAYS / DAYS_IN_YEAR), Math.ceil(ABS_AGE_DAYS % DAYS_IN_YEAR));
2979
+ const formattedYears = years === 0 ? '' : `${years} ${years === 1 ? 'year' : 'years'}`;
2980
+ const formattedDays = !yearsOnly && remainingDays > 0 ? `${remainingDays} ${remainingDays === 1 ? 'day' : 'days'}` : years === 0 && remainingDays === 0 ? '0 days' : '';
2981
+ const ageString = [
2982
+ formattedYears,
2983
+ formattedDays
2984
+ ].filter(Boolean).join(' ');
2985
+ return ageInDays >= 0 ? ageString : `-${ageString}`;
2986
+ };
2987
+ /**
2988
+ * Given an object of JSON, stringify it into a string.
2989
+ * @param obj - the object to stringify
2990
+ * @param defaults - the default value to return if the object is undefined
2991
+ * @category Utility
2992
+ */ const stringifyJSONParam = (obj, defaults = '{}')=>obj ? JSON.stringify(obj) : defaults;
2780
2993
 
2781
2994
  const queryWTSFederatedLoginStatus = async (signal)=>{
2782
2995
  try {
@@ -4768,7 +4981,7 @@ const { useGraphQLQuery } = graphQLAPI;
4768
4981
  return {
4769
4982
  url: `${GEN3_GUPPY_API}/download`,
4770
4983
  method: 'POST',
4771
- body: JSON.stringify(queryBody),
4984
+ body: queryBody,
4772
4985
  cache: 'no-cache'
4773
4986
  };
4774
4987
  },
@@ -5321,24 +5534,6 @@ const userHasMethodOnAnyProject = (method, userAuthMapping = {})=>{
5321
5534
  };
5322
5535
  const userHasCreateOrUpdateOnAnyProject = (userAuthMapping = {})=>userHasMethodOnAnyProject('create', userAuthMapping) || userHasMethodOnAnyProject('update', userAuthMapping);
5323
5536
 
5324
- const extractValuesFromObject = (jsonPathMappings, obj)=>{
5325
- const result = {};
5326
- const extractObjectValue = (jsonPath, obj)=>{
5327
- const extractedValues = jsonpathPlus.JSONPath({
5328
- path: jsonPath,
5329
- json: obj
5330
- });
5331
- return extractedValues.length > 0 ? extractedValues[0] : undefined;
5332
- };
5333
- for(const key in jsonPathMappings){
5334
- if (key in Object.keys(jsonPathMappings)) {
5335
- // Extract value from an object and store it in the result.
5336
- result[key] = extractObjectValue(jsonPathMappings[key], obj);
5337
- }
5338
- }
5339
- return result;
5340
- };
5341
-
5342
5537
  const SubmissionGraphqlQuery = `query transactionList {
5343
5538
  transactionList: transaction_log(last: 20) {
5344
5539
  id
@@ -5622,12 +5817,15 @@ const isWorkspaceActive = (status)=>status === WorkspaceStatus.Running || status
5622
5817
  const isWorkspaceRunningOrStopping = (status)=>status === WorkspaceStatus.Running || status === WorkspaceStatus.Terminating;
5623
5818
 
5624
5819
  exports.Accessibility = Accessibility;
5820
+ exports.CART_LIMIT = CART_LIMIT;
5625
5821
  exports.CohortStorage = CohortStorage;
5626
5822
  exports.CoreProvider = CoreProvider;
5823
+ exports.DAYS_IN_YEAR = DAYS_IN_YEAR;
5627
5824
  exports.DataLibraryStoreMode = DataLibraryStoreMode;
5628
5825
  exports.EmptyFilterSet = EmptyFilterSet;
5629
5826
  exports.EmptyWorkspaceStatusResponse = EmptyWorkspaceStatusResponse;
5630
5827
  exports.EnumValueExtractorHandler = EnumValueExtractorHandler;
5828
+ exports.ExtractValueFromObject = ExtractValueFromObject;
5631
5829
  exports.GEN3_API = GEN3_API;
5632
5830
  exports.GEN3_AUTHZ_API = GEN3_AUTHZ_API;
5633
5831
  exports.GEN3_COMMONS_NAME = GEN3_COMMONS_NAME;
@@ -5653,12 +5851,18 @@ exports.RequestedWorkspaceStatus = RequestedWorkspaceStatus;
5653
5851
  exports.ToGqlHandler = ToGqlHandler;
5654
5852
  exports.ValueExtractorHandler = ValueExtractorHandler;
5655
5853
  exports.WorkspaceStatus = WorkspaceStatus;
5854
+ exports.addItemsToCart = addItemsToCart;
5855
+ exports.ageDisplay = ageDisplay;
5656
5856
  exports.appendFilterToOperation = appendFilterToOperation;
5657
5857
  exports.buildGetAggregationQuery = buildGetAggregationQuery;
5658
5858
  exports.buildGetStatsAggregationQuery = buildGetStatsAggregationQuery;
5659
5859
  exports.buildListItemsGroupedByDataset = buildListItemsGroupedByDataset;
5860
+ exports.buildNestedGQLFilter = buildNestedGQLFilter;
5660
5861
  exports.calculatePercentageAsNumber = calculatePercentageAsNumber;
5661
5862
  exports.calculatePercentageAsString = calculatePercentageAsString;
5863
+ exports.capitalize = capitalize;
5864
+ exports.cartReducer = cartReducer;
5865
+ exports.cartReducerPath = cartReducerPath;
5662
5866
  exports.clearActiveWorkspaceId = clearActiveWorkspaceId;
5663
5867
  exports.clearCohortFilters = clearCohortFilters;
5664
5868
  exports.cohortReducer = cohortReducer;
@@ -5674,6 +5878,7 @@ exports.createGen3App = createGen3App;
5674
5878
  exports.createGen3AppWithOwnStore = createGen3AppWithOwnStore;
5675
5879
  exports.createNewCohort = createNewCohort;
5676
5880
  exports.createUseCoreDataHook = createUseCoreDataHook;
5881
+ exports.customQueryStrForField = customQueryStrForField;
5677
5882
  exports.defaultCohortNameGenerator = defaultCohortNameGenerator;
5678
5883
  exports.downloadFromGuppyToBlob = downloadFromGuppyToBlob;
5679
5884
  exports.downloadJSONDataFromGuppy = downloadJSONDataFromGuppy;
@@ -5685,6 +5890,7 @@ exports.extractEnumFilterValue = extractEnumFilterValue;
5685
5890
  exports.extractFieldNameFromFullFieldName = extractFieldNameFromFullFieldName;
5686
5891
  exports.extractFileDatasetsInRecords = extractFileDatasetsInRecords;
5687
5892
  exports.extractFilterValue = extractFilterValue;
5893
+ exports.extractFiltersWithPrefixFromFilterSet = extractFiltersWithPrefixFromFilterSet;
5688
5894
  exports.extractIndexAndFieldNameFromFullFieldName = extractIndexAndFieldNameFromFullFieldName;
5689
5895
  exports.extractIndexFromDataLibraryCohort = extractIndexFromDataLibraryCohort;
5690
5896
  exports.extractIndexFromFullFieldName = extractIndexFromFullFieldName;
@@ -5714,6 +5920,7 @@ exports.handleGqlOperation = handleGqlOperation;
5714
5920
  exports.handleOperation = handleOperation;
5715
5921
  exports.hideModal = hideModal;
5716
5922
  exports.histogramQueryStrForEachField = histogramQueryStrForEachField;
5923
+ exports.humanify = humanify;
5717
5924
  exports.isAdditionalDataItem = isAdditionalDataItem;
5718
5925
  exports.isArray = isArray;
5719
5926
  exports.isAuthenticated = isAuthenticated;
@@ -5741,10 +5948,12 @@ exports.isHistogramRangeData = isHistogramRangeData;
5741
5948
  exports.isHttpStatusError = isHttpStatusError;
5742
5949
  exports.isIndexedFilterSetEmpty = isIndexedFilterSetEmpty;
5743
5950
  exports.isIntersection = isIntersection;
5951
+ exports.isIntersectionOrUnion = isIntersectionOrUnion;
5744
5952
  exports.isJSONObject = isJSONObject;
5745
5953
  exports.isJSONValue = isJSONValue;
5746
5954
  exports.isJSONValueArray = isJSONValueArray;
5747
5955
  exports.isNameUnique = isNameUnique;
5956
+ exports.isNestedFilter = isNestedFilter;
5748
5957
  exports.isNotDefined = isNotDefined;
5749
5958
  exports.isObject = isObject;
5750
5959
  exports.isOperandsType = isOperandsType;
@@ -5774,6 +5983,7 @@ exports.rawDataQueryStrForEachField = rawDataQueryStrForEachField;
5774
5983
  exports.registerDefaultRemoteSupport = registerDefaultRemoteSupport;
5775
5984
  exports.removeCohort = removeCohort;
5776
5985
  exports.removeCohortFilter = removeCohortFilter;
5986
+ exports.removeItemsFromCart = removeItemsFromCart;
5777
5987
  exports.requestorApi = requestorApi;
5778
5988
  exports.resetUserState = resetUserState;
5779
5989
  exports.resourcePathFromProjectID = resourcePathFromProjectID;
@@ -5787,6 +5997,10 @@ exports.selectAvailableCohortByName = selectAvailableCohortByName;
5787
5997
  exports.selectAvailableCohorts = selectAvailableCohorts;
5788
5998
  exports.selectCSRFToken = selectCSRFToken;
5789
5999
  exports.selectCSRFTokenData = selectCSRFTokenData;
6000
+ exports.selectCart = selectCart;
6001
+ exports.selectCartCount = selectCartCount;
6002
+ exports.selectCartItem = selectCartItem;
6003
+ exports.selectCartItems = selectCartItems;
5790
6004
  exports.selectCohortById = selectCohortById;
5791
6005
  exports.selectCohortFilterCombineMode = selectCohortFilterCombineMode;
5792
6006
  exports.selectCohortFilterExpanded = selectCohortFilterExpanded;
@@ -5834,6 +6048,7 @@ exports.setShouldShareFilters = setShouldShareFilters;
5834
6048
  exports.setupCoreStore = setupCoreStore;
5835
6049
  exports.showModal = showModal;
5836
6050
  exports.statsQueryStrForEachField = statsQueryStrForEachField;
6051
+ exports.stringifyJSONParam = stringifyJSONParam;
5837
6052
  exports.submissionApi = submissionApi;
5838
6053
  exports.toggleCohortBuilderAllFilters = toggleCohortBuilderAllFilters;
5839
6054
  exports.toggleCohortBuilderCategoryFilter = toggleCohortBuilderCategoryFilter;