@gen3/core 0.11.33 → 0.11.35

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 (43) hide show
  1. package/dist/cjs/index.js +782 -710
  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/types.d.ts +7 -0
  22. package/dist/dts/features/filters/types.d.ts.map +1 -1
  23. package/dist/dts/features/guppy/guppySlice.d.ts +61 -14
  24. package/dist/dts/features/guppy/guppySlice.d.ts.map +1 -1
  25. package/dist/dts/features/user/userSliceRTK.d.ts +3 -0
  26. package/dist/dts/features/user/userSliceRTK.d.ts.map +1 -1
  27. package/dist/dts/hooks.d.ts +2 -0
  28. package/dist/dts/hooks.d.ts.map +1 -1
  29. package/dist/dts/index.d.ts +3 -2
  30. package/dist/dts/index.d.ts.map +1 -1
  31. package/dist/dts/reducers.d.ts +2 -0
  32. package/dist/dts/reducers.d.ts.map +1 -1
  33. package/dist/dts/server.d.ts +4 -0
  34. package/dist/dts/server.d.ts.map +1 -0
  35. package/dist/dts/store.d.ts +4 -0
  36. package/dist/dts/store.d.ts.map +1 -1
  37. package/dist/esm/index.js +771 -711
  38. package/dist/esm/index.js.map +1 -1
  39. package/dist/esm/server.js +29 -0
  40. package/dist/esm/server.js.map +1 -0
  41. package/dist/index.d.ts +433 -209
  42. package/dist/server.d.ts +31 -0
  43. package/package.json +7 -2
package/dist/cjs/index.js CHANGED
@@ -8,9 +8,9 @@ var reactRedux = require('react-redux');
8
8
  var React = require('react');
9
9
  var graphql = require('graphql');
10
10
  var jsonpathPlus = require('jsonpath-plus');
11
+ var lodash = require('lodash');
11
12
  var nanoid$1 = require('nanoid');
12
13
  var useSWR = require('swr');
13
- var lodash = require('lodash');
14
14
  var flat = require('flat');
15
15
  var Papa = require('papaparse');
16
16
  var reduxPersist = require('redux-persist');
@@ -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
@@ -829,578 +849,176 @@ const guppyAPISliceMiddleware = guppyApi.middleware;
829
849
  const guppyApiSliceReducerPath = guppyApi.reducerPath;
830
850
  const guppyApiReducer = guppyApi.reducer;
831
851
 
832
- const defaultCohortNameGenerator = ()=>`Custom cohort ${new Date().toLocaleString('en-CA', {
833
- timeZone: 'America/Chicago',
834
- hour12: false
835
- }).replace(',', '')}`;
836
- const isNameUnique = (entities, name, excludeId)=>{
837
- const trimmedName = name.trim();
838
- if (!trimmedName) return false;
839
- return !entities.some((cohort)=>cohort && cohort.id !== excludeId && cohort.name.trim().toLowerCase() === trimmedName.toLowerCase());
852
+ const isOperationWithField = (operation)=>{
853
+ return operation?.field !== undefined;
840
854
  };
841
- const generateUniqueName = (entities, baseName)=>{
842
- const trimmedBaseName = baseName.trim();
843
- // If base name is unique, use it
844
- if (isNameUnique(entities, trimmedBaseName)) {
845
- 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);
846
908
  }
847
- // Find a unique name by appending numbers
848
- let counter = 1;
849
- let uniqueName;
850
- do {
851
- uniqueName = `${trimmedBaseName} (${counter})`;
852
- counter++;
853
- }while (!isNameUnique(entities, uniqueName))
854
- return uniqueName;
855
909
  };
856
-
857
910
  /**
858
- * Cohorts in Gen3 are defined as a set of filters for each index in the data.
859
- * This means one cohort id defined for all "tabs" in CohortBuilder (explorer)
860
- * Switching a cohort id means all the cohorts for the index are changed.
861
- */ const DEFAULT_COHORT_NAME = 'Cohort';
862
- const newCohort = ({ filters = {}, customName })=>{
863
- const ts = new Date().toISOString();
864
- const newName = customName ?? defaultCohortNameGenerator();
865
- const newId = createCohortId();
866
- return {
867
- name: newName,
868
- id: newId,
869
- filters: filters ?? {},
870
- modified: false,
871
- saved: false,
872
- createdDatetime: ts,
873
- modifiedDatetime: ts,
874
- counts: {}
875
- };
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);
876
920
  };
877
- const nanoid = nanoid$1.customAlphabet('1234567890abcdef', 16);
878
- const createCohortId = ()=>nanoid();
879
- const cohortsAdapter = toolkit.createEntityAdapter({
880
- sortComparer: (a, b)=>{
881
- if (a.modifiedDatetime <= b.modifiedDatetime) return 1;
882
- else return -1;
883
- },
884
- selectId: (cohort)=>cohort.id
885
- });
886
- // Create an initial unsaved cohort
887
- const initialCohort = newCohort({
888
- customName: DEFAULT_COHORT_NAME
889
- });
890
- const emptyInitialState = cohortsAdapter.getInitialState({
891
- currentCohortId: initialCohort.id,
892
- message: undefined
893
- });
894
- // Set the initial cohort in the adapter state
895
- const initialState$3 = cohortsAdapter.setOne(emptyInitialState, initialCohort);
896
- const getCurrentCohortId = (state)=>state.currentCohortId;
897
921
  /**
898
- * Redux slice for cohort filters
899
- */ const cohortManagerSlice = toolkit.createSlice({
900
- name: 'cohort',
901
- initialState: initialState$3,
902
- reducers: {
903
- createNewCohort: (state, action)=>{
904
- const baseName = action.payload.name || `Cohort`;
905
- const uniqueName = generateUniqueName(Object.values(state.entities), baseName);
906
- const cohort = newCohort({
907
- filters: action.payload.filters,
908
- 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
+ }
909
934
  });
910
- cohortsAdapter.addOne(state, cohort);
911
- state.currentCohortId = cohort.id;
912
- },
913
- updateCohortName: (state, action)=>{
914
- const { id, name } = action.payload;
915
- cohortsAdapter.updateOne(state, {
916
- id: id,
917
- changes: {
918
- name: name,
919
- modified: true,
920
- modifiedDatetime: new Date().toISOString()
935
+ this.handleNotEquals = (op)=>({
936
+ '!=': {
937
+ [op.field]: op.operand
921
938
  }
922
939
  });
923
- },
924
- removeCohort: (state, action)=>{
925
- const { id: cohortId } = action.payload;
926
- const removedCohortName = state.entities[cohortId].name;
927
- const totalCohorts = Object.keys(state.entities).length;
928
- if (totalCohorts <= 1) {
929
- cohortsAdapter.removeAll(state);
930
- const defaultCohort = newCohort({
931
- filters: {},
932
- customName: DEFAULT_COHORT_NAME
933
- });
934
- cohortsAdapter.addOne(state, defaultCohort);
935
- state.currentCohortId = defaultCohort.id;
936
- if (action?.payload.shouldShowMessage) {
937
- state.message = [
938
- `deleteCohort|${removedCohortName}|${state.currentCohortId}`
939
- ];
940
+ this.handleLessThan = (op)=>({
941
+ '<': {
942
+ [op.field]: op.operand
940
943
  }
941
- return;
942
- }
943
- cohortsAdapter.removeOne(state, cohortId);
944
- // deleted the current cohort so set to the most recent cohort
945
- if (state.currentCohortId === cohortId) {
946
- const remainingIds = Object.keys(state.entities);
947
- state.currentCohortId = remainingIds[0];
948
- }
949
- if (action?.payload.shouldShowMessage) {
950
- state.message = [
951
- `deleteCohort|${removedCohortName}|${state.currentCohortId}`
952
- ];
953
- }
954
- },
955
- // adds a filter to the cohort filter set at the given index
956
- updateCohortFilter: (state, action)=>{
957
- const { index, field, filter } = action.payload;
958
- const currentCohortId = getCurrentCohortId(state);
959
- if (!state.entities[currentCohortId]) {
960
- return;
961
- }
962
- cohortsAdapter.updateOne(state, {
963
- id: currentCohortId,
964
- changes: {
965
- filters: {
966
- ...state.entities[currentCohortId].filters,
967
- [index]: {
968
- mode: state.entities[currentCohortId]?.filters[index]?.mode ?? 'and',
969
- root: {
970
- ...state.entities[currentCohortId]?.filters[index]?.root ?? {},
971
- [field]: filter
972
- }
973
- }
974
- },
975
- modified: true,
976
- modifiedDatetime: new Date().toISOString()
944
+ });
945
+ this.handleLessThanOrEquals = (op)=>({
946
+ '<=': {
947
+ [op.field]: op.operand
977
948
  }
978
949
  });
979
- },
980
- setCohortFilter: (state, action)=>{
981
- const { index, filters } = action.payload;
982
- const currentCohortId = getCurrentCohortId(state);
983
- if (!state.entities[currentCohortId]) {
984
- console.error(`no cohort with id=${currentCohortId} defined`);
985
- return;
986
- }
987
- cohortsAdapter.updateOne(state, {
988
- id: currentCohortId,
989
- changes: {
990
- filters: {
991
- ...state.entities[currentCohortId].filters,
992
- [index]: filters
993
- },
994
- modified: true,
995
- modifiedDatetime: new Date().toISOString()
950
+ this.handleGreaterThan = (op)=>({
951
+ '>': {
952
+ [op.field]: op.operand
996
953
  }
997
954
  });
998
- },
999
- setCohortIndexFilters: (state, action)=>{
1000
- const currentCohortId = getCurrentCohortId(state);
1001
- if (!state.entities[currentCohortId]) {
1002
- console.error(`no cohort with id=${currentCohortId} defined`);
1003
- return;
1004
- }
1005
- cohortsAdapter.updateOne(state, {
1006
- id: currentCohortId,
1007
- changes: {
1008
- filters: action.payload.filters,
1009
- modified: true,
1010
- modifiedDatetime: new Date().toISOString()
955
+ this.handleGreaterThanOrEquals = (op)=>({
956
+ '>=': {
957
+ [op.field]: op.operand
1011
958
  }
1012
959
  });
1013
- },
1014
- // removes a filter to the cohort filter set at the given index
1015
- removeCohortFilter: (state, action)=>{
1016
- const { index, field } = action.payload;
1017
- const currentCohortId = getCurrentCohortId(state);
1018
- if (!state.entities[currentCohortId]) {
1019
- console.error(`no cohort with id=${currentCohortId} defined`);
1020
- return;
1021
- }
1022
- const filters = state.entities[currentCohortId]?.filters[index]?.root;
1023
- if (!filters) {
1024
- return;
1025
- }
1026
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1027
- const { [field]: _a, ...updated } = filters;
1028
- cohortsAdapter.updateOne(state, {
1029
- id: currentCohortId,
1030
- changes: {
1031
- filters: {
1032
- ...state.entities[currentCohortId]?.filters,
1033
- [index]: {
1034
- mode: state.entities[currentCohortId].filters[index].mode,
1035
- root: updated
1036
- }
1037
- },
1038
- modified: true,
1039
- modifiedDatetime: new Date().toISOString()
960
+ this.handleIncludes = (op)=>({
961
+ in: {
962
+ [op.field]: op.operands
1040
963
  }
1041
964
  });
1042
- },
1043
- duplicateCohort: (state)=>{
1044
- const currentCohortId = getCurrentCohortId(state);
1045
- const currentCohort = state.entities[currentCohortId];
1046
- const newName = generateUniqueName(Object.values(state.entities), currentCohort.name);
1047
- const duplicatedCohort = newCohort({
1048
- filters: {
1049
- ...currentCohort.filters
1050
- },
1051
- customName: newName
1052
- });
1053
- cohortsAdapter.addOne(state, {
1054
- ...duplicatedCohort,
1055
- counts: {
1056
- ...currentCohort.counts
965
+ this.handleExcludes = (op)=>({
966
+ exclude: {
967
+ [op.field]: op.operands
1057
968
  }
1058
969
  });
1059
- state.currentCohortId = duplicatedCohort.id;
1060
- },
1061
- // removes all filters from the cohort filter set at the given index
1062
- clearCohortFilters: (state, action)=>{
1063
- const { index } = action.payload;
1064
- const currentCohortId = getCurrentCohortId(state);
1065
- if (!state.entities[currentCohortId]) {
1066
- console.error(`no cohort with id=${currentCohortId} defined`);
1067
- return;
1068
- }
1069
- const filters = state.entities[currentCohortId]?.filters[index]?.root;
1070
- if (!filters) {
1071
- return;
1072
- }
1073
- cohortsAdapter.updateOne(state, {
1074
- id: currentCohortId,
1075
- changes: {
1076
- filters: {
1077
- ...state.entities[currentCohortId]?.filters,
1078
- [index]: {
1079
- mode: 'and',
1080
- root: {}
1081
- }
1082
- },
1083
- modified: true,
1084
- modifiedDatetime: new Date().toISOString()
970
+ this.handleExcludeIfAny = (op)=>({
971
+ excludeifany: {
972
+ [op.field]: op.operands
1085
973
  }
1086
974
  });
1087
- },
1088
- updateCohortCounts: (state, action)=>{
1089
- const currentCohortId = getCurrentCohortId(state);
1090
- const currentCohort = state.entities[currentCohortId];
1091
- cohortsAdapter.updateOne(state, {
1092
- id: currentCohortId,
1093
- changes: {
1094
- counts: {
1095
- ...currentCohort.counts,
1096
- ...action.payload
1097
- }
1098
- }
975
+ this.handleIntersection = (op)=>({
976
+ and: op.operands.map((x)=>convertFilterToGqlFilter(x))
1099
977
  });
1100
- },
1101
- updateCohortIndexCountById: (state, action)=>{
1102
- const { index, cohortId, counts } = action.payload;
1103
- const cohort = state.entities[cohortId];
1104
- cohortsAdapter.updateOne(state, {
1105
- id: cohortId,
1106
- changes: {
1107
- counts: {
1108
- ...cohort.counts,
1109
- ...{
1110
- [index]: counts
1111
- }
1112
- }
978
+ this.handleUnion = (op)=>({
979
+ or: op.operands.map((x)=>convertFilterToGqlFilter(x))
980
+ });
981
+ this.handleMissing = (op)=>({
982
+ is: {
983
+ [op.field]: 'MISSING'
1113
984
  }
1114
985
  });
1115
- },
1116
- setCurrentCohortId: (state, action)=>{
1117
- state.currentCohortId = action.payload;
1118
- },
1119
- /** @hidden */ setCohortList: (state, action)=>{
1120
- if (!action.payload) {
1121
- cohortsAdapter.removeMany(state, state.ids);
1122
- } else {
1123
- cohortsAdapter.upsertMany(state, [
1124
- ...action.payload
1125
- ]);
1126
- }
1127
- }
1128
- }
1129
- });
1130
- /**
1131
- * Returns the selectors for the cohorts EntityAdapter
1132
- * @param state - the CoreState
1133
- *
1134
- * @hidden
1135
- */ const cohortSelectors = cohortsAdapter.getSelectors((state)=>state.cohorts.cohortManager);
1136
- // Filter actions: addFilter, removeFilter, updateFilter
1137
- const { createNewCohort, updateCohortFilter, setCohortFilter, setCohortIndexFilters, duplicateCohort, removeCohortFilter, clearCohortFilters, removeCohort, setCurrentCohortId, updateCohortName, updateCohortCounts, updateCohortIndexCountById, setCohortList } = cohortManagerSlice.actions;
1138
- const cohortReducer = cohortManagerSlice.reducer;
1139
-
1140
- const initialState$2 = {};
1141
- const expandSlice$1 = toolkit.createSlice({
1142
- name: 'CohortBuilder/filterExpand',
1143
- initialState: initialState$2,
1144
- reducers: {
1145
- toggleCohortBuilderCategoryFilter: (state, action)=>{
1146
- return {
1147
- ...state,
1148
- [action.payload.index]: {
1149
- ...state[action.payload.index],
1150
- [action.payload.field]: action.payload.expanded
986
+ this.handleExists = (op)=>({
987
+ not: {
988
+ [op.field]: op?.operand ?? null
1151
989
  }
1152
- };
1153
- },
1154
- toggleCohortBuilderAllFilters: (state, action)=>{
1155
- return {
1156
- ...state,
1157
- [action.payload.index]: Object.keys(state[action.payload.index]).reduce((acc, k)=>{
1158
- acc[k] = action.payload.expand;
1159
- return acc;
1160
- }, {})
1161
- };
1162
- }
1163
- }
1164
- });
1165
- const cohortBuilderFiltersExpandedReducer = expandSlice$1.reducer;
1166
- const { toggleCohortBuilderCategoryFilter, toggleCohortBuilderAllFilters } = expandSlice$1.actions;
1167
- const selectCohortFilterExpanded = (state, index, field)=>state.cohorts.filtersExpanded?.[index]?.[field];
1168
- const selectAllCohortFiltersCollapsed = (state, index)=>index in state.cohorts.filtersExpanded ? Object.values(state.cohorts.filtersExpanded?.[index]).every((e)=>!e) : false;
1169
-
1170
- const initialState$1 = {};
1171
- const expandSlice = toolkit.createSlice({
1172
- name: 'CohortBuilder/filterCombineMode',
1173
- initialState: initialState$1,
1174
- reducers: {
1175
- setCohortFilterCombineMode: (state, action)=>{
990
+ });
991
+ this.handleNestedFilter = (op)=>{
992
+ const child = convertFilterToGqlFilter(op.operand);
1176
993
  return {
1177
- ...state,
1178
- [action.payload.index]: {
1179
- ...state[action.payload.index],
1180
- [action.payload.field]: action.payload.mode
994
+ nested: {
995
+ path: op.path,
996
+ ...child
1181
997
  }
1182
998
  };
1183
- }
999
+ };
1184
1000
  }
1185
- });
1186
- const cohortBuilderFiltersCombineModeReducer = expandSlice.reducer;
1187
- const { setCohortFilterCombineMode } = expandSlice.actions;
1188
- const selectCohortFilterCombineMode = (state, index, field)=>state.cohorts.filtersCombineMode?.[index]?.[field] ?? 'or';
1189
-
1190
- const initialState = {
1191
- shouldShareFilters: false,
1192
- sharedFiltersMap: {}
1001
+ }
1002
+ const convertFilterToGqlFilter = (filter)=>{
1003
+ const handler = new ToGqlHandler();
1004
+ return handleOperation(handler, filter);
1193
1005
  };
1194
- const cohortSharedFiltersSlice = toolkit.createSlice({
1195
- name: 'cohortSharedFilters',
1196
- initialState: initialState,
1197
- reducers: {
1198
- setShouldShareFilters: (state, action)=>{
1199
- state.shouldShareFilters = action.payload;
1200
- return state;
1201
- },
1202
- setSharedFilters: (state, action)=>{
1203
- state.sharedFiltersMap = action.payload;
1204
- }
1205
- }
1206
- });
1207
- const selectShouldShareFilters = (state)=>state.cohorts.sharedFilters.shouldShareFilters;
1208
- const selectSharedFilters = (state)=>state.cohorts.sharedFilters.sharedFiltersMap;
1209
- const selectSharedFiltersForFields = (state, field)=>state.cohorts.sharedFilters.sharedFiltersMap?.[field] ?? [
1210
- field
1211
- ];
1212
- const { setShouldShareFilters, setSharedFilters } = cohortSharedFiltersSlice.actions;
1213
- const cohortSharedFiltersReducer = cohortSharedFiltersSlice.reducer;
1214
-
1215
- const cohortReducers = toolkit.combineReducers({
1216
- filtersExpanded: cohortBuilderFiltersExpandedReducer,
1217
- filtersCombineMode: cohortBuilderFiltersCombineModeReducer,
1218
- sharedFilters: cohortSharedFiltersReducer,
1219
- cohortManager: cohortReducer
1220
- });
1221
-
1222
- const rootReducer = toolkit.combineReducers({
1223
- gen3Services: gen3ServicesReducer,
1224
- user: userReducer,
1225
- gen3Apps: gen3AppReducer,
1226
- drsHostnames: drsHostnamesReducer,
1227
- modals: modalReducer,
1228
- cohorts: cohortReducers,
1229
- activeWorkspace: activeWorkspaceReducer,
1230
- [guppyApiSliceReducerPath]: guppyApiReducer,
1231
- [userAuthApiReducerPath]: userAuthApiReducer
1232
- });
1233
-
1234
- const isOperationWithField = (operation)=>{
1235
- return operation?.field !== undefined;
1236
- };
1237
- const isOperatorWithFieldAndArrayOfOperands = (operation)=>{
1238
- 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
1239
- ) {
1240
- const { operator } = operation.operator;
1241
- return operator === 'in' || operator === 'exclude' || operator === 'excludeifany';
1242
- }
1243
- return false;
1244
- };
1245
- const extractFilterValue = (op)=>{
1246
- const valueExtractorHandler = new ValueExtractorHandler();
1247
- return handleOperation(valueExtractorHandler, op);
1248
- };
1249
- const extractEnumFilterValue = (op)=>{
1250
- const enumValueExtractorHandler = new EnumValueExtractorHandler();
1251
- const results = handleOperation(enumValueExtractorHandler, op);
1252
- return results ?? [];
1253
- };
1254
- const assertNever = (x)=>{
1255
- throw Error(`Exhaustive comparison did not handle: ${x}`);
1256
- };
1257
- const handleOperation = (handler, op)=>{
1258
- switch(op.operator){
1259
- case '=':
1260
- return handler.handleEquals(op);
1261
- case '!=':
1262
- return handler.handleNotEquals(op);
1263
- case '<':
1264
- return handler.handleLessThan(op);
1265
- case '<=':
1266
- return handler.handleLessThanOrEquals(op);
1267
- case '>':
1268
- return handler.handleGreaterThan(op);
1269
- case '>=':
1270
- return handler.handleGreaterThanOrEquals(op);
1271
- case 'and':
1272
- return handler.handleIntersection(op);
1273
- case 'or':
1274
- return handler.handleUnion(op);
1275
- case 'nested':
1276
- return handler.handleNestedFilter(op);
1277
- case 'in':
1278
- case 'includes':
1279
- return handler.handleIncludes(op);
1280
- case 'excludeifany':
1281
- return handler.handleExcludeIfAny(op);
1282
- case 'excludes':
1283
- return handler.handleExcludes(op);
1284
- case 'exists':
1285
- return handler.handleExists(op);
1286
- case 'missing':
1287
- return handler.handleMissing(op);
1288
- default:
1289
- return assertNever(op);
1290
- }
1291
- };
1292
- /**
1293
- * Return true if a FilterSet's root value is an empty object
1294
- * @param fs - FilterSet to test
1295
- */ const isFilterEmpty = (fs)=>lodash.isEqual({}, fs);
1296
- /**
1297
- * Type guard to check if an object is a GQLIntersection
1298
- * @param value - The value to check
1299
- * @returns True if the value is a GQLIntersection
1300
- */ const isGQLIntersection = (value)=>{
1301
- return typeof value === 'object' && value !== null && 'and' in value && Array.isArray(value.and);
1302
- };
1303
- /**
1304
- * Type guard to check if an object is a GQLIntersection
1305
- * @param value - The value to check
1306
- * @returns True if the value is a GQLIntersection
1307
- */ const isGQLUnion = (value)=>{
1308
- return typeof value === 'object' && value !== null && 'or' in value && Array.isArray(value.or);
1309
- };
1310
- class ToGqlHandler {
1311
- constructor(){
1312
- this.handleEquals = (op)=>({
1313
- '=': {
1314
- [op.field]: op.operand
1315
- }
1316
- });
1317
- this.handleNotEquals = (op)=>({
1318
- '!=': {
1319
- [op.field]: op.operand
1320
- }
1321
- });
1322
- this.handleLessThan = (op)=>({
1323
- '<': {
1324
- [op.field]: op.operand
1325
- }
1326
- });
1327
- this.handleLessThanOrEquals = (op)=>({
1328
- '<=': {
1329
- [op.field]: op.operand
1330
- }
1331
- });
1332
- this.handleGreaterThan = (op)=>({
1333
- '>': {
1334
- [op.field]: op.operand
1335
- }
1336
- });
1337
- this.handleGreaterThanOrEquals = (op)=>({
1338
- '>=': {
1339
- [op.field]: op.operand
1340
- }
1341
- });
1342
- this.handleIncludes = (op)=>({
1343
- in: {
1344
- [op.field]: op.operands
1345
- }
1346
- });
1347
- this.handleExcludes = (op)=>({
1348
- exclude: {
1349
- [op.field]: op.operands
1350
- }
1351
- });
1352
- this.handleExcludeIfAny = (op)=>({
1353
- excludeifany: {
1354
- [op.field]: op.operands
1355
- }
1356
- });
1357
- this.handleIntersection = (op)=>({
1358
- and: op.operands.map((x)=>convertFilterToGqlFilter(x))
1359
- });
1360
- this.handleUnion = (op)=>({
1361
- or: op.operands.map((x)=>convertFilterToGqlFilter(x))
1362
- });
1363
- this.handleMissing = (op)=>({
1364
- is: {
1365
- [op.field]: 'MISSING'
1366
- }
1367
- });
1368
- this.handleExists = (op)=>({
1369
- not: {
1370
- [op.field]: op?.operand ?? null
1371
- }
1372
- });
1373
- this.handleNestedFilter = (op)=>{
1374
- const child = convertFilterToGqlFilter(op.operand);
1375
- return {
1376
- nested: {
1377
- path: op.path,
1378
- ...child
1379
- }
1380
- };
1381
- };
1382
- }
1383
- }
1384
- const convertFilterToGqlFilter = (filter)=>{
1385
- const handler = new ToGqlHandler();
1386
- return handleOperation(handler, filter);
1387
- };
1388
- const convertFilterSetToGqlFilter = (fs, toplevelOp = 'and')=>{
1389
- const fsKeys = Object.keys(fs.root);
1390
- // if no keys return undefined
1391
- if (fsKeys.length === 0) return {
1392
- and: []
1393
- };
1394
- return toplevelOp === 'and' ? {
1395
- and: fsKeys.map((key)=>convertFilterToGqlFilter(fs.root[key]))
1396
- } : {
1397
- or: fsKeys.map((key)=>convertFilterToGqlFilter(fs.root[key]))
1398
- };
1399
- };
1400
- const handleGqlOperation = (handler, op)=>{
1401
- const operationKeys = Object.keys(op);
1402
- if (operationKeys.includes('=')) {
1403
- 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);
1404
1022
  }
1405
1023
  if (operationKeys.includes('!=')) {
1406
1024
  return handler.handleNotEquals(op);
@@ -1622,132 +1240,571 @@ const filterSetToOperation = (fs)=>{
1622
1240
  })
1623
1241
  };
1624
1242
  }
1625
- return undefined;
1626
- };
1243
+ return undefined;
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
+ };
1275
+
1276
+ const isFilterSet = (input)=>{
1277
+ if (typeof input !== 'object' || input === null) {
1278
+ return false;
1279
+ }
1280
+ const { root, mode } = input;
1281
+ if (typeof root !== 'object' || root === null) {
1282
+ return false;
1283
+ }
1284
+ if (![
1285
+ 'and',
1286
+ 'or'
1287
+ ].includes(mode)) {
1288
+ return false;
1289
+ }
1290
+ return true;
1291
+ };
1292
+ const isUnion = (value)=>{
1293
+ return typeof value === 'object' && value !== null && value.operator === 'or' && Array.isArray(value.operands);
1294
+ };
1295
+ const isIntersection = (value)=>{
1296
+ return typeof value === 'object' && value !== null && value.operator === 'and' && Array.isArray(value.operands);
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';
1303
+ const isOperandsType = (operation)=>{
1304
+ return operation?.operands !== undefined;
1305
+ };
1306
+ const isNestedFilter = (operation)=>{
1307
+ return operation.operator === 'nested';
1308
+ };
1309
+ const isIndexedFilterSetEmpty = (filters)=>Object.values(filters).every((filterSet)=>Object.keys(filterSet).length === 0);
1310
+ const EmptyFilterSet = {
1311
+ mode: 'and',
1312
+ root: {}
1313
+ };
1314
+
1315
+ const FieldNameOverrides = {};
1316
+ const COMMON_PREPOSITIONS = [
1317
+ 'a',
1318
+ 'an',
1319
+ 'and',
1320
+ 'at',
1321
+ 'but',
1322
+ 'by',
1323
+ 'for',
1324
+ 'in',
1325
+ 'is',
1326
+ 'nor',
1327
+ 'of',
1328
+ 'on',
1329
+ 'or',
1330
+ 'out',
1331
+ 'so',
1332
+ 'the',
1333
+ 'to',
1334
+ 'up',
1335
+ 'yet'
1336
+ ];
1337
+ const capitalize$1 = (s)=>s.length > 0 ? s[0].toUpperCase() + s.slice(1) : '';
1338
+ const trimFirstFieldNameToTitle = (fieldName, trim = false)=>{
1339
+ if (trim) {
1340
+ const source = fieldName.slice(fieldName.indexOf('.') + 1);
1341
+ return fieldNameToTitle(source ? source : fieldName, 0);
1342
+ }
1343
+ return fieldNameToTitle(fieldName);
1344
+ };
1345
+ /**
1346
+ * Converts a filter name to a title,
1347
+ * For example files.input.experimental_strategy will get converted to Experimental Strategy
1348
+ * if sections == 2 then the output would be Input Experimental Strategy
1349
+ * @param fieldName input filter expected to be: string.firstpart_secondpart
1350
+ * @param sections number of "sections" string.string.string to got back from the end of the field
1351
+ */ const fieldNameToTitle = (fieldName, sections = 1)=>{
1352
+ if (fieldName in FieldNameOverrides) {
1353
+ return FieldNameOverrides[fieldName];
1354
+ }
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
+ });
1627
1704
  /**
1628
- * Constructs a nested operation object based on the provided field and leaf operand.
1629
- * If the field does not contain a dot '.', it either assigns the field to the leaf operand (if applicable)
1630
- * or returns the leaf operand as is. When the field contains dots, it splits the field into parts,
1631
- * creates a "nested" operation for the root field, and recursively constructs the nested structure
1632
- * for the remaining portion of the field.
1705
+ * Returns the selectors for the cohorts EntityAdapter
1706
+ * @param state - the CoreState
1633
1707
  *
1634
- * @param {string} field - The hierarchical field path, with segments separated by dots (e.g., "root.child").
1635
- * @param {Operation} leafOperand - The operation to be nested within the specified path.
1636
- * @param parentPath - The parent path of the current field. Guppy nested filters require a parent path.
1637
- * @param depth
1638
- * @returns {Operation} A nested operation object that represents the structured path and operand.
1639
- */ const buildNestedGQLFilter = (field, leafOperand, parentPath = undefined)=>{
1640
- if (!field.includes('.')) {
1641
- return leafOperand;
1642
- }
1643
- const splitFieldArray = field.split('.');
1644
- const nextField = splitFieldArray.shift();
1645
- if (!nextField) {
1646
- console.warn('Invalid field path:', field);
1647
- return leafOperand;
1648
- }
1649
- const currentPath = parentPath ? `${parentPath}.${nextField}` : nextField;
1650
- return {
1651
- nested: {
1652
- path: currentPath,
1653
- ...buildNestedGQLFilter(splitFieldArray.join('.'), leafOperand, currentPath)
1654
- }
1655
- };
1656
- };
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;
1657
1713
 
1658
- const isFilterSet = (input)=>{
1659
- if (typeof input !== 'object' || input === null) {
1660
- return false;
1661
- }
1662
- const { root, mode } = input;
1663
- if (typeof root !== 'object' || root === null) {
1664
- return false;
1665
- }
1666
- if (![
1667
- 'and',
1668
- 'or'
1669
- ].includes(mode)) {
1670
- return false;
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
+ }
1671
1737
  }
1672
- return true;
1673
- };
1674
- const isUnion = (value)=>{
1675
- return typeof value === 'object' && value !== null && value.operator === 'or' && Array.isArray(value.operands);
1676
- };
1677
- const isIntersection = (value)=>{
1678
- return typeof value === 'object' && value !== null && value.operator === 'and' && Array.isArray(value.operands);
1679
- };
1680
- const isOperandsType = (operation)=>{
1681
- return operation?.operands !== undefined;
1682
- };
1683
- const isIndexedFilterSetEmpty = (filters)=>Object.values(filters).every((filterSet)=>Object.keys(filterSet).length === 0);
1684
- const EmptyFilterSet = {
1685
- mode: 'and',
1686
- root: {}
1687
- };
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;
1688
1743
 
1689
- const FieldNameOverrides = {};
1690
- const COMMON_PREPOSITIONS = [
1691
- 'a',
1692
- 'an',
1693
- 'and',
1694
- 'at',
1695
- 'but',
1696
- 'by',
1697
- 'for',
1698
- 'in',
1699
- 'is',
1700
- 'nor',
1701
- 'of',
1702
- 'on',
1703
- 'or',
1704
- 'out',
1705
- 'so',
1706
- 'the',
1707
- 'to',
1708
- 'up',
1709
- 'yet'
1710
- ];
1711
- const capitalize$1 = (s)=>s.length > 0 ? s[0].toUpperCase() + s.slice(1) : '';
1712
- const trimFirstFieldNameToTitle = (fieldName, trim = false)=>{
1713
- if (trim) {
1714
- const source = fieldName.slice(fieldName.indexOf('.') + 1);
1715
- return fieldNameToTitle(source ? source : fieldName, 0);
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
+ }
1716
1758
  }
1717
- return fieldNameToTitle(fieldName);
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: {}
1718
1767
  };
1719
- /**
1720
- * Converts a filter name to a title,
1721
- * For example files.input.experimental_strategy will get converted to Experimental Strategy
1722
- * if sections == 2 then the output would be Input Experimental Strategy
1723
- * @param fieldName input filter expected to be: string.firstpart_secondpart
1724
- * @param sections number of "sections" string.string.string to got back from the end of the field
1725
- */ const fieldNameToTitle = (fieldName, sections = 1)=>{
1726
- if (fieldName in FieldNameOverrides) {
1727
- return FieldNameOverrides[fieldName];
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
+ }
1728
1779
  }
1729
- if (fieldName === undefined) return 'No Title';
1730
- return fieldName.split('.').slice(-sections).map((s)=>s.split('_')).flat().map((word)=>COMMON_PREPOSITIONS.includes(word) ? word : capitalize$1(word)).join(' ');
1731
- };
1732
- /**
1733
- * Extracts the index name from the field name
1734
- * @param fieldName
1735
- */ const extractIndexFromFullFieldName = (fieldName)=>fieldName.split('.')[0];
1736
- /**
1737
- * prepend the index name to the field name
1738
- */ const prependIndexToFieldName = (fieldName, index)=>`${index}.${fieldName}`;
1739
- /**
1740
- * extract the field name from the index.field name
1741
- */ const extractFieldNameFromFullFieldName = (fieldName)=>fieldName.split('.').slice(1).join('.');
1742
- /**
1743
- * extract the field name and the index from the index.field name returning as a tuple
1744
- */ const extractIndexAndFieldNameFromFullFieldName = (fieldName)=>{
1745
- const [index, ...rest] = fieldName.split('.');
1746
- return [
1747
- index,
1748
- rest.join('.')
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
1749
1785
  ];
1750
- };
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
+ });
1751
1808
 
1752
1809
  /**
1753
1810
  * Flattens a deep nested JSON object skipping
@@ -2235,18 +2292,18 @@ const explorerTags = guppyApi.enhanceEndpoints({
2235
2292
  */ const explorerApi = explorerTags.injectEndpoints({
2236
2293
  endpoints: (builder)=>({
2237
2294
  getAllFieldsForType: builder.query({
2238
- query: (type)=>({
2239
- query: `{ _mapping ${type} } }`
2295
+ query: ({ type, indexPrefix = '' })=>({
2296
+ query: `{ ${indexPrefix}_mapping ${type} } }`
2240
2297
  }),
2241
2298
  transformResponse: (response, _meta, params)=>{
2242
2299
  return response[params.type];
2243
2300
  }
2244
2301
  }),
2245
2302
  getAccessibleData: builder.query({
2246
- query: ({ type, fields, accessibility })=>{
2303
+ query: ({ type, fields, accessibility, indexPrefix = '' })=>{
2247
2304
  const fieldParts = fields.map((field)=>`${field} { histogram { key count } }`);
2248
2305
  return {
2249
- query: `_aggregation {
2306
+ query: `${indexPrefix}_aggregation {
2250
2307
  ${type} (accessibility: ${accessibility}) {
2251
2308
  ${fieldParts.join(',')}
2252
2309
  }
@@ -2255,7 +2312,7 @@ const explorerTags = guppyApi.enhanceEndpoints({
2255
2312
  }
2256
2313
  }),
2257
2314
  getRawDataAndTotalCounts: builder.query({
2258
- query: ({ type, fields, filters, sort, offset = 0, size = 20, accessibility = Accessibility.ALL, format = undefined })=>{
2315
+ query: ({ type, fields, filters, sort, offset = 0, size = 20, accessibility = Accessibility.ALL, format = undefined, indexPrefix = '' })=>{
2259
2316
  const gqlFilter = convertFilterSetToGqlFilter(filters);
2260
2317
  const params = [
2261
2318
  ...sort ? [
@@ -2280,7 +2337,7 @@ const explorerTags = guppyApi.enhanceEndpoints({
2280
2337
  'filter: $filter'
2281
2338
  ] : []
2282
2339
  ].join(',');
2283
- const dataTypeLine = `${type} (accessibility: ${accessibility}, offset: ${offset}, first: ${size},
2340
+ const dataTypeLine = `${indexPrefix}${type} (accessibility: ${accessibility}, offset: ${offset}, first: ${size},
2284
2341
  ${dataParams}) {`;
2285
2342
  const typeAggsLine = `${type} (${gqlFilter && 'filter: $filter,'} accessibility: ${accessibility}) {`;
2286
2343
  const processedFields = fields.map((field)=>rawDataQueryStrForEachField(field));
@@ -2288,7 +2345,7 @@ const explorerTags = guppyApi.enhanceEndpoints({
2288
2345
  ${dataTypeLine}
2289
2346
  ${processedFields.join(' ')}
2290
2347
  }
2291
- _aggregation {
2348
+ ${indexPrefix}_aggregation {
2292
2349
  ${typeAggsLine}
2293
2350
  _totalCount
2294
2351
  }
@@ -2316,13 +2373,13 @@ const explorerTags = guppyApi.enhanceEndpoints({
2316
2373
  ]
2317
2374
  }),
2318
2375
  getAggs: builder.query({
2319
- query: ({ type, fields, filters, accessibility = Accessibility.ALL, filterSelf = false })=>{
2320
- return buildGetAggregationQuery(type, fields, filters, accessibility, filterSelf);
2376
+ query: ({ type, fields, filters, accessibility = Accessibility.ALL, filterSelf = false, indexPrefix = '' })=>{
2377
+ return buildGetAggregationQuery(type, fields, filters, accessibility, filterSelf, undefined, indexPrefix);
2321
2378
  },
2322
2379
  transformResponse: (response, _meta, args)=>{
2323
- const buckets = processHistogramResponse(response?.data?._aggregation[args.type] ?? {});
2380
+ const buckets = processHistogramResponse(response?.data?.[`${args?.indexPrefix ?? ''}_aggregation`][args.type] ?? {});
2324
2381
  // check for totals
2325
- const count = response?.data?._aggregation[args.type]?._totalCount ?? null;
2382
+ const count = response?.data?.[`${args?.indexPrefix ?? ''}_aggregation`][args.type]?._totalCount ?? null;
2326
2383
  return {
2327
2384
  _totalCount: [
2328
2385
  {
@@ -2338,24 +2395,24 @@ const explorerTags = guppyApi.enhanceEndpoints({
2338
2395
  ]
2339
2396
  }),
2340
2397
  getStatsAggregations: builder.query({
2341
- query: ({ type, fields, filters, accessibility = Accessibility.ALL, filterSelf = false, queryId = undefined })=>{
2342
- return buildGetStatsAggregationQuery(type, fields, filters, accessibility, filterSelf, queryId);
2398
+ query: ({ type, fields, filters, accessibility = Accessibility.ALL, filterSelf = false, queryId = undefined, indexPrefix = '' })=>{
2399
+ return buildGetStatsAggregationQuery(type, fields, filters, accessibility, filterSelf, queryId, indexPrefix);
2343
2400
  },
2344
2401
  transformResponse: (response, _meta, args)=>{
2345
- return processHistogramResponse(response?.data?._aggregation[args.type] ?? {});
2402
+ return processHistogramResponse(response?.data?.[`${args?.indexPrefix ?? ''}_aggregation`][args.type] ?? {});
2346
2403
  },
2347
2404
  providesTags: [
2348
2405
  'STATS'
2349
2406
  ]
2350
2407
  }),
2351
2408
  getSubAggs: builder.query({
2352
- query: ({ type, mainField, termsFields = undefined, missingFields = undefined, numericAggAsText = false, filters = undefined, accessibility = Accessibility.ALL })=>{
2409
+ query: ({ type, mainField, termsFields = undefined, missingFields = undefined, numericAggAsText = false, filters = undefined, accessibility = Accessibility.ALL, indexPrefix = '' })=>{
2353
2410
  const nestedAggFields = {
2354
2411
  termsFields: termsFields,
2355
2412
  missingFields: missingFields
2356
2413
  };
2357
2414
  const query = `query getSubAggs ( ${filters ?? '$filter: JSON,'} $nestedAggFields: JSON) {
2358
- _aggregation {
2415
+ ${indexPrefix}_aggregation {
2359
2416
  ${type} ( ${filters ?? 'filter: $filter, filterSelf: false,'} nestedAggFields: $nestedAggFields, accessibility: ${accessibility}) {
2360
2417
  _totalCounts
2361
2418
  ${nestedHistogramQueryStrForEachField(mainField, numericAggAsText)}
@@ -2371,18 +2428,18 @@ const explorerTags = guppyApi.enhanceEndpoints({
2371
2428
  };
2372
2429
  },
2373
2430
  transformResponse: (response, _meta, args)=>{
2374
- return processHistogramResponse(response?.data?._aggregation[args.type] ?? {});
2431
+ return processHistogramResponse(response?.data?.[`${args?.indexPrefix ?? ''}_aggregation`][args.type] ?? {});
2375
2432
  },
2376
2433
  providesTags: [
2377
2434
  'AGGS'
2378
2435
  ]
2379
2436
  }),
2380
2437
  getCounts: builder.query({
2381
- query: ({ type, filters, accessibility = Accessibility.ALL, queryId = undefined })=>{
2438
+ query: ({ type, filters, accessibility = Accessibility.ALL, queryId = undefined, indexPrefix = '' })=>{
2382
2439
  const gqlFilters = convertFilterSetToGqlFilter(filters);
2383
- const queryLine = `query totalCounts${queryId ? `_${queryId}` : ''} ${gqlFilters ? '($filter: JSON)' : ''}{`;
2440
+ const queryLine = `query totalCounts${queryId ? `${indexPrefix}_${queryId}` : ''} ${gqlFilters ? '($filter: JSON)' : ''}{`;
2384
2441
  const typeAggsLine = `${type} ${gqlFilters ? '(filter: $filter, ' : '('} accessibility: ${accessibility}) {`;
2385
- const query = `${queryLine} _aggregation {
2442
+ const query = `${queryLine} ${indexPrefix}_aggregation {
2386
2443
  ${typeAggsLine}
2387
2444
  _totalCount
2388
2445
  }
@@ -2398,23 +2455,23 @@ const explorerTags = guppyApi.enhanceEndpoints({
2398
2455
  };
2399
2456
  },
2400
2457
  transformResponse: (response, _meta, args)=>{
2401
- if (!response.data || !response.data._aggregation) {
2458
+ if (!response.data || !response.data[`${args?.indexPrefix ?? ''}_aggregation`]) {
2402
2459
  throw new Error('Invalid response: Missing data or _aggregation field');
2403
2460
  }
2404
- if (!(args.type in response.data._aggregation)) {
2461
+ if (!(args.type in response.data[`${args?.indexPrefix ?? ''}_aggregation`])) {
2405
2462
  throw new Error(`Invalid response: Missing expected key '${args.type}' in _aggregation`);
2406
2463
  }
2407
- return response.data._aggregation[args.type]._totalCount ?? 0;
2464
+ return response.data[`${args?.indexPrefix ?? ''}_aggregation`][args.type]._totalCount ?? 0;
2408
2465
  },
2409
2466
  providesTags: [
2410
2467
  'COUNTS'
2411
2468
  ]
2412
2469
  }),
2413
2470
  getFieldCountSummary: builder.query({
2414
- query: ({ type, field, filters, accessibility = Accessibility.ALL })=>{
2471
+ query: ({ type, field, filters, accessibility = Accessibility.ALL, indexPrefix = '' })=>{
2415
2472
  const gqlFilters = convertFilterSetToGqlFilter(filters);
2416
2473
  const query = `query summary ($filter: JSON) {
2417
- _aggregation {
2474
+ ${indexPrefix}_aggregation {
2418
2475
  ${type} (filter: $filter, accessibility: ${accessibility}) {
2419
2476
  ${field} {
2420
2477
  histogram {
@@ -2435,15 +2492,15 @@ const explorerTags = guppyApi.enhanceEndpoints({
2435
2492
  }
2436
2493
  }),
2437
2494
  getFieldsForIndex: builder.query({
2438
- query: (index)=>{
2495
+ query: ({ index, indexPrefix = '' })=>{
2439
2496
  return {
2440
2497
  query: `{
2441
- _mapping { ${index} }
2498
+ ${indexPrefix}_mapping { ${index} }
2442
2499
  }`
2443
2500
  };
2444
2501
  },
2445
- transformResponse: (response)=>{
2446
- return response['_mapping'];
2502
+ transformResponse: (response, _meta, args)=>{
2503
+ return response[`${args.indexPrefix}_mapping`];
2447
2504
  }
2448
2505
  }),
2449
2506
  getSharedFieldsForIndex: builder.query({
@@ -2480,16 +2537,18 @@ const useGetArrayTypes = ()=>{
2480
2537
  return data ? data['indices'] : {};
2481
2538
  }
2482
2539
  };
2483
- const useGetIndexFields = (index)=>{
2484
- const { data } = useGetFieldsForIndexQuery(index);
2540
+ const useGetIndexFields = (index, indexPrefix = '')=>{
2541
+ const { data } = useGetFieldsForIndexQuery({
2542
+ index: index,
2543
+ indexPrefix: indexPrefix
2544
+ });
2485
2545
  return data ?? [];
2486
2546
  };
2487
- const buildGetAggregationQuery = (type, fields, filters, accessibility = Accessibility.ALL, filterSelf = false, queryId = undefined)=>{
2488
- const queryStart = isFilterEmpty(filters) ? `
2489
- query getAggs${queryId ? `_${queryId}` : ''} {
2490
- _aggregation {
2547
+ const buildGetAggregationQuery = (type, fields, filters, accessibility = Accessibility.ALL, filterSelf = false, queryId = undefined, indexPrefix = '')=>{
2548
+ const queryStart = isFilterEmpty(filters) ? `query getAggs${queryId ? `_${queryId}` : ''} {
2549
+ ${indexPrefix}_aggregation {
2491
2550
  ${type} (accessibility: ${accessibility}) {` : `query getAggs ($filter: JSON) {
2492
- _aggregation {
2551
+ ${indexPrefix}_aggregation {
2493
2552
 
2494
2553
  ${type} (filter: $filter, filterSelf: ${filterSelf ? 'true' : 'false'}, accessibility: ${accessibility}) { _totalCount`;
2495
2554
  const query = `${queryStart}
@@ -2505,12 +2564,12 @@ const buildGetAggregationQuery = (type, fields, filters, accessibility = Accessi
2505
2564
  };
2506
2565
  return queryBody;
2507
2566
  };
2508
- const buildGetStatsAggregationQuery = (type, fields, filters, accessibility = Accessibility.ALL, filterSelf = false, queryId = undefined)=>{
2567
+ const buildGetStatsAggregationQuery = (type, fields, filters, accessibility = Accessibility.ALL, filterSelf = false, queryId = undefined, indexPrefix = '')=>{
2509
2568
  const queryStart = isFilterEmpty(filters) ? `
2510
2569
  query getStatsAggs${queryId ? `_${queryId}` : ''} {
2511
- _aggregation {
2570
+ ${indexPrefix}_aggregation {
2512
2571
  ${type} (accessibility: ${accessibility}) {` : `query getStatsAggs${queryId ? `_${queryId}` : ''} ($filter: JSON) {
2513
- _aggregation {
2572
+ ${indexPrefix}_aggregation {
2514
2573
  ${type} (filter: $filter, filterSelf: ${filterSelf ? 'true' : 'false'}, accessibility: ${accessibility}) { _totalCount`;
2515
2574
  const query = `${queryStart}
2516
2575
  ${fields.map((field)=>statsQueryStrForEachField(field))}
@@ -2574,7 +2633,8 @@ const persistConfig = {
2574
2633
  storage,
2575
2634
  whitelist: [
2576
2635
  'cohorts',
2577
- 'activeWorkspace'
2636
+ 'activeWorkspace',
2637
+ 'cart'
2578
2638
  ]
2579
2639
  };
2580
2640
  const persistedReducer = reduxPersist.persistReducer(persistConfig, rootReducer);
@@ -5759,6 +5819,7 @@ const isWorkspaceActive = (status)=>status === WorkspaceStatus.Running || status
5759
5819
  const isWorkspaceRunningOrStopping = (status)=>status === WorkspaceStatus.Running || status === WorkspaceStatus.Terminating;
5760
5820
 
5761
5821
  exports.Accessibility = Accessibility;
5822
+ exports.CART_LIMIT = CART_LIMIT;
5762
5823
  exports.CohortStorage = CohortStorage;
5763
5824
  exports.CoreProvider = CoreProvider;
5764
5825
  exports.DAYS_IN_YEAR = DAYS_IN_YEAR;
@@ -5792,6 +5853,7 @@ exports.RequestedWorkspaceStatus = RequestedWorkspaceStatus;
5792
5853
  exports.ToGqlHandler = ToGqlHandler;
5793
5854
  exports.ValueExtractorHandler = ValueExtractorHandler;
5794
5855
  exports.WorkspaceStatus = WorkspaceStatus;
5856
+ exports.addItemsToCart = addItemsToCart;
5795
5857
  exports.ageDisplay = ageDisplay;
5796
5858
  exports.appendFilterToOperation = appendFilterToOperation;
5797
5859
  exports.buildGetAggregationQuery = buildGetAggregationQuery;
@@ -5801,6 +5863,8 @@ exports.buildNestedGQLFilter = buildNestedGQLFilter;
5801
5863
  exports.calculatePercentageAsNumber = calculatePercentageAsNumber;
5802
5864
  exports.calculatePercentageAsString = calculatePercentageAsString;
5803
5865
  exports.capitalize = capitalize;
5866
+ exports.cartReducer = cartReducer;
5867
+ exports.cartReducerPath = cartReducerPath;
5804
5868
  exports.clearActiveWorkspaceId = clearActiveWorkspaceId;
5805
5869
  exports.clearCohortFilters = clearCohortFilters;
5806
5870
  exports.cohortReducer = cohortReducer;
@@ -5828,6 +5892,7 @@ exports.extractEnumFilterValue = extractEnumFilterValue;
5828
5892
  exports.extractFieldNameFromFullFieldName = extractFieldNameFromFullFieldName;
5829
5893
  exports.extractFileDatasetsInRecords = extractFileDatasetsInRecords;
5830
5894
  exports.extractFilterValue = extractFilterValue;
5895
+ exports.extractFiltersWithPrefixFromFilterSet = extractFiltersWithPrefixFromFilterSet;
5831
5896
  exports.extractIndexAndFieldNameFromFullFieldName = extractIndexAndFieldNameFromFullFieldName;
5832
5897
  exports.extractIndexFromDataLibraryCohort = extractIndexFromDataLibraryCohort;
5833
5898
  exports.extractIndexFromFullFieldName = extractIndexFromFullFieldName;
@@ -5885,10 +5950,12 @@ exports.isHistogramRangeData = isHistogramRangeData;
5885
5950
  exports.isHttpStatusError = isHttpStatusError;
5886
5951
  exports.isIndexedFilterSetEmpty = isIndexedFilterSetEmpty;
5887
5952
  exports.isIntersection = isIntersection;
5953
+ exports.isIntersectionOrUnion = isIntersectionOrUnion;
5888
5954
  exports.isJSONObject = isJSONObject;
5889
5955
  exports.isJSONValue = isJSONValue;
5890
5956
  exports.isJSONValueArray = isJSONValueArray;
5891
5957
  exports.isNameUnique = isNameUnique;
5958
+ exports.isNestedFilter = isNestedFilter;
5892
5959
  exports.isNotDefined = isNotDefined;
5893
5960
  exports.isObject = isObject;
5894
5961
  exports.isOperandsType = isOperandsType;
@@ -5918,6 +5985,7 @@ exports.rawDataQueryStrForEachField = rawDataQueryStrForEachField;
5918
5985
  exports.registerDefaultRemoteSupport = registerDefaultRemoteSupport;
5919
5986
  exports.removeCohort = removeCohort;
5920
5987
  exports.removeCohortFilter = removeCohortFilter;
5988
+ exports.removeItemsFromCart = removeItemsFromCart;
5921
5989
  exports.requestorApi = requestorApi;
5922
5990
  exports.resetUserState = resetUserState;
5923
5991
  exports.resourcePathFromProjectID = resourcePathFromProjectID;
@@ -5931,6 +5999,10 @@ exports.selectAvailableCohortByName = selectAvailableCohortByName;
5931
5999
  exports.selectAvailableCohorts = selectAvailableCohorts;
5932
6000
  exports.selectCSRFToken = selectCSRFToken;
5933
6001
  exports.selectCSRFTokenData = selectCSRFTokenData;
6002
+ exports.selectCart = selectCart;
6003
+ exports.selectCartCount = selectCartCount;
6004
+ exports.selectCartItem = selectCartItem;
6005
+ exports.selectCartItems = selectCartItems;
5934
6006
  exports.selectCohortById = selectCohortById;
5935
6007
  exports.selectCohortFilterCombineMode = selectCohortFilterCombineMode;
5936
6008
  exports.selectCohortFilterExpanded = selectCohortFilterExpanded;