@gen3/core 0.12.68 → 0.12.70

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 (118) hide show
  1. package/dist/cjs/index.js +2124 -2154
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/dts/constants.d.ts.map +1 -1
  4. package/dist/dts/dataAccess.d.ts.map +1 -1
  5. package/dist/dts/features/aiSearch/aiSearchSlice.d.ts +174 -174
  6. package/dist/dts/features/app/store.d.ts.map +1 -1
  7. package/dist/dts/features/authz/authzMappingSlice.d.ts +403 -403
  8. package/dist/dts/features/cedar/cedarApi.d.ts +54 -54
  9. package/dist/dts/features/cedar/cedarApi.d.ts.map +1 -1
  10. package/dist/dts/features/cedar/createCedarInstance.d.ts.map +1 -1
  11. package/dist/dts/features/cohort/cohortManagerSelector.d.ts +5 -5
  12. package/dist/dts/features/cohort/cohortManagerSelector.d.ts.map +1 -1
  13. package/dist/dts/features/cohort/cohortManagerSlice.d.ts.map +1 -1
  14. package/dist/dts/features/cohort/filterCombineModeSlice.d.ts.map +1 -1
  15. package/dist/dts/features/cohort/filterExpandSlice.d.ts.map +1 -1
  16. package/dist/dts/features/cohort/sharedFiltersSlice.d.ts +1 -1
  17. package/dist/dts/features/cohort/sharedFiltersSlice.d.ts.map +1 -1
  18. package/dist/dts/features/cohort/storage/CohortStorage.d.ts.map +1 -1
  19. package/dist/dts/features/cohort/utils.d.ts.map +1 -1
  20. package/dist/dts/features/cohortComparison/cohortFacetSlice.d.ts +60 -60
  21. package/dist/dts/features/cohortComparison/pValueApi.d.ts +60 -60
  22. package/dist/dts/features/cohortComparison/vennDiagramSlice.d.ts +60 -60
  23. package/dist/dts/features/config/registry.d.ts.map +1 -1
  24. package/dist/dts/features/dataLibrary/storage/APIStorageService.d.ts.map +1 -1
  25. package/dist/dts/features/dataLibrary/storage/CachedAPIStorageService.d.ts.map +1 -1
  26. package/dist/dts/features/dataLibrary/storage/DataLibraryStorageService.d.ts.map +1 -1
  27. package/dist/dts/features/dataLibrary/storage/LocalStorageService.d.ts.map +1 -1
  28. package/dist/dts/features/dataLibrary/storage/tests/data.d.ts.map +1 -1
  29. package/dist/dts/features/dataLibrary/types.d.ts.map +1 -1
  30. package/dist/dts/features/dataLibrary/useDataLibrary.d.ts.map +1 -1
  31. package/dist/dts/features/dataLibrary/utils.d.ts.map +1 -1
  32. package/dist/dts/features/download/constants.d.ts.map +1 -1
  33. package/dist/dts/features/download/downloadStatusApi.d.ts +235 -235
  34. package/dist/dts/features/drsResolver/drsHostnameSlice.d.ts.map +1 -1
  35. package/dist/dts/features/drsResolver/resolvers/cachedDRSResolver.d.ts.map +1 -1
  36. package/dist/dts/features/drsResolver/resolvers/dataGUIDSDotOrg.d.ts.map +1 -1
  37. package/dist/dts/features/drsResolver/utils.d.ts.map +1 -1
  38. package/dist/dts/features/fence/credentialsApi.d.ts +222 -222
  39. package/dist/dts/features/fence/fenceApi.d.ts +300 -300
  40. package/dist/dts/features/fence/fenceApi.d.ts.map +1 -1
  41. package/dist/dts/features/fence/fetchFence.d.ts.map +1 -1
  42. package/dist/dts/features/fence/jwtApi.d.ts +120 -120
  43. package/dist/dts/features/fence/utils.d.ts.map +1 -1
  44. package/dist/dts/features/filters/filters.d.ts +1 -1
  45. package/dist/dts/features/filters/filters.d.ts.map +1 -1
  46. package/dist/dts/features/filters/nestedFilters.d.ts +1 -1
  47. package/dist/dts/features/filters/nestedFilters.d.ts.map +1 -1
  48. package/dist/dts/features/filters/types.d.ts.map +1 -1
  49. package/dist/dts/features/filters/utils.d.ts.map +1 -1
  50. package/dist/dts/features/gen3Apps/Gen3App.d.ts.map +1 -1
  51. package/dist/dts/features/gen3Apps/Gen3AppRTKQ.d.ts +1 -1
  52. package/dist/dts/features/gen3Apps/Gen3AppRTKQ.d.ts.map +1 -1
  53. package/dist/dts/features/gen3Apps/gen3AppRegistry.d.ts.map +1 -1
  54. package/dist/dts/features/gen3Apps/gen3AppsSlice.d.ts.map +1 -1
  55. package/dist/dts/features/graphQL/graphQLSlice.d.ts +61 -61
  56. package/dist/dts/features/guppy/download.d.ts +9 -0
  57. package/dist/dts/features/guppy/download.d.ts.map +1 -0
  58. package/dist/dts/features/guppy/guppyApi.d.ts +2 -2
  59. package/dist/dts/features/guppy/guppyDownloadSlice.d.ts +120 -120
  60. package/dist/dts/features/guppy/guppySlice.d.ts +2141 -2141
  61. package/dist/dts/features/guppy/guppySlice.d.ts.map +1 -1
  62. package/dist/dts/features/guppy/index.d.ts +2 -1
  63. package/dist/dts/features/guppy/index.d.ts.map +1 -1
  64. package/dist/dts/features/guppy/processing.d.ts.map +1 -1
  65. package/dist/dts/features/guppy/queryGenerators.d.ts.map +1 -1
  66. package/dist/dts/features/guppy/range.d.ts.map +1 -1
  67. package/dist/dts/features/guppy/utils.d.ts +1 -27
  68. package/dist/dts/features/guppy/utils.d.ts.map +1 -1
  69. package/dist/dts/features/indexd/indexdSlice.d.ts +240 -240
  70. package/dist/dts/features/manifest/manifestApi.d.ts +585 -585
  71. package/dist/dts/features/metadata/metadataSlice.d.ts +540 -540
  72. package/dist/dts/features/metadata/utils.d.ts.map +1 -1
  73. package/dist/dts/features/modals/modalsSlice.d.ts.map +1 -1
  74. package/dist/dts/features/notifications/notificationService.d.ts.map +1 -1
  75. package/dist/dts/features/requestor/remoteSupport/registeredRemoteSupportServices.d.ts.map +1 -1
  76. package/dist/dts/features/requestor/remoteSupport/types.d.ts.map +1 -1
  77. package/dist/dts/features/requestor/requestorSlice.d.ts +354 -354
  78. package/dist/dts/features/requestor/requestorSlice.d.ts.map +1 -1
  79. package/dist/dts/features/sower/sowerApi.d.ts +558 -558
  80. package/dist/dts/features/sower/sowerApi.d.ts.map +1 -1
  81. package/dist/dts/features/sower/sowerJobDatetime.d.ts.map +1 -1
  82. package/dist/dts/features/submission/authMappingUtils.d.ts.map +1 -1
  83. package/dist/dts/features/submission/submissionApi.d.ts +481 -481
  84. package/dist/dts/features/updateStudyInMDS/mdsApi.d.ts +54 -54
  85. package/dist/dts/features/updateStudyInMDS/updateStudyInMDS.d.ts.map +1 -1
  86. package/dist/dts/features/user/externalLoginsSlice.d.ts +241 -241
  87. package/dist/dts/features/user/externalLoginsSlice.d.ts.map +1 -1
  88. package/dist/dts/features/user/hooks.d.ts +1 -1
  89. package/dist/dts/features/user/hooks.d.ts.map +1 -1
  90. package/dist/dts/features/user/userSlice.d.ts +2 -2
  91. package/dist/dts/features/user/userSlice.d.ts.map +1 -1
  92. package/dist/dts/features/user/userSliceRTK.d.ts +672 -692
  93. package/dist/dts/features/user/userSliceRTK.d.ts.map +1 -1
  94. package/dist/dts/features/user/utils.d.ts.map +1 -1
  95. package/dist/dts/features/workspace/jegKernelSlice.d.ts +83 -83
  96. package/dist/dts/features/workspace/jegWorkspaceSlice.d.ts.map +1 -1
  97. package/dist/dts/features/workspace/tieredWorkspaceSlice.d.ts.map +1 -1
  98. package/dist/dts/features/workspace/utils.d.ts.map +1 -1
  99. package/dist/dts/features/workspace/workspaceApi.d.ts +939 -939
  100. package/dist/dts/features/workspace/workspaceSlice.d.ts.map +1 -1
  101. package/dist/dts/hooks.d.ts +1 -1
  102. package/dist/dts/reducers.d.ts +24 -24
  103. package/dist/dts/storage-persist.d.ts.map +1 -1
  104. package/dist/dts/store.d.ts +3 -3
  105. package/dist/dts/store.d.ts.map +1 -1
  106. package/dist/dts/types/types.d.ts.map +1 -1
  107. package/dist/dts/utils/caseConversion.d.ts.map +1 -1
  108. package/dist/dts/utils/conversions.d.ts.map +1 -1
  109. package/dist/dts/utils/extractvalues.d.ts.map +1 -1
  110. package/dist/dts/utils/fetch.d.ts +2 -2
  111. package/dist/dts/utils/fetch.d.ts.map +1 -1
  112. package/dist/dts/utils/time.d.ts.map +1 -1
  113. package/dist/dts/utils/ts-utils.d.ts.map +1 -1
  114. package/dist/dts/utils/url.d.ts.map +1 -1
  115. package/dist/esm/index.js +2124 -2154
  116. package/dist/esm/index.js.map +1 -1
  117. package/dist/index.d.ts +8363 -8394
  118. package/package.json +4 -4
package/dist/esm/index.js CHANGED
@@ -6,19 +6,19 @@ import { QueryStatus, setupListeners } from '@reduxjs/toolkit/query';
6
6
  import { createApi, fetchBaseQuery, buildCreateApi, coreModule, reactHooksModule } from '@reduxjs/toolkit/query/react';
7
7
  import { getCookie } from 'cookies-next';
8
8
  import { GraphQLError, parse } from 'graphql';
9
- import { JSONPath } from 'jsonpath-plus';
10
9
  import { isEqual, toString } from 'lodash';
11
10
  import { customAlphabet } from 'nanoid';
12
11
  import { persistReducer, FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER, persistStore } from 'redux-persist';
13
12
  import createWebStorage from 'redux-persist/lib/storage/createWebStorage';
14
13
  import useSWR from 'swr';
15
- import { flatten } from 'flat';
16
- import Papa from 'papaparse';
14
+ import { JSONPath } from 'jsonpath-plus';
17
15
  import { PersistGate } from 'redux-persist/integration/react';
18
16
  import { openDB } from 'idb';
19
17
  import { useDeepCompareMemo } from 'use-deep-compare';
20
18
  import { v5 } from 'uuid';
21
19
  import { CookiesProvider } from 'react-cookie';
20
+ import { flatten } from 'flat';
21
+ import Papa from 'papaparse';
22
22
  import Queue from 'queue';
23
23
 
24
24
  const GEN3_COMMONS_NAME = process.env.NEXT_PUBLIC_GEN3_COMMONS_NAME || 'gen3';
@@ -658,1054 +658,931 @@ const { selectById: selectCartItem, selectIds: selectCartItems, selectAll: selec
658
658
  const cartReducerPath = 'cart';
659
659
 
660
660
  /**
661
- * @description Creates a externalLoginApi for listing the configured identity providers
662
- * in workspace token service. Includes user token expiration time.
663
- * @see https://github.com/uc-cdis/workspace-token-service/tree/master
664
- */ const externalLoginApi = gen3Api.injectEndpoints({
665
- endpoints: (builder)=>({
666
- getExternalLogins: builder.query({
667
- query: ()=>({
668
- url: `${GEN3_WTS_API}/external_oidc/`
669
- })
670
- }),
671
- isExternalConnected: builder.query({
672
- query: (idp)=>({
673
- url: `${GEN3_WTS_API}/oauth2/connected?idp=${idp}`
674
- }),
675
- transformResponse: ()=>{
676
- return true; // if success then connected is true
677
- }
678
- })
679
- })
680
- });
681
- const { useGetExternalLoginsQuery, useLazyGetExternalLoginsQuery, useLazyIsExternalConnectedQuery, useIsExternalConnectedQuery } = externalLoginApi;
682
-
683
- /**
684
- * Custom error class for resolving DRS object ID errors
685
- */ class ResolveDRSError extends Error {
686
- constructor(message){
687
- super(message);
688
- this.name = 'ResolveDRSError';
689
- }
690
- }
691
- /**
692
- * Resolves DRS object ID from a list of guids using dataguids.org
693
- * Use of this function should be limited
694
- * @param guidsForHostnameResolution
695
- */ const resolveDRSWithDataGUISOrg = async (guidsForHostnameResolution)=>{
696
- const INVALID_INPUT_ERROR = 'Invalid input: guidsForHostnameResolution must be an array of strings';
697
- const RESOLVE_DRS_ERROR = 'Failed to resolve DRS object ID';
698
- if (!Array.isArray(guidsForHostnameResolution) || !guidsForHostnameResolution.every((guid)=>typeof guid === 'string')) {
699
- throw new Error(INVALID_INPUT_ERROR);
700
- }
701
- try {
702
- const responses = await Promise.allSettled(guidsForHostnameResolution.map(async (guid)=>{
703
- try {
704
- const response = await fetch(`https://dataguids.org/index/${guid}`);
705
- if (response.status === 200) {
706
- try {
707
- return await response.json();
708
- // oxlint-disable-next-line no-unused-vars
709
- } catch (error) {
710
- throw new ResolveDRSError('Failed to parse response as JSON');
661
+ * Creates a base class core API for guppy API calls.
662
+ * @returns: guppy core API with guppyAPIFetch base query
663
+ */ const guppyApi = createApi({
664
+ reducerPath: 'guppy',
665
+ // TODO: refactor to use fetchBaseQuery
666
+ baseQuery: async (query, api)=>{
667
+ const csrfToken = selectCSRFToken(api.getState());
668
+ let accessToken = undefined;
669
+ if (process.env.NODE_ENV === 'development') {
670
+ // NOTE: This cookie can only be accessed from the client side
671
+ // in development mode. Otherwise, the cookie is set as httpOnly
672
+ accessToken = getCookie('credentials_token');
673
+ }
674
+ const headers = {
675
+ Accept: 'application/json',
676
+ 'Content-Type': 'application/json',
677
+ ...csrfToken && {
678
+ 'X-CSRF-Token': csrfToken
679
+ },
680
+ ...accessToken && {
681
+ Authorization: `Bearer ${accessToken}`
682
+ }
683
+ };
684
+ try {
685
+ const response = await fetch(`${GEN3_GUPPY_API}/graphql`, {
686
+ headers: headers,
687
+ method: 'POST',
688
+ body: JSON.stringify(query)
689
+ });
690
+ const data = await response.json();
691
+ // Check the response for GraphQL errors
692
+ if (data && 'errors' in data) {
693
+ // Return an object with an 'error' property if GraphQL errors are present
694
+ return {
695
+ error: {
696
+ message: data.errors[0].message,
697
+ locations: data.errors[0].locations
711
698
  }
712
- } else {
713
- throw new ResolveDRSError(RESOLVE_DRS_ERROR);
714
- }
715
- // oxlint-disable-next-line no-unused-vars
716
- } catch (error) {
717
- throw new ResolveDRSError(RESOLVE_DRS_ERROR);
699
+ };
718
700
  }
719
- }));
720
- return responses.reduce((acc, response, index)=>{
721
- if (response.status === 'fulfilled' && response.value.data) {
701
+ return {
702
+ data
703
+ };
704
+ } catch (e) {
705
+ if (e instanceof GraphQLError) {
722
706
  return {
723
- ...acc,
724
- [guidsForHostnameResolution[index]]: response.value.data
707
+ error: {
708
+ message: e.message,
709
+ locations: e.locations,
710
+ path: e.path
711
+ }
725
712
  };
726
- } else {
727
- throw new ResolveDRSError(RESOLVE_DRS_ERROR);
728
713
  }
729
- }, {});
730
- // oxlint-disable-next-line no-unused-vars
731
- } catch (error) {
732
- throw new ResolveDRSError(RESOLVE_DRS_ERROR);
733
- }
734
- };
735
-
736
- const resolveCachedDRS = async (guidsForHostnameResolution)=>{
737
- const state = coreStore.getState();
738
- const resolvedIds = {};
739
- for (const guid of guidsForHostnameResolution){
740
- const hostname = drsHostnamesSelector(guid, state);
741
- if (hostname) {
742
- resolvedIds[guid] = hostname;
714
+ if (e instanceof Error) return {
715
+ error: e.message
716
+ };
717
+ return {
718
+ error: e
719
+ };
743
720
  }
721
+ },
722
+ endpoints: ()=>({})
723
+ });
724
+ const guppyAPISliceMiddleware = guppyApi.middleware;
725
+ const guppyApiSliceReducerPath = guppyApi.reducerPath;
726
+ const guppyApiReducer = guppyApi.reducer;
727
+
728
+ const isFilterSet = (input)=>{
729
+ if (typeof input !== 'object' || input === null) {
730
+ return false;
744
731
  }
745
- return resolvedIds;
732
+ const { root, mode } = input;
733
+ if (typeof root !== 'object' || root === null) {
734
+ return false;
735
+ }
736
+ if (![
737
+ 'and',
738
+ 'or'
739
+ ].includes(mode)) {
740
+ return false;
741
+ }
742
+ return true;
746
743
  };
747
-
748
- const DRS_Resolvers = {
749
- dataGUISOrg: resolveDRSWithDataGUISOrg,
750
- cached: resolveCachedDRS
744
+ /**
745
+ * Type guard for Includes
746
+ * @param o - operator to check
747
+ * @category Filters
748
+ */ const isIncludes = (o)=>o.operator === 'includes' || o.operator === 'in';
749
+ const isUnion = (value)=>{
750
+ return typeof value === 'object' && value !== null && value.operator === 'or' && Array.isArray(value.operands);
751
751
  };
752
- // TODO - add to config file for DRS resolver
753
- const resolveDRSObjectId = (guidsForHostnameResolution, resolver = "cached")=>{
754
- return DRS_Resolvers[resolver](guidsForHostnameResolution);
752
+ const isIntersection = (value)=>{
753
+ return typeof value === 'object' && value !== null && value.operator === 'and' && Array.isArray(value.operands);
755
754
  };
756
-
757
- const isNotDefined = (x)=>{
758
- return x === undefined || x === null || x === void 0;
755
+ /**
756
+ * Type guard for Union or Intersection
757
+ * @param o - operator to check
758
+ * @category Filters
759
+ */ const isIntersectionOrUnion = (o)=>o.operator === 'and' || o.operator === 'or';
760
+ const isOperandsType = (operation)=>{
761
+ return operation?.operands !== undefined;
759
762
  };
760
- const isObject = (x)=>{
761
- return typeof x === 'object';
763
+ const ifOperationWithField = (operation)=>{
764
+ return operation.field !== undefined;
762
765
  };
763
- const isArray = (x)=>{
764
- return Array.isArray(x);
766
+ const isNestedFilter = (operation)=>{
767
+ return operation.operator === 'nested';
765
768
  };
766
- const isString = (x)=>{
767
- return typeof x === 'string';
769
+ const isIndexedFilterSetEmpty = (filters)=>Object.values(filters).every((filterSet)=>Object.keys(filterSet).length === 0);
770
+ const EmptyFilterSet = {
771
+ mode: 'and',
772
+ root: {}
768
773
  };
769
774
 
770
- /**
771
- * Prepares a URL for downloading by appending '/download' to the provided apiUrl.
772
- *
773
- * @param {string} apiUrl - The base URL to be used for preparing the download URL.
774
- * @returns {URL} - The prepared download URL as a URL object.
775
- */ const prepareUrl$1 = (apiUrl)=>new URL(apiUrl + '/download');
776
-
777
- var HttpMethod = /*#__PURE__*/ function(HttpMethod) {
778
- HttpMethod["GET"] = "GET";
779
- HttpMethod["POST"] = "POST";
780
- HttpMethod["PUT"] = "PUT";
781
- HttpMethod["DELETE"] = "DELETE";
782
- return HttpMethod;
783
- }({});
784
- const DEFAULT_METHOD = "GET";
785
- const CONTENT_TYPE_HEADER = 'Content-Type';
786
- const CONTENT_TYPE_JSON = 'application/json';
787
- const HTTPErrorMessages = {
788
- // 4xx Client Errors
789
- 400: 'Bad Request',
790
- 401: 'Unauthorized',
791
- 402: 'Payment Required',
792
- 403: 'Forbidden',
793
- 404: 'Not Found',
794
- 405: 'Method Not Allowed',
795
- 406: 'Not Acceptable',
796
- 407: 'Proxy Authentication Required',
797
- 408: 'Request Timeout',
798
- 409: 'Conflict',
799
- 410: 'Gone',
800
- 411: 'Length Required',
801
- 412: 'Precondition Failed',
802
- 413: 'Payload Too Large',
803
- 414: 'URI Too Long',
804
- 415: 'Unsupported Media Type',
805
- 416: 'Range Not Satisfiable',
806
- 417: 'Expectation Failed',
807
- 418: "I'm a teapot",
808
- 421: 'Misdirected Request',
809
- 422: 'Unprocessable Entity',
810
- 423: 'Locked',
811
- 424: 'Failed Dependency',
812
- 425: 'Too Early',
813
- 426: 'Upgrade Required',
814
- 428: 'Precondition Required',
815
- 429: 'Too Many Requests',
816
- 431: 'Request Header Fields Too Large',
817
- 451: 'Unavailable For Legal Reasons',
818
- // 5xx Server Errors
819
- 500: 'Internal Server Error',
820
- 501: 'Not Implemented',
821
- 502: 'Bad Gateway',
822
- 503: 'Service Unavailable',
823
- 504: 'Gateway Timeout',
824
- 505: 'HTTP Version Not Supported',
825
- 506: 'Variant Also Negotiates',
826
- 507: 'Insufficient Storage',
827
- 508: 'Loop Detected',
828
- 510: 'Not Extended',
829
- 511: 'Network Authentication Required'
830
- };
831
- /**
832
- * Represents an error that occurs during an HTTP request.
833
- * Extends the built-in `Error` class to provide additional information
834
- * about the HTTP status code and optional response data.
835
- */ class HTTPError extends Error {
836
- constructor(status, message, responseData){
837
- super(message), this.status = status, this.responseData = responseData;
838
- this.name = 'HTTPError';
839
- }
840
- }
841
- const fetchFencePresignedURL = async ({ guid, method = 'GET', onAbort = ()=>null, signal = undefined })=>{
842
- const csrfToken = selectCSRFToken(coreStore.getState());
843
- const headers = new Headers();
844
- headers.set('Content-Type', 'application/json');
845
- if (process.env.NODE_ENV === 'development') {
846
- // NOTE: This cookie can only be accessed from the client side
847
- // in development mode. Otherwise, the cookie is set as httpOnly
848
- const accessToken = getCookie('credentials_token');
849
- if (accessToken) {
850
- headers.set('Authorization', `Bearer ${accessToken}`);
851
- }
852
- }
853
- if (csrfToken) headers.set('X-CSRF-Token', csrfToken);
854
- const url = `${GEN3_FENCE_API}/data/download/${guid}`;
855
- try {
856
- const response = await fetch(url, {
857
- method: method,
858
- headers: headers,
859
- ...signal ? {
860
- signal: signal
861
- } : {}
862
- });
863
- // Check if the response is ok before proceeding
864
- if (!response.ok) {
865
- let errorMessage;
866
- let errorData;
867
- try {
868
- // Attempt to parse error response as JSON
869
- errorData = await response.json();
870
- errorMessage = errorData.message || response.statusText;
871
- } catch {
872
- // If JSON parsing fails, use status text
873
- errorMessage = response.statusText;
874
- }
875
- throw new HTTPError(response.status, errorMessage, errorData);
876
- }
877
- return (await response.json())['url'];
878
- } catch (error) {
879
- if (error instanceof Error) {
880
- if (error.name === 'AbortError') {
881
- onAbort?.();
882
- }
883
- }
884
- throw error;
885
- }
886
- };
887
- /**
888
- * Retrieves a CSRF token from the server.
889
- *
890
- * This asynchronous function sends a GET request to the server's status endpoint
891
- * to fetch the CSRF token in the response. The token is expected to be included
892
- * in the JSON response under the `csrf_token` field.
893
- *
894
- * @returns {Promise<string | null>} A promise that resolves to the CSRF token as a string if successfully retrieved,
895
- * or null if the token is not present in the response.
896
- * @throws {HTTPError} Throws an HTTPError if the server response is not successful.
897
- */ const getCSRFToken = async ()=>{
898
- const requestHeaders = new Headers({
899
- [CONTENT_TYPE_HEADER]: CONTENT_TYPE_JSON
900
- });
901
- const response = await fetch(`${GEN3_API}/_status`, {
902
- headers: requestHeaders
903
- });
904
- if (!response.ok) {
905
- throw new HTTPError(response.status, response.statusText);
906
- }
907
- const { csrf: csrfToken } = await response.json();
908
- return csrfToken || null;
909
- };
910
- /**
911
- * Fetches JSON data from a specified URL using the Fetch API.
912
- *
913
- * @param {string} url - The URL to fetch the JSON data from.
914
- * @param {boolean} [requiresCSRF=false] - Indicates whether a CSRF token is required for the request.
915
- * If true, the CSRF token will be added to the request headers.
916
- * @param {string} [method=DEFAULT_METHOD] - The HTTP method to use for the request (e.g., 'GET', 'POST').
917
- * @param {unknown} [body=undefined] - The request body to send, applicable when using methods like 'POST'.
918
- *
919
- * @param signal - Abort fetch
920
- * @returns {Promise<T>} A promise that resolves to the parsed JSON data from the response.
921
- *
922
- * @throws {HTTPError} Throws an error if the HTTP response status indicates a failure.
923
- */ const fetchJSONDataFromURL = async (url, requiresCSRF = false, method = DEFAULT_METHOD, body = undefined, signal)=>{
924
- const requestHeaders = new Headers({
925
- [CONTENT_TYPE_HEADER]: CONTENT_TYPE_JSON
926
- });
927
- if (requiresCSRF) {
928
- const csrfToken = await getCSRFToken();
929
- if (csrfToken) {
930
- requestHeaders.set('X-CSRF-Token', csrfToken);
931
- }
932
- }
933
- if (process.env.NODE_ENV === 'development') {
934
- const accessToken = getCookie('credentials_token');
935
- if (accessToken) {
936
- requestHeaders.set('Authorization', `Bearer ${accessToken}`);
937
- }
938
- }
939
- const response = await fetch(url, {
940
- method,
941
- headers: requestHeaders,
942
- body: [
943
- 'POST',
944
- 'PUT',
945
- 'PATCH'
946
- ].includes(method.toUpperCase()) ? body : undefined,
947
- ...signal ? {
948
- signal: signal
949
- } : {}
950
- });
951
- if (!response.ok) {
952
- const errorBody = await response.json();
953
- throw new HTTPError(response.status, response.statusText, errorBody);
954
- }
955
- if (response.status === 204) {
956
- // no content so returns null
957
- return null;
958
- }
959
- return await response.json();
775
+ const isOperationWithField = (operation)=>{
776
+ return operation?.field !== undefined;
960
777
  };
961
-
962
- const extractValuesFromObject = (jsonPathMappings, obj)=>{
963
- const result = {};
964
- const extractObjectValue = (jsonPath, obj)=>{
965
- const extractedValues = JSONPath({
966
- path: jsonPath,
967
- json: obj
968
- });
969
- return extractedValues.length > 0 ? extractedValues[0] : undefined;
970
- };
971
- for(const key in jsonPathMappings){
972
- if (key in Object.keys(jsonPathMappings)) {
973
- // Extract value from an object and store it in the result.
974
- result[key] = extractObjectValue(jsonPathMappings[key], obj);
975
- }
778
+ const isOperatorWithFieldAndArrayOfOperands = (operation)=>{
779
+ 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
780
+ ) {
781
+ const { operator } = operation;
782
+ return operator === 'in' || operator === 'includes' || operator === 'excludes' || operator === 'excludeifany';
976
783
  }
977
- return result;
978
- };
979
- const ExtractValueFromObject = (obj, key, valueIfNotFound)=>{
980
- return obj?.[key] ?? valueIfNotFound;
981
- };
982
-
983
- // type guard functions
984
- const isHistogramRangeData = (key)=>{
985
- return Array.isArray(key) && key.length === 2 && key.every((item)=>typeof item === 'number');
986
- };
987
- const isJSONObject = (data)=>{
988
- return typeof data === 'object' && data !== null && !Array.isArray(data);
784
+ return false;
989
785
  };
990
- const isJSONValue = (data)=>{
991
- return typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean' || Array.isArray(data) && data.every(isJSONValue) || isJSONObject(data);
786
+ const extractFilterValue = (op)=>{
787
+ const valueExtractorHandler = new ValueExtractorHandler();
788
+ return handleOperation(valueExtractorHandler, op);
992
789
  };
993
- const isJSONValueArray = (data)=>{
994
- return Array.isArray(data) && data.every(isJSONValue);
790
+ const extractEnumFilterValue = (op)=>{
791
+ const enumValueExtractorHandler = new EnumValueExtractorHandler();
792
+ const results = handleOperation(enumValueExtractorHandler, op);
793
+ return results ?? [];
995
794
  };
996
- const isValidObject = (input)=>typeof input === 'object' && input !== null;
997
- const isHistogramData = (data)=>{
998
- return isValidObject(data) && 'key' in data && 'count' in data;
795
+ const assertNever = (x)=>{
796
+ throw Error(`Exhaustive comparison did not handle: ${x}`);
999
797
  };
1000
- const isHistogramDataArray = (input)=>{
1001
- if (!isValidObject(input) || !Array.isArray(input.histogram)) {
1002
- return false;
798
+ const handleOperation = (handler, op)=>{
799
+ switch(op.operator){
800
+ case '=':
801
+ return handler.handleEquals(op);
802
+ case '!=':
803
+ return handler.handleNotEquals(op);
804
+ case '<':
805
+ return handler.handleLessThan(op);
806
+ case '<=':
807
+ return handler.handleLessThanOrEquals(op);
808
+ case '>':
809
+ return handler.handleGreaterThan(op);
810
+ case '>=':
811
+ return handler.handleGreaterThanOrEquals(op);
812
+ case 'and':
813
+ return handler.handleIntersection(op);
814
+ case 'or':
815
+ return handler.handleUnion(op);
816
+ case 'nested':
817
+ return handler.handleNestedFilter(op);
818
+ case 'in':
819
+ case 'includes':
820
+ return handler.handleIncludes(op);
821
+ case 'excludeifany':
822
+ return handler.handleExcludeIfAny(op);
823
+ case 'excludes':
824
+ return handler.handleExcludes(op);
825
+ case 'exists':
826
+ return handler.handleExists(op);
827
+ case 'missing':
828
+ return handler.handleMissing(op);
829
+ default:
830
+ return assertNever(op);
1003
831
  }
1004
- return input.histogram.every(isHistogramData);
1005
- };
1006
- const isHistogramDataCollection = (obj)=>{
1007
- return isValidObject(obj) && 'histogram' in obj && isHistogramData(obj.histogram);
1008
832
  };
1009
- // Type guard function for GuppyAggregationData interface
1010
- const isGuppyAggregationData = (obj)=>{
1011
- if (!isValidObject(obj)) return false;
1012
- for(const key in obj){
1013
- if (!isHistogramDataCollection(obj[key])) {
1014
- return false;
1015
- }
1016
- }
1017
- return true;
833
+ /**
834
+ * Return true if a FilterSet's root value is an empty object
835
+ * @param fs - FilterSet to test
836
+ */ const isFilterEmpty = (fs)=>isEqual({}, fs);
837
+ /**
838
+ * Type guard to check if an object is a GQLIntersection
839
+ * @param value - The value to check
840
+ * @returns True if the value is a GQLIntersection
841
+ */ const isGQLIntersection = (value)=>{
842
+ return typeof value === 'object' && value !== null && 'and' in value && Array.isArray(value.and);
1018
843
  };
1019
- const isHistogramDataAnEnum = (data)=>{
1020
- return typeof data === 'object' && data !== null && 'key' in data && 'count' in data && typeof data.key === 'string' && typeof data.count === 'number';
844
+ /**
845
+ * Type guard to check if an object is a GQLIntersection
846
+ * @param value - The value to check
847
+ * @returns True if the value is a GQLIntersection
848
+ */ const isGQLUnion = (value)=>{
849
+ return typeof value === 'object' && value !== null && 'or' in value && Array.isArray(value.or);
1021
850
  };
1022
- const isStatsValue = (item)=>{
1023
- if (typeof item !== 'object' || item === null) {
1024
- return false;
1025
- }
1026
- const obj = item;
1027
- // Check that all present properties have correct types
1028
- const numericFields = [
1029
- 'count',
1030
- 'min',
1031
- 'max',
1032
- 'avg',
1033
- 'sum',
1034
- 'stddev',
1035
- 'median'
1036
- ];
1037
- if (!numericFields.some((field)=>field in obj && typeof obj[field] !== 'number')) {
1038
- return false;
1039
- }
1040
- // Check percentiles structure if present
1041
- if ('percentiles' in obj) {
1042
- const percentiles = obj.percentiles;
1043
- if (typeof percentiles !== 'object' || percentiles === null) {
1044
- return false;
1045
- }
851
+ class ToGqlHandler {
852
+ constructor(){
853
+ this.handleEquals = (op)=>({
854
+ '=': {
855
+ [op.field]: op.operand
856
+ }
857
+ });
858
+ this.handleNotEquals = (op)=>({
859
+ '!=': {
860
+ [op.field]: op.operand
861
+ }
862
+ });
863
+ this.handleLessThan = (op)=>({
864
+ '<': {
865
+ [op.field]: op.operand
866
+ }
867
+ });
868
+ this.handleLessThanOrEquals = (op)=>({
869
+ '<=': {
870
+ [op.field]: op.operand
871
+ }
872
+ });
873
+ this.handleGreaterThan = (op)=>({
874
+ '>': {
875
+ [op.field]: op.operand
876
+ }
877
+ });
878
+ this.handleGreaterThanOrEquals = (op)=>({
879
+ '>=': {
880
+ [op.field]: op.operand
881
+ }
882
+ });
883
+ this.handleIncludes = (op)=>({
884
+ in: {
885
+ [op.field]: op.operands
886
+ }
887
+ });
888
+ this.handleExcludes = (op)=>({
889
+ exclude: {
890
+ [op.field]: op.operands
891
+ }
892
+ });
893
+ this.handleExcludeIfAny = (op)=>({
894
+ excludeifany: {
895
+ [op.field]: op.operands
896
+ }
897
+ });
898
+ this.handleIntersection = (op)=>({
899
+ and: op.operands.map((x)=>convertFilterToGqlFilter(x))
900
+ });
901
+ this.handleUnion = (op)=>({
902
+ or: op.operands.map((x)=>convertFilterToGqlFilter(x))
903
+ });
904
+ this.handleMissing = (op)=>({
905
+ is: {
906
+ [op.field]: 'MISSING'
907
+ }
908
+ });
909
+ this.handleExists = (op)=>({
910
+ not: {
911
+ [op.field]: op?.operand ?? null
912
+ }
913
+ });
914
+ this.handleNestedFilter = (op)=>{
915
+ const child = convertFilterToGqlFilter(op.operand);
916
+ return {
917
+ nested: {
918
+ path: op.path,
919
+ ...child
920
+ }
921
+ };
922
+ };
1046
923
  }
1047
- return true;
1048
- };
1049
- const isStatsValuesArray = (data)=>{
1050
- return Array.isArray(data) && data.every(isStatsValue);
1051
- };
1052
- const isHistogramDataAArray = (data)=>{
1053
- return Array.isArray(data) && data.every(isHistogramData);
1054
- };
1055
- const isHistogramDataArrayAnEnum = (data)=>{
1056
- return Array.isArray(data) && data.every(isHistogramDataAnEnum);
1057
- };
1058
- const isHistogramDataArrayARange = (data)=>{
1059
- return Array.isArray(data) && data.every((item)=>isHistogramRangeData(item.key));
1060
- };
1061
- /**
1062
- * Type predicate to narrow an unknown error to `FetchBaseQueryError`
1063
- */ function isFetchBaseQueryError(error) {
1064
- return typeof error === 'object' && error != null && 'status' in error;
1065
- }
1066
- /**
1067
- * Type predicate to narrow an unknown error to an object with a string 'message' property
1068
- */ function isErrorWithMessage(error) {
1069
- return typeof error === 'object' && error != null && 'message' in error && typeof error.message === 'string';
1070
- }
1071
- function isHttpStatusError(error) {
1072
- return typeof error === 'object' && error != null && 'status' in error && typeof error.status === 'number';
1073
- }
1074
- /**
1075
- * Type predicate to narrow an unknown error to an object with a string 'message' property
1076
- */ function isFetchParseError(error) {
1077
- return typeof error === 'object' && error != null && 'originalStatus' in error && 'status' in error && error['status'] === 'PARSING_ERROR';
1078
- }
1079
- /**
1080
- * Type predicate to narrow an unknown error to a `SerializedError`
1081
- */ function isSerializedError(error) {
1082
- return typeof error === 'object' && error != null && !('status' in error) && ('message' in error || 'name' in error || 'code' in error || 'stack' in error);
1083
924
  }
1084
-
1085
- const HTTPUserFriendlyErrorMessages = {
1086
- // 4xx Client Errors
1087
- 400: 'Sorry, there was a problem with your request. Please check the information you provided and try again.',
1088
- 401: 'Please sign in to access this content. If you need to create an account, please contact support.',
1089
- 402: 'This feature requires payment to continue. Please update your subscription.',
1090
- 403: "You don't have permission to access this. If you think this is a mistake, please contact support.",
1091
- 404: "We couldn't find what you're looking for. The link might be broken or the content may have been moved.",
1092
- 405: 'This action is not supported. Please try a different approach.',
1093
- 406: "Your browser sent a request that this server couldn't understand. Try updating your browser.",
1094
- 407: 'Please authenticate with the network proxy first.',
1095
- 408: 'The request took too long to complete. Please check your internet connection and try again.',
1096
- 409: 'There was a conflict with your request. Someone might have modified the same content.',
1097
- 410: 'This content has been permanently removed and is no longer available.',
1098
- 411: 'There was a technical problem with your request. Please try again.',
1099
- 412: "Some conditions for this request weren't met. Please try again.",
1100
- 413: "The file you're trying to upload is too large. Please choose a smaller file.",
1101
- 414: 'The web address is too long. Please try a shorter URL.',
1102
- 415: 'This type of file is not supported. Please try a different format.',
1103
- 416: 'The requested content range is not available.',
1104
- 417: "Your request couldn't be completed as expected. Please try again.",
1105
- 418: 'Unusual error occurred. Please try again or contact support if the problem persists.',
1106
- 421: 'Your request was sent to the wrong server. Please try again.',
1107
- 422: "We couldn't process your request. Please check the information and try again.",
1108
- 423: 'This resource is temporarily locked. Please try again in a few moments.',
1109
- 424: 'The request failed because it depends on another request that failed.',
1110
- 425: 'Your request was processed too early. Please try again in a moment.',
1111
- 426: 'You need to upgrade your browser or app to continue.',
1112
- 428: "Some required conditions weren't met. Please check all requirements and try again.",
1113
- 429: "You've made too many requests. Please wait a while before trying again.",
1114
- 431: 'Request failed because the headers are too large. Please simplify your request.',
1115
- 451: 'This content is not available for legal reasons.',
1116
- // 5xx Server Errors
1117
- 500: 'Something went wrong with the service. Please try again later.',
1118
- 501: "This feature isn't available yet. Check back later.",
1119
- 502: "We're having trouble connecting to our servers. Please try again in a few minutes.",
1120
- 503: "Our service is temporarily unavailable. We're working to restore it as quickly as possible.",
1121
- 504: 'The server took too long to respond. Please try again later.',
1122
- 505: "Your browser's HTTP version isn't supported. Please try updating your browser.",
1123
- 506: 'We encountered a configuration error.',
1124
- 507: "We're out of storage space.",
1125
- 508: 'We detected an infinite loop in our servers.',
1126
- 510: 'This server requires additional extensions to process your request.',
1127
- 511: 'Please sign in to the network before continuing.'
925
+ const convertFilterToGqlFilter = (filter)=>{
926
+ const handler = new ToGqlHandler();
927
+ return handleOperation(handler, filter);
1128
928
  };
1129
-
1130
- const MAX_ERROR_LENGTH = 256;
1131
- const DEFAULT_API_ERROR_MESSAGE = 'API Query Error';
1132
- /**
1133
- * Checks if a string appears to be an HTML page
1134
- */ function isHtmlContent(str) {
1135
- const trimmed = str.trim().toLowerCase();
1136
- return trimmed.startsWith('<!doctype html') || trimmed.startsWith('<html') || /<html[\s>]/i.test(str);
1137
- }
1138
- /**
1139
- * Sanitizes an error message - returns DEFAULT_API_ERROR_MESSAGE if
1140
- * the message is too long or contains HTML content
1141
- */ function sanitizeErrorMessage(message) {
1142
- if (!message) return DEFAULT_API_ERROR_MESSAGE;
1143
- if (message.length > MAX_ERROR_LENGTH || isHtmlContent(message)) {
1144
- return DEFAULT_API_ERROR_MESSAGE;
929
+ const convertFilterSetToGqlFilter = (fs, toplevelOp = 'and')=>{
930
+ const fsKeys = Object.keys(fs.root);
931
+ // if no keys return undefined
932
+ if (fsKeys.length === 0) return {
933
+ and: []
934
+ };
935
+ return toplevelOp === 'and' ? {
936
+ and: fsKeys.map((key)=>convertFilterToGqlFilter(fs.root[key]))
937
+ } : {
938
+ or: fsKeys.map((key)=>convertFilterToGqlFilter(fs.root[key]))
939
+ };
940
+ };
941
+ const handleGqlOperation = (handler, op)=>{
942
+ const operationKeys = Object.keys(op);
943
+ if (operationKeys.includes('=')) {
944
+ return handler.handleEquals(op);
1145
945
  }
1146
- return message;
1147
- }
1148
- /** Best-effort message extraction from an HTTP error body */ function extractMessage(data) {
1149
- if (typeof data === 'string') return data;
1150
- if (typeof data === 'object' && data != null) {
1151
- const obj = data;
1152
- // common API error shapes: { message }, { error }, { detail } (FastAPI)
1153
- for (const key of [
1154
- 'message',
1155
- 'error',
1156
- 'detail'
1157
- ]){
1158
- const val = obj[key];
1159
- if (typeof val === 'string') return val;
1160
- }
946
+ if (operationKeys.includes('!=')) {
947
+ return handler.handleNotEquals(op);
1161
948
  }
1162
- return undefined;
1163
- }
1164
- function normalizeRtkError(error) {
1165
- if (!error) {
1166
- return {
1167
- type: 'UNKNOWN_ERROR',
1168
- message: 'Unknown error'
1169
- };
949
+ if (operationKeys.includes('<')) {
950
+ return handler.handleLessThan(op);
1170
951
  }
1171
- if (isFetchBaseQueryError(error)) {
1172
- // status is number for HTTP errors, string literal for the others
1173
- if (typeof error.status === 'number') {
1174
- const extractedMessage = extractMessage(error.data);
1175
- return {
1176
- type: 'HTTP_ERROR',
1177
- status: error.status,
1178
- message: sanitizeErrorMessage(extractedMessage) !== DEFAULT_API_ERROR_MESSAGE ? sanitizeErrorMessage(extractedMessage) : HTTPUserFriendlyErrorMessages[error.status] ?? `Request failed with status ${error.status}`,
1179
- data: error.data
1180
- };
1181
- }
1182
- switch(error.status){
1183
- case 'FETCH_ERROR':
1184
- return {
1185
- type: 'FETCH_ERROR',
1186
- message: error.error
1187
- };
1188
- case 'PARSING_ERROR':
1189
- return {
1190
- type: 'PARSING_ERROR',
1191
- status: error.originalStatus,
1192
- message: HTTPUserFriendlyErrorMessages[error.originalStatus] ?? `Request failed with status ${error.originalStatus}`,
1193
- data: error.data
1194
- };
1195
- case 'TIMEOUT_ERROR':
1196
- return {
1197
- type: 'TIMEOUT_ERROR',
1198
- message: sanitizeErrorMessage(error.error)
1199
- };
1200
- case 'CUSTOM_ERROR':
1201
- return {
1202
- type: 'CUSTOM_ERROR',
1203
- message: sanitizeErrorMessage(error.error),
1204
- data: error.data
1205
- };
1206
- }
952
+ if (operationKeys.includes('<=')) {
953
+ return handler.handleLessThanOrEquals(op);
1207
954
  }
1208
- // SerializedError — a JS error thrown somewhere in the pipeline
1209
- if ('message' in error || 'name' in error) {
1210
- return {
1211
- type: 'SERIALIZED_ERROR',
1212
- message: sanitizeErrorMessage(error.message) || 'An error occurred'
1213
- };
955
+ if (operationKeys.includes('>')) {
956
+ return handler.handleGreaterThan(op);
1214
957
  }
1215
- return {
1216
- type: 'UNKNOWN_ERROR',
1217
- message: 'Unknown error'
1218
- };
1219
- }
1220
-
1221
- const DAYS_IN_YEAR = 365.25;
1222
- /**
1223
- * Converts HistogramData to HistogramDataAsStringKey by ensuring the key is a string.
1224
- * If the key is already a string, it's used as is.
1225
- * If the key is a number tuple [min, max], it's converted to a string in the format "min-max".
1226
- *
1227
- * @param data The HistogramData object to convert
1228
- * @returns HistogramDataAsStringKey with the converted key
1229
- */ const convertToHistogramDataAsStringKey = (data)=>{
1230
- return {
1231
- key: typeof data.key === 'string' ? data.key : `${data.key[0]}-${data.key[1]}`,
1232
- count: data.count
1233
- };
1234
- };
1235
- const calculatePercentageAsNumber = (count, total)=>count ? count / total * 100 : 0;
1236
- const calculatePercentageAsString = (count, total)=>`${(count / total * 100).toFixed(2)}%`;
1237
- const capitalize$1 = (original)=>{
1238
- const customCapitalizations = {
1239
- id: 'ID',
1240
- uuid: 'UUID',
1241
- dna: 'DNA',
1242
- dbsnp: 'dbSNP',
1243
- cosmic: 'COSMIC',
1244
- civic: 'CIViC',
1245
- dbgap: 'dbGaP',
1246
- ecog: 'ECOG',
1247
- bmi: 'BMI',
1248
- gdc: 'GDC',
1249
- cnv: 'CNV',
1250
- ssm: 'SSM',
1251
- aa: 'AA'
1252
- };
1253
- return original.split(' ').map((word)=>customCapitalizations[word.toLowerCase()] || `${word.charAt(0).toUpperCase()}${word.slice(1)}`).join(' ');
1254
- };
1255
- const humanify = ({ term = '', capitalize: cap = true, facetTerm = false })=>{
1256
- let original;
1257
- let humanified;
1258
- if (facetTerm) {
1259
- // Splits on capital letters followed by lowercase letters to find
1260
- // words squished together in a string.
1261
- original = term?.split(/(?=[A-Z][a-z])/).join(' ');
1262
- humanified = term?.replace(/\./g, ' ').replace(/_/g, ' ').trim();
1263
- } else {
1264
- const split = (original || term)?.split('.');
1265
- humanified = split[split.length - 1]?.replace(/_/g, ' ').trim();
1266
- // Special case 'name' to include any parent nested for sake of
1267
- // specificity in the UI
1268
- if (humanified === 'name' && split?.length > 1) {
1269
- humanified = `${split[split?.length - 2]} ${humanified}`;
1270
- }
958
+ if (operationKeys.includes('>=')) {
959
+ return handler.handleGreaterThanOrEquals(op);
1271
960
  }
1272
- return cap ? capitalize$1(humanified) : humanified;
1273
- };
1274
- /*https://github.com/NCI-GDC/portal-ui/blob/develop/src/packages/%40ncigdc/utils/ageDisplay.js*/ /**
1275
- * Converts age in days into a human-readable format.
1276
- *
1277
- * @param ageInDays - The age in days.
1278
- * @param yearsOnly - If true, only display years.
1279
- * @defaultValue false
1280
- * @param defaultValue - The default value to return if ageInDays is falsy.
1281
- * @defaultValue "--"
1282
- * @returns The formatted age string.
1283
- */ const ageDisplay = (ageInDays, yearsOnly = false, defaultValue = '--')=>{
1284
- if (ageInDays !== 0 && !ageInDays) {
1285
- return defaultValue;
961
+ if (operationKeys.includes('in')) {
962
+ return handler.handleIncludes(op);
1286
963
  }
1287
- const calculateYearsAndDays = (years, days)=>days === 365 ? [
1288
- years + 1,
1289
- 0
1290
- ] : [
1291
- years,
1292
- days
1293
- ];
1294
- const ABS_AGE_DAYS = Math.abs(ageInDays);
1295
- const [years, remainingDays] = calculateYearsAndDays(Math.floor(ABS_AGE_DAYS / DAYS_IN_YEAR), Math.ceil(ABS_AGE_DAYS % DAYS_IN_YEAR));
1296
- const formattedYears = years === 0 ? '' : `${years} ${years === 1 ? 'year' : 'years'}`;
1297
- const formattedDays = !yearsOnly && remainingDays > 0 ? `${remainingDays} ${remainingDays === 1 ? 'day' : 'days'}` : years === 0 && remainingDays === 0 ? '0 days' : '';
1298
- const ageString = [
1299
- formattedYears,
1300
- formattedDays
1301
- ].filter(Boolean).join(' ');
1302
- return ageInDays >= 0 ? ageString : `-${ageString}`;
1303
- };
1304
- /**
1305
- * Given an object of JSON, stringify it into a string.
1306
- * @param obj - the object to stringify
1307
- * @param defaults - the default value to return if the object is undefined
1308
- * @category Utility
1309
- */ const stringifyJSONParam = (obj, defaults = '{}')=>obj ? JSON.stringify(obj) : defaults;
1310
-
1311
- const queryWTSFederatedLoginStatus = async (signal)=>{
1312
- try {
1313
- const results = await fetchJSONDataFromURL(`${GEN3_WTS_API}/external_oidc/`, false, HttpMethod.GET, undefined, signal);
1314
- // Handle both null response and response without providers
1315
- if (!results || !results.providers) {
1316
- return {
1317
- providers: []
1318
- };
1319
- }
1320
- return results;
1321
- } catch (error) {
1322
- return {
1323
- providers: [],
1324
- error: error instanceof Error ? error : new Error('Unknown error')
1325
- };
964
+ if (operationKeys.includes('exclude')) {
965
+ return handler.handleExcludes(op);
1326
966
  }
967
+ if (operationKeys.includes('excludeifany')) {
968
+ return handler.handleExcludeIfAny(op);
969
+ }
970
+ if (operationKeys.includes('and')) {
971
+ return handler.handleIntersection(op);
972
+ }
973
+ if (operationKeys.includes('or')) {
974
+ return handler.handleUnion(op);
975
+ }
976
+ if (operationKeys.includes('nested')) {
977
+ return handler.handleNestedFilter(op);
978
+ }
979
+ if (operationKeys.includes('is')) {
980
+ return handler.handleExists(op);
981
+ }
982
+ if (operationKeys.includes('not')) {
983
+ return handler.handleMissing(op);
984
+ }
985
+ return assertNever(op);
1327
986
  };
1328
-
1329
- /**
1330
- * Input is a list of files selected for download.
1331
- * Output is a array of GUID prefixes
1332
- * @param selectedFiles
1333
- */ const gatherGUIDPrefixes = (selectedFiles)=>{
1334
- return selectedFiles.reduce((acc, selectedFile)=>{
1335
- if (selectedFile.object_id) {
1336
- const guidDomainPrefix = (selectedFile.object_id.match(GUID_PREFIX_PATTERN) || []).shift();
1337
- if (guidDomainPrefix) {
1338
- if (!acc.includes(guidDomainPrefix)) {
1339
- acc.push(guidDomainPrefix);
1340
- }
1341
- }
1342
- }
1343
- return acc;
1344
- }, []);
1345
- };
1346
- /**
1347
- * Resolves DRS object ID from a list of guids using dataguids.org or other DRS resolver
1348
- * @param guidPrefixes
1349
- */ const resolveDRSPrefixes = async (guidPrefixes)=>{
1350
- return await resolveDRSObjectId(guidPrefixes);
987
+ const convertGqlFilterToFilter = (gqlFilter)=>{
988
+ const handler = new ToOperationHandler();
989
+ return handleGqlOperation(handler, gqlFilter);
1351
990
  };
1352
991
  /**
1353
- * Given a list of object ids, extracts and DRS prefixes and resolves them to DRS server hostnames
1354
- * then returns a list of resolved GUIDs, and a list of unresolved GUIDs
1355
- * @param selectedFiles
1356
- * @returns - {
1357
- * resolvedDRSPrefixes: Record<string, string> - map of GUID prefixes to DRS server hostnames
1358
- * resolvedFiles: ResolvedGUID[] - list of resolved GUIDs
1359
- * unresolvedFiles: ResolvedGUID[] - list of unresolved GUIDs
1360
- * }
1361
- */ const resolveGUIDsInSelectedFiles = async (selectedFiles)=>{
1362
- if (!selectedFiles || selectedFiles.length === 0) {
1363
- return {
1364
- externalHosts: {},
1365
- resolvedFiles: [],
1366
- unresolvedFiles: []
1367
- };
1368
- }
1369
- const guidPrefixes = gatherGUIDPrefixes(selectedFiles);
1370
- const resolvedDRSPrefixes = await resolveDRSPrefixes(guidPrefixes);
1371
- const resolvedIds = [];
1372
- const unresolvedIds = [];
1373
- selectedFiles.forEach((selectedFile)=>{
1374
- if (selectedFile.object_id) {
1375
- const guidDomainPrefix = (selectedFile.object_id.match(GUID_PREFIX_PATTERN) || []).shift();
1376
- if (guidDomainPrefix && resolvedDRSPrefixes[guidDomainPrefix]) {
1377
- resolvedIds.push({
1378
- ...selectedFile,
1379
- drsPrefix: guidDomainPrefix,
1380
- url: resolvedDRSPrefixes[guidDomainPrefix]
1381
- });
1382
- }
1383
- } else {
1384
- unresolvedIds.push(selectedFile);
1385
- }
1386
- });
1387
- return {
1388
- externalHosts: resolvedDRSPrefixes,
1389
- resolvedFiles: resolvedIds,
1390
- unresolvedFiles: unresolvedIds
1391
- };
1392
- };
1393
- const fetchExternalLogins = async (externalProviders, selectedFiles)=>{
1394
- const providers = externalProviders ?? [];
1395
- // find all the providers that do not have tokens
1396
- const unauthenticatedProviders = providers.filter((provider)=>!provider.refresh_token_expiration);
1397
- try {
1398
- const guidResolutions = await resolveGUIDsInSelectedFiles(selectedFiles);
1399
- const providersToAuthenticate = unauthenticatedProviders.filter((unauthenticatedProvider)=>Object.values(guidResolutions.externalHosts).includes(new URL(unauthenticatedProvider.base_url).hostname));
1400
- const missingProviders = providersToAuthenticate.filter((provider)=>!Object.values(guidResolutions.externalHosts).includes(new URL(provider.base_url).hostname));
1401
- return {
1402
- providersToAuthenticate: providersToAuthenticate,
1403
- missingProviders: missingProviders
992
+ * Convert GQL to Filterset
993
+ * Note assumes all GqlOperators have one field: value
994
+ */ class ToOperationHandler {
995
+ constructor(){
996
+ this.handleEquals = (op)=>{
997
+ const [field, value] = Object.entries(op['='])[0];
998
+ return {
999
+ operator: '=',
1000
+ field: field,
1001
+ operand: value
1002
+ };
1404
1003
  };
1405
- } catch (error) {
1406
- if (error instanceof Error) {
1004
+ this.handleNotEquals = (op)=>{
1005
+ const [field, value] = Object.entries(op['!='])[0];
1407
1006
  return {
1408
- error
1007
+ operator: '!=',
1008
+ field: field,
1009
+ operand: value
1409
1010
  };
1410
- }
1411
- return {
1412
- error: new Error('Unknown error')
1413
1011
  };
1414
- }
1415
- };
1416
- const useGetFederatedLoginStatus = ({ selectedFiles })=>{
1417
- const { data: wtsResults, isLoading: wstIsLoading, error: wtsError } = useGetExternalLoginsQuery();
1418
- // State to manage the asynchronous results
1419
- const [result, setResult] = useState(null);
1420
- useEffect(()=>{
1421
- const fetchData = async ()=>{
1422
- if (wtsError || !wtsResults) {
1423
- if (wtsError instanceof Error) setResult({
1424
- error: wtsError
1425
- });
1426
- else setResult({
1427
- error: new Error('Unknown error')
1428
- });
1429
- return;
1430
- }
1431
- const results = await fetchExternalLogins(wtsResults.providers, selectedFiles);
1432
- setResult(results);
1012
+ this.handleLessThan = (op)=>{
1013
+ const [field, value] = Object.entries(op['<'])[0];
1014
+ return {
1015
+ operator: '<',
1016
+ field: field,
1017
+ operand: value
1018
+ };
1433
1019
  };
1434
- // Only run if there's data to act on
1435
- if (!wstIsLoading && wtsResults) {
1436
- void fetchData();
1437
- }
1438
- }, [
1439
- selectedFiles,
1440
- wstIsLoading,
1441
- wtsError,
1442
- wtsResults
1443
- ]);
1444
- return {
1445
- isLoading: wstIsLoading,
1446
- data: result,
1447
- error: result?.error || wtsError
1448
- };
1449
- };
1450
- const getFederatedLoginStatus = async (selectedFiles)=>{
1451
- const providers = await queryWTSFederatedLoginStatus();
1452
- return await fetchExternalLogins(providers.providers, selectedFiles);
1453
- };
1454
-
1455
- /**
1456
- * Creates a base class core API for guppy API calls.
1457
- * @returns: guppy core API with guppyAPIFetch base query
1458
- */ const guppyApi = createApi({
1459
- reducerPath: 'guppy',
1460
- // TODO: refactor to use fetchBaseQuery
1461
- baseQuery: async (query, api)=>{
1462
- const csrfToken = selectCSRFToken(api.getState());
1463
- let accessToken = undefined;
1464
- if (process.env.NODE_ENV === 'development') {
1465
- // NOTE: This cookie can only be accessed from the client side
1466
- // in development mode. Otherwise, the cookie is set as httpOnly
1467
- accessToken = getCookie('credentials_token');
1468
- }
1469
- const headers = {
1470
- Accept: 'application/json',
1471
- 'Content-Type': 'application/json',
1472
- ...csrfToken && {
1473
- 'X-CSRF-Token': csrfToken
1474
- },
1475
- ...accessToken && {
1476
- Authorization: `Bearer ${accessToken}`
1477
- }
1020
+ this.handleLessThanOrEquals = (op)=>{
1021
+ const [field, value] = Object.entries(op['<='])[0];
1022
+ return {
1023
+ operator: '<=',
1024
+ field: field,
1025
+ operand: value
1026
+ };
1478
1027
  };
1479
- try {
1480
- const response = await fetch(`${GEN3_GUPPY_API}/graphql`, {
1481
- headers: headers,
1482
- method: 'POST',
1483
- body: JSON.stringify(query)
1484
- });
1485
- const data = await response.json();
1486
- // Check the response for GraphQL errors
1487
- if (data && 'errors' in data) {
1488
- // Return an object with an 'error' property if GraphQL errors are present
1489
- return {
1490
- error: {
1491
- message: data.errors[0].message,
1492
- locations: data.errors[0].locations
1493
- }
1494
- };
1495
- }
1028
+ this.handleGreaterThan = (op)=>{
1029
+ const [field, value] = Object.entries(op['>'])[0];
1496
1030
  return {
1497
- data
1031
+ operator: '>',
1032
+ field: field,
1033
+ operand: value
1498
1034
  };
1499
- } catch (e) {
1500
- if (e instanceof GraphQLError) {
1501
- return {
1502
- error: {
1503
- message: e.message,
1504
- locations: e.locations,
1505
- path: e.path
1506
- }
1507
- };
1508
- }
1509
- if (e instanceof Error) return {
1510
- error: e.message
1035
+ };
1036
+ this.handleGreaterThanOrEquals = (op)=>{
1037
+ const [field, value] = Object.entries(op['>='])[0];
1038
+ return {
1039
+ operator: '>=',
1040
+ field: field,
1041
+ operand: value
1511
1042
  };
1043
+ };
1044
+ this.handleIncludes = (op)=>{
1045
+ const [field, value] = Object.entries(op.in)[0];
1512
1046
  return {
1513
- error: e
1047
+ operator: 'in',
1048
+ field: field,
1049
+ operands: value
1514
1050
  };
1515
- }
1516
- },
1517
- endpoints: ()=>({})
1518
- });
1519
- const guppyAPISliceMiddleware = guppyApi.middleware;
1520
- const guppyApiSliceReducerPath = guppyApi.reducerPath;
1521
- const guppyApiReducer = guppyApi.reducer;
1522
-
1523
- const isFilterSet = (input)=>{
1524
- if (typeof input !== 'object' || input === null) {
1525
- return false;
1526
- }
1527
- const { root, mode } = input;
1528
- if (typeof root !== 'object' || root === null) {
1529
- return false;
1051
+ };
1052
+ this.handleExcludes = (op)=>{
1053
+ const [field, value] = Object.entries(op.exclude)[0];
1054
+ return {
1055
+ operator: 'excludes',
1056
+ field: field,
1057
+ operands: value
1058
+ };
1059
+ };
1060
+ this.handleExcludeIfAny = (op)=>{
1061
+ const [field, value] = Object.entries(op.excludeifany)[0];
1062
+ return {
1063
+ operator: 'excludeifany',
1064
+ field: field,
1065
+ operands: value
1066
+ };
1067
+ };
1068
+ this.handleIntersection = (op)=>({
1069
+ operator: 'and',
1070
+ operands: op.and.map(convertGqlFilterToFilter)
1071
+ });
1072
+ this.handleUnion = (op)=>({
1073
+ operator: 'or',
1074
+ operands: op.or.map(convertGqlFilterToFilter)
1075
+ });
1076
+ this.handleExists = (op)=>{
1077
+ const [field, value] = Object.entries(op.not)[0];
1078
+ return {
1079
+ operator: 'exists',
1080
+ field: field,
1081
+ operand: value
1082
+ };
1083
+ };
1084
+ this.handleMissing = (op)=>{
1085
+ const field = Object.keys(op.is)[0];
1086
+ return {
1087
+ operator: 'missing',
1088
+ field: field
1089
+ };
1090
+ };
1091
+ this.handleNestedFilter = (op)=>({
1092
+ operator: 'nested',
1093
+ path: op.nested.path,
1094
+ operand: convertGqlFilterToFilter(op.nested)
1095
+ });
1530
1096
  }
1531
- if (![
1532
- 'and',
1533
- 'or'
1534
- ].includes(mode)) {
1535
- return false;
1097
+ }
1098
+ /**
1099
+ * Extract the operand values, if operands themselves have values, otherwise undefined.
1100
+ */ class ValueExtractorHandler {
1101
+ constructor(){
1102
+ this.handleEquals = (op)=>op.operand;
1103
+ this.handleNotEquals = (op)=>op.operand;
1104
+ this.handleIncludes = (op)=>op.operands;
1105
+ this.handleExcludes = (op)=>op.operands;
1106
+ this.handleExcludeIfAny = (op)=>op.operands;
1107
+ this.handleGreaterThanOrEquals = (op)=>op.operand;
1108
+ this.handleGreaterThan = (op)=>op.operand;
1109
+ this.handleLessThan = (op)=>op.operand;
1110
+ this.handleLessThanOrEquals = (op)=>op.operand;
1111
+ this.handleIntersection = (_arg)=>undefined;
1112
+ this.handleUnion = (_)=>undefined;
1113
+ this.handleNestedFilter = (_)=>undefined;
1114
+ this.handleExists = (_)=>undefined;
1115
+ this.handleMissing = (_)=>undefined;
1536
1116
  }
1537
- return true;
1538
- };
1117
+ }
1539
1118
  /**
1540
- * Type guard for Includes
1541
- * @param o - operator to check
1542
- * @category Filters
1543
- */ const isIncludes = (o)=>o.operator === 'includes' || o.operator === 'in';
1544
- const isUnion = (value)=>{
1545
- return typeof value === 'object' && value !== null && value.operator === 'or' && Array.isArray(value.operands);
1546
- };
1547
- const isIntersection = (value)=>{
1548
- return typeof value === 'object' && value !== null && value.operator === 'and' && Array.isArray(value.operands);
1119
+ * Extract the operand values, if operands themselves have values, otherwise undefined.
1120
+ */ class EnumValueExtractorHandler {
1121
+ constructor(){
1122
+ this.handleEquals = (_)=>undefined;
1123
+ this.handleNotEquals = (_)=>undefined;
1124
+ this.handleIncludes = (op)=>op.operands;
1125
+ this.handleExcludes = (op)=>op.operands;
1126
+ this.handleExcludeIfAny = (op)=>op.operands;
1127
+ this.handleGreaterThanOrEquals = (_)=>undefined;
1128
+ this.handleGreaterThan = (_)=>undefined;
1129
+ this.handleLessThan = (_)=>undefined;
1130
+ this.handleLessThanOrEquals = (_)=>undefined;
1131
+ this.handleIntersection = (_)=>undefined;
1132
+ this.handleUnion = (_)=>undefined;
1133
+ this.handleNestedFilter = (op)=>{
1134
+ return extractEnumFilterValue(op.operand);
1135
+ };
1136
+ this.handleExists = (_)=>undefined;
1137
+ this.handleMissing = (_)=>undefined;
1138
+ }
1139
+ }
1140
+ const appendFilterToOperation = (filter, addition)=>{
1141
+ if (filter === undefined && addition === undefined) return {
1142
+ operator: 'and',
1143
+ operands: []
1144
+ };
1145
+ if (addition === undefined && filter) return filter;
1146
+ if (filter === undefined && addition) return addition;
1147
+ return {
1148
+ ...filter,
1149
+ operands: [
1150
+ ...filter?.operands || [],
1151
+ addition
1152
+ ]
1153
+ };
1549
1154
  };
1550
- /**
1551
- * Type guard for Union or Intersection
1552
- * @param o - operator to check
1553
- * @category Filters
1554
- */ const isIntersectionOrUnion = (o)=>o.operator === 'and' || o.operator === 'or';
1555
- const isOperandsType = (operation)=>{
1556
- return operation?.operands !== undefined;
1557
- };
1558
- const ifOperationWithField = (operation)=>{
1559
- return operation.field !== undefined;
1155
+ const filterSetToOperation = (fs)=>{
1156
+ if (!fs) return undefined;
1157
+ switch(fs.mode){
1158
+ case 'and':
1159
+ return Object.keys(fs.root).length == 0 ? undefined : {
1160
+ operator: fs.mode,
1161
+ operands: Object.keys(fs.root).map((k)=>{
1162
+ return fs.root[k];
1163
+ })
1164
+ };
1165
+ }
1166
+ return undefined;
1560
1167
  };
1561
- const isNestedFilter = (operation)=>{
1562
- return operation.operator === 'nested';
1168
+ /**
1169
+ * Constructs a nested operation object based on the provided field and leaf operand.
1170
+ * If the field does not contain a dot '.', it either assigns the field to the leaf operand (if applicable)
1171
+ * or returns the leaf operand as is. When the field contains dots, it splits the field into parts,
1172
+ * creates a "nested" operation for the root field, and recursively constructs the nested structure
1173
+ * for the remaining portion of the field.
1174
+ *
1175
+ * @param {string} field - The hierarchical field path, with segments separated by dots (e.g., "root.child").
1176
+ * @param {Operation} leafOperand - The operation to be nested within the specified path.
1177
+ * @param parentPath - The parent path of the current field. Guppy nested filters require a parent path.
1178
+ * @param depth
1179
+ * @returns {Operation} A nested operation object that represents the structured path and operand.
1180
+ */ const buildNestedGQLFilter = (field, leafOperand, parentPath = undefined)=>{
1181
+ if (!field.includes('.')) {
1182
+ return leafOperand;
1183
+ }
1184
+ const splitFieldArray = field.split('.');
1185
+ const nextField = splitFieldArray.shift();
1186
+ if (!nextField) {
1187
+ console.warn('Invalid field path:', field);
1188
+ return leafOperand;
1189
+ }
1190
+ const currentPath = parentPath ? `${parentPath}.${nextField}` : nextField;
1191
+ return {
1192
+ nested: {
1193
+ path: currentPath,
1194
+ ...buildNestedGQLFilter(splitFieldArray.join('.'), leafOperand, currentPath)
1195
+ }
1196
+ };
1563
1197
  };
1564
- const isIndexedFilterSetEmpty = (filters)=>Object.values(filters).every((filterSet)=>Object.keys(filterSet).length === 0);
1565
- const EmptyFilterSet = {
1566
- mode: 'and',
1567
- root: {}
1198
+ /**
1199
+ * Constructs a nested operation object based on the provided field and leaf operand.
1200
+ * If the field does not contain a dot '.', it either assigns the field to the leaf operand (if applicable)
1201
+ * or returns the leaf operand as is. When the field contains dots, it splits the field into parts,
1202
+ * creates a "nested" operation for the root field, and recursively constructs the nested structure
1203
+ * for the remaining portion of the field.
1204
+ *
1205
+ * @param {string} field - The hierarchical field path, with segments separated by dots (e.g., "root.child").
1206
+ * @param {Operation} leafOperand - The operation to be nested within the specified path.
1207
+ * @param parentPath - The parent path of the current field. Guppy nested filters require a parent path.
1208
+ * @returns {Operation} A nested operation object that represents the structured path and operand.
1209
+ */ const buildNestedFilterForOperation = (field, leafOperand, parentPath = undefined)=>{
1210
+ if (!field.includes('.')) {
1211
+ // at the end of the path
1212
+ if (ifOperationWithField(leafOperand)) {
1213
+ // update the field for operations with a field
1214
+ return {
1215
+ ...leafOperand,
1216
+ field
1217
+ };
1218
+ }
1219
+ return leafOperand;
1220
+ }
1221
+ const splitFieldArray = field.split('.');
1222
+ const nextField = splitFieldArray.shift();
1223
+ if (!nextField) {
1224
+ console.warn('Invalid field path:', field);
1225
+ return leafOperand;
1226
+ }
1227
+ const currentPath = parentPath ? `${parentPath}.${nextField}` : nextField;
1228
+ return {
1229
+ operator: 'nested',
1230
+ path: currentPath,
1231
+ operand: buildNestedFilterForOperation(splitFieldArray.join('.'), leafOperand, currentPath)
1232
+ };
1568
1233
  };
1569
1234
 
1570
- const isOperationWithField = (operation)=>{
1571
- return operation?.field !== undefined;
1235
+ const FieldNameOverrides = {
1236
+ gender: 'Sex',
1237
+ Gender: 'Sex'
1572
1238
  };
1573
- const isOperatorWithFieldAndArrayOfOperands = (operation)=>{
1574
- 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
1575
- ) {
1576
- const { operator } = operation;
1577
- return operator === 'in' || operator === 'includes' || operator === 'excludes' || operator === 'excludeifany';
1239
+ const COMMON_PREPOSITIONS = [
1240
+ 'a',
1241
+ 'an',
1242
+ 'and',
1243
+ 'at',
1244
+ 'but',
1245
+ 'by',
1246
+ 'for',
1247
+ 'in',
1248
+ 'is',
1249
+ 'nor',
1250
+ 'of',
1251
+ 'on',
1252
+ 'or',
1253
+ 'out',
1254
+ 'so',
1255
+ 'the',
1256
+ 'to',
1257
+ 'up',
1258
+ 'yet'
1259
+ ];
1260
+ const capitalize$1 = (s)=>s.length > 0 ? s[0].toUpperCase() + s.slice(1) : '';
1261
+ const trimFirstfieldNameToLabel = (fieldName, trim = false)=>{
1262
+ if (trim) {
1263
+ const source = fieldName.slice(fieldName.indexOf('.') + 1);
1264
+ return fieldNameToLabel(source ? source : fieldName, 0);
1578
1265
  }
1579
- return false;
1580
- };
1581
- const extractFilterValue = (op)=>{
1582
- const valueExtractorHandler = new ValueExtractorHandler();
1583
- return handleOperation(valueExtractorHandler, op);
1584
- };
1585
- const extractEnumFilterValue = (op)=>{
1586
- const enumValueExtractorHandler = new EnumValueExtractorHandler();
1587
- const results = handleOperation(enumValueExtractorHandler, op);
1588
- return results ?? [];
1589
- };
1590
- const assertNever = (x)=>{
1591
- throw Error(`Exhaustive comparison did not handle: ${x}`);
1266
+ return fieldNameToLabel(fieldName);
1592
1267
  };
1593
- const handleOperation = (handler, op)=>{
1594
- switch(op.operator){
1595
- case '=':
1596
- return handler.handleEquals(op);
1597
- case '!=':
1598
- return handler.handleNotEquals(op);
1599
- case '<':
1600
- return handler.handleLessThan(op);
1601
- case '<=':
1602
- return handler.handleLessThanOrEquals(op);
1603
- case '>':
1604
- return handler.handleGreaterThan(op);
1605
- case '>=':
1606
- return handler.handleGreaterThanOrEquals(op);
1607
- case 'and':
1608
- return handler.handleIntersection(op);
1609
- case 'or':
1610
- return handler.handleUnion(op);
1611
- case 'nested':
1612
- return handler.handleNestedFilter(op);
1613
- case 'in':
1614
- case 'includes':
1615
- return handler.handleIncludes(op);
1616
- case 'excludeifany':
1617
- return handler.handleExcludeIfAny(op);
1618
- case 'excludes':
1619
- return handler.handleExcludes(op);
1620
- case 'exists':
1621
- return handler.handleExists(op);
1622
- case 'missing':
1623
- return handler.handleMissing(op);
1624
- default:
1625
- return assertNever(op);
1626
- }
1268
+ /**
1269
+ * Converts a filter name to a title,
1270
+ * For example files.input.experimental_strategy will get converted to Experimental Strategy
1271
+ * if sections == 2 then the output would be Input Experimental Strategy
1272
+ * @param fieldName input filter expected to be: string.firstpart_secondpart
1273
+ * @param sections number of "sections" string.string.string to got back from the end of the field
1274
+ */ const fieldNameToLabel = (fieldName, sections = 1)=>{
1275
+ if (fieldName === undefined) return 'No Title';
1276
+ return fieldName.split('.').slice(-sections).map((s)=>s.split('_')).flat().map((word)=>COMMON_PREPOSITIONS.includes(word) ? word : capitalize$1(word)).map((word)=>word in FieldNameOverrides ? FieldNameOverrides[word] : word).join(' ');
1627
1277
  };
1628
1278
  /**
1629
- * Return true if a FilterSet's root value is an empty object
1630
- * @param fs - FilterSet to test
1631
- */ const isFilterEmpty = (fs)=>isEqual({}, fs);
1279
+ * Extracts the index name from the field name
1280
+ * @param fieldName
1281
+ */ const extractIndexFromFullFieldName = (fieldName)=>fieldName.split('.')[0];
1632
1282
  /**
1633
- * Type guard to check if an object is a GQLIntersection
1634
- * @param value - The value to check
1635
- * @returns True if the value is a GQLIntersection
1636
- */ const isGQLIntersection = (value)=>{
1637
- return typeof value === 'object' && value !== null && 'and' in value && Array.isArray(value.and);
1283
+ * prepend the index name to the field name
1284
+ */ const prependIndexToFieldName = (fieldName, index)=>`${index}.${fieldName}`;
1285
+ /**
1286
+ * extract the field name from the index.field name
1287
+ */ const extractFieldNameFromFullFieldName = (fieldName)=>fieldName.split('.').slice(1).join('.');
1288
+ /**
1289
+ * extract the field name and the index from the index.field name returning as a tuple
1290
+ */ const extractIndexAndFieldNameFromFullFieldName = (fieldName)=>{
1291
+ const [index, ...rest] = fieldName.split('.');
1292
+ return [
1293
+ index,
1294
+ rest.join('.')
1295
+ ];
1638
1296
  };
1297
+
1639
1298
  /**
1640
- * Type guard to check if an object is a GQLIntersection
1641
- * @param value - The value to check
1642
- * @returns True if the value is a GQLIntersection
1643
- */ const isGQLUnion = (value)=>{
1644
- return typeof value === 'object' && value !== null && 'or' in value && Array.isArray(value.or);
1299
+ * Constructs a nested operation object based on the provided field and leaf operand.
1300
+ * If the field does not contain a dot '.', it either assigns the field to the leaf operand (if applicable)
1301
+ * or returns the leaf operand as is. When the field contains dots, it splits the field into parts,
1302
+ * creates a "nested" operation for the root field, and recursively constructs the nested structure
1303
+ * for the remaining portion of the field.
1304
+ *
1305
+ * @param {string} field - The hierarchical field path, with segments separated by dots (e.g., "root.child").
1306
+ * @param {Operation} leafOperand - The operation to be nested within the specified path.
1307
+ * @param parentPath - The parent path of the current field. Guppy nested filters require a parent path.
1308
+ * @param depth
1309
+ * @returns {Operation} A nested operation object that represents the structured path and operand.
1310
+ */ const buildNestedWithParentPathGQLFilter = (field, leafOperand, parentPath = undefined)=>{
1311
+ if (!field.includes('.')) {
1312
+ return leafOperand;
1313
+ }
1314
+ const splitFieldArray = field.split('.');
1315
+ const nextField = splitFieldArray.shift();
1316
+ if (!nextField) {
1317
+ console.warn('Invalid field path:', field);
1318
+ return leafOperand;
1319
+ }
1320
+ const currentPath = parentPath ? `${parentPath}.${nextField}` : nextField;
1321
+ return {
1322
+ nested: {
1323
+ path: currentPath,
1324
+ ...buildNestedGQLFilter(splitFieldArray.join('.'), leafOperand, currentPath)
1325
+ }
1326
+ };
1645
1327
  };
1646
- class ToGqlHandler {
1647
- constructor(){
1648
- this.handleEquals = (op)=>({
1649
- '=': {
1650
- [op.field]: op.operand
1651
- }
1652
- });
1653
- this.handleNotEquals = (op)=>({
1654
- '!=': {
1655
- [op.field]: op.operand
1656
- }
1657
- });
1658
- this.handleLessThan = (op)=>({
1659
- '<': {
1660
- [op.field]: op.operand
1661
- }
1662
- });
1663
- this.handleLessThanOrEquals = (op)=>({
1328
+ const buildCohortGqlOperator = (fs)=>{
1329
+ if (!fs || !fs.root) return undefined;
1330
+ const fsKeys = Object.keys(fs.root);
1331
+ // if no keys return undefined
1332
+ if (fsKeys.length === 0) return undefined;
1333
+ // TODO consider changing FilterSet: mode to support joinOrToAll as FilterSet mode
1334
+ // find key using keyword "joinOrToAll"
1335
+ const joinOrToAllKey = fsKeys.filter((x)=>x.includes('joinOrToAll'));
1336
+ switch(fs.mode){
1337
+ case 'and':
1338
+ if (joinOrToAllKey.length === 1) {
1339
+ const firstJoinOrToAllKey = joinOrToAllKey[0];
1340
+ // Remove firstJoinOrToAllKey from Array
1341
+ fsKeys.splice(fsKeys.indexOf(firstJoinOrToAllKey), 1);
1342
+ const firstJoinOrToAllObj = fs.root[firstJoinOrToAllKey];
1343
+ // make sure type is or/ Union
1344
+ if (firstJoinOrToAllObj.operator === 'or') {
1345
+ return {
1346
+ or: firstJoinOrToAllObj?.operands.map((orObj)=>{
1347
+ // go through each or statement and add all other filters to it
1348
+ return {
1349
+ and: [
1350
+ convertFilterToNestedGqlFilter(orObj),
1351
+ ...fsKeys.map((k)=>{
1352
+ return convertFilterToNestedGqlFilter(fs.root[k]);
1353
+ })
1354
+ ]
1355
+ };
1356
+ })
1357
+ };
1358
+ } else {
1359
+ console.error(`function buildCohortGqlOperator expecting "or" received "${firstJoinOrToAllObj.operator}" on key "${firstJoinOrToAllKey}"`);
1360
+ }
1361
+ } else if (joinOrToAllKey.length > 1) {
1362
+ console.error(`function buildCohortGqlOperator expecting only one joinOrToAll received: ${joinOrToAllKey.length}`, fsKeys);
1363
+ }
1364
+ return {
1365
+ // TODO: Replace fixed AND with cohort top level operation like Union or Intersection
1366
+ [fs.mode]: fsKeys.map((k)=>{
1367
+ return convertFilterToGqlFilter(fs.root[k]);
1368
+ })
1369
+ };
1370
+ case 'or':
1371
+ return {
1372
+ [fs.mode]: fsKeys.map((k)=>{
1373
+ return convertFilterToGqlFilter(fs.root[k]);
1374
+ })
1375
+ };
1376
+ }
1377
+ };
1378
+ /**
1379
+ * Merged two FilterSets returning the merged pair.
1380
+ * @param a - first FilterSet
1381
+ * @param b - other FilterSet
1382
+ */ const joinFilters = (a, b)=>{
1383
+ return {
1384
+ mode: a.mode,
1385
+ root: {
1386
+ ...a.root,
1387
+ ...b.root
1388
+ }
1389
+ };
1390
+ };
1391
+ const convertFilterSetToOperation = (fs)=>{
1392
+ if (!fs) return undefined;
1393
+ switch(fs.mode){
1394
+ case 'and':
1395
+ return Object.keys(fs.root).length == 0 ? undefined : {
1396
+ operator: fs.mode,
1397
+ operands: Object.keys(fs.root).map((k)=>{
1398
+ return fs.root[k];
1399
+ })
1400
+ };
1401
+ }
1402
+ return undefined;
1403
+ };
1404
+ const extractContents = (filter)=>{
1405
+ if (isGQLUnion(filter)) {
1406
+ return filter.or;
1407
+ }
1408
+ if (isGQLIntersection(filter)) {
1409
+ return filter.and;
1410
+ }
1411
+ return undefined;
1412
+ };
1413
+ class ToGqlAllNested {
1414
+ constructor(){
1415
+ this.handleEquals = (op)=>{
1416
+ if (op.field.includes('.')) {
1417
+ const leafField = op.field.split('.').at(-1) ?? 'unset';
1418
+ return buildNestedGQLFilter(op.field, {
1419
+ '=': {
1420
+ [leafField]: op.operand
1421
+ }
1422
+ });
1423
+ }
1424
+ return {
1425
+ '=': {
1426
+ [op.field]: op.operand
1427
+ }
1428
+ };
1429
+ };
1430
+ this.handleNotEquals = (op)=>{
1431
+ if (op.field.includes('.')) {
1432
+ const leafField = op.field.split('.').at(-1) ?? 'unset';
1433
+ return buildNestedGQLFilter(op.field, {
1434
+ '!=': {
1435
+ [leafField]: op.operand
1436
+ }
1437
+ });
1438
+ }
1439
+ return {
1440
+ '!=': {
1441
+ [op.field]: op.operand
1442
+ }
1443
+ };
1444
+ };
1445
+ this.handleLessThan = (op)=>{
1446
+ if (op.field.includes('.')) {
1447
+ const leafField = op.field.split('.').at(-1) ?? 'unset';
1448
+ return buildNestedGQLFilter(op.field, {
1449
+ '<': {
1450
+ [leafField]: op.operand
1451
+ }
1452
+ });
1453
+ }
1454
+ return {
1455
+ '<': {
1456
+ [op.field]: op.operand
1457
+ }
1458
+ };
1459
+ };
1460
+ this.handleLessThanOrEquals = (op)=>{
1461
+ if (op.field.includes('.')) {
1462
+ const leafField = op.field.split('.').at(-1) ?? 'unset';
1463
+ return buildNestedGQLFilter(op.field, {
1464
+ '<=': {
1465
+ [leafField]: op.operand
1466
+ }
1467
+ });
1468
+ }
1469
+ return {
1664
1470
  '<=': {
1665
1471
  [op.field]: op.operand
1666
1472
  }
1667
- });
1668
- this.handleGreaterThan = (op)=>({
1473
+ };
1474
+ };
1475
+ this.handleGreaterThan = (op)=>{
1476
+ if (op.field.includes('.')) {
1477
+ const leafField = op.field.split('.').at(-1) ?? 'unset';
1478
+ return buildNestedGQLFilter(op.field, {
1479
+ '>': {
1480
+ [leafField]: op.operand
1481
+ }
1482
+ });
1483
+ }
1484
+ return {
1669
1485
  '>': {
1670
1486
  [op.field]: op.operand
1671
1487
  }
1672
- });
1673
- this.handleGreaterThanOrEquals = (op)=>({
1488
+ };
1489
+ };
1490
+ this.handleGreaterThanOrEquals = (op)=>{
1491
+ if (op.field.includes('.')) {
1492
+ const leafField = op.field.split('.').at(-1) ?? 'unset';
1493
+ return buildNestedGQLFilter(op.field, {
1494
+ '>=': {
1495
+ [leafField]: op.operand
1496
+ }
1497
+ });
1498
+ }
1499
+ return {
1674
1500
  '>=': {
1675
1501
  [op.field]: op.operand
1676
1502
  }
1677
- });
1678
- this.handleIncludes = (op)=>({
1503
+ };
1504
+ };
1505
+ this.handleIncludes = (op)=>{
1506
+ if (op.field.includes('.')) {
1507
+ const leafField = op.field.split('.').at(-1) ?? 'unset';
1508
+ return buildNestedGQLFilter(op.field, {
1509
+ in: {
1510
+ [leafField]: op.operands
1511
+ }
1512
+ });
1513
+ }
1514
+ return {
1679
1515
  in: {
1680
1516
  [op.field]: op.operands
1681
1517
  }
1682
- });
1683
- this.handleExcludes = (op)=>({
1518
+ };
1519
+ };
1520
+ this.handleExcludes = (op)=>{
1521
+ if (op.field.includes('.')) {
1522
+ const leafField = op.field.split('.').at(-1) ?? 'unset';
1523
+ return buildNestedGQLFilter(op.field, {
1524
+ exclude: {
1525
+ [leafField]: op.operands
1526
+ }
1527
+ });
1528
+ }
1529
+ return {
1684
1530
  exclude: {
1685
1531
  [op.field]: op.operands
1686
1532
  }
1687
- });
1688
- this.handleExcludeIfAny = (op)=>({
1533
+ };
1534
+ };
1535
+ this.handleExcludeIfAny = (op)=>{
1536
+ if (op.field.includes('.')) {
1537
+ const leafField = op.field.split('.').at(-1) ?? 'unset';
1538
+ return buildNestedGQLFilter(op.field, {
1539
+ excludeifany: {
1540
+ [leafField]: op.operands
1541
+ }
1542
+ });
1543
+ }
1544
+ return {
1689
1545
  excludeifany: {
1690
1546
  [op.field]: op.operands
1691
1547
  }
1692
- });
1548
+ };
1549
+ };
1693
1550
  this.handleIntersection = (op)=>({
1694
1551
  and: op.operands.map((x)=>convertFilterToGqlFilter(x))
1695
1552
  });
1696
1553
  this.handleUnion = (op)=>({
1697
1554
  or: op.operands.map((x)=>convertFilterToGqlFilter(x))
1698
1555
  });
1699
- this.handleMissing = (op)=>({
1556
+ this.handleMissing = (op)=>{
1557
+ if (op.field.includes('.')) {
1558
+ const leafField = op.field.split('.').at(-1) ?? 'unset';
1559
+ return buildNestedGQLFilter(op.field, {
1560
+ is: {
1561
+ [leafField]: 'MISSING'
1562
+ }
1563
+ });
1564
+ }
1565
+ return {
1700
1566
  is: {
1701
1567
  [op.field]: 'MISSING'
1702
1568
  }
1703
- });
1704
- this.handleExists = (op)=>({
1569
+ };
1570
+ };
1571
+ this.handleExists = (op)=>{
1572
+ if (op.field.includes('.')) {
1573
+ const leafField = op.field.split('.').at(-1) ?? 'unset';
1574
+ return buildNestedGQLFilter(op.field, {
1575
+ not: {
1576
+ [leafField]: op?.operand ?? null
1577
+ }
1578
+ });
1579
+ }
1580
+ return {
1705
1581
  not: {
1706
1582
  [op.field]: op?.operand ?? null
1707
1583
  }
1708
- });
1584
+ };
1585
+ };
1709
1586
  this.handleNestedFilter = (op)=>{
1710
1587
  const child = convertFilterToGqlFilter(op.operand);
1711
1588
  return {
@@ -1717,964 +1594,292 @@ class ToGqlHandler {
1717
1594
  };
1718
1595
  }
1719
1596
  }
1720
- const convertFilterToGqlFilter = (filter)=>{
1721
- const handler = new ToGqlHandler();
1597
+ const convertFilterToNestedGqlFilter = (filter)=>{
1598
+ const handler = new ToGqlAllNested();
1722
1599
  return handleOperation(handler, filter);
1723
1600
  };
1724
- const convertFilterSetToGqlFilter = (fs, toplevelOp = 'and')=>{
1601
+ const convertFilterSetToNestedGqlFilter = (fs, toplevelOp = 'and')=>{
1725
1602
  const fsKeys = Object.keys(fs.root);
1726
1603
  // if no keys return undefined
1727
1604
  if (fsKeys.length === 0) return {
1728
1605
  and: []
1729
1606
  };
1730
1607
  return toplevelOp === 'and' ? {
1731
- and: fsKeys.map((key)=>convertFilterToGqlFilter(fs.root[key]))
1608
+ and: fsKeys.map((key)=>convertFilterToNestedGqlFilter(fs.root[key]))
1732
1609
  } : {
1733
- or: fsKeys.map((key)=>convertFilterToGqlFilter(fs.root[key]))
1610
+ or: fsKeys.map((key)=>convertFilterToNestedGqlFilter(fs.root[key]))
1734
1611
  };
1735
1612
  };
1736
- const handleGqlOperation = (handler, op)=>{
1737
- const operationKeys = Object.keys(op);
1738
- if (operationKeys.includes('=')) {
1739
- return handler.handleEquals(op);
1740
- }
1741
- if (operationKeys.includes('!=')) {
1742
- return handler.handleNotEquals(op);
1743
- }
1744
- if (operationKeys.includes('<')) {
1745
- return handler.handleLessThan(op);
1746
- }
1747
- if (operationKeys.includes('<=')) {
1748
- return handler.handleLessThanOrEquals(op);
1749
- }
1750
- if (operationKeys.includes('>')) {
1751
- return handler.handleGreaterThan(op);
1752
- }
1753
- if (operationKeys.includes('>=')) {
1754
- return handler.handleGreaterThanOrEquals(op);
1755
- }
1756
- if (operationKeys.includes('in')) {
1757
- return handler.handleIncludes(op);
1758
- }
1759
- if (operationKeys.includes('exclude')) {
1760
- return handler.handleExcludes(op);
1761
- }
1762
- if (operationKeys.includes('excludeifany')) {
1763
- return handler.handleExcludeIfAny(op);
1764
- }
1765
- if (operationKeys.includes('and')) {
1766
- return handler.handleIntersection(op);
1767
- }
1768
- if (operationKeys.includes('or')) {
1769
- return handler.handleUnion(op);
1770
- }
1771
- if (operationKeys.includes('nested')) {
1772
- return handler.handleNestedFilter(op);
1773
- }
1774
- if (operationKeys.includes('is')) {
1775
- return handler.handleExists(op);
1776
- }
1777
- if (operationKeys.includes('not')) {
1778
- return handler.handleMissing(op);
1779
- }
1780
- return assertNever(op);
1613
+
1614
+ const defaultCohortNameGenerator = ()=>`Custom cohort ${new Date().toLocaleString('en-CA', {
1615
+ timeZone: 'America/Chicago',
1616
+ hour12: false
1617
+ }).replace(',', '')}`;
1618
+ const isNameUnique = (entities, name, excludeId)=>{
1619
+ const trimmedName = name.trim();
1620
+ if (!trimmedName) return false;
1621
+ return !entities.some((cohort)=>cohort && cohort.id !== excludeId && cohort.name.trim().toLowerCase() === trimmedName.toLowerCase());
1781
1622
  };
1782
- const convertGqlFilterToFilter = (gqlFilter)=>{
1783
- const handler = new ToOperationHandler();
1784
- return handleGqlOperation(handler, gqlFilter);
1623
+ const generateUniqueName = (entities, baseName)=>{
1624
+ const trimmedBaseName = baseName.trim();
1625
+ // If base name is unique, use it
1626
+ if (isNameUnique(entities, trimmedBaseName)) {
1627
+ return trimmedBaseName;
1628
+ }
1629
+ // Find a unique name by appending numbers
1630
+ let counter = 1;
1631
+ let uniqueName;
1632
+ do {
1633
+ uniqueName = `${trimmedBaseName} (${counter})`;
1634
+ counter++;
1635
+ }while (!isNameUnique(entities, uniqueName))
1636
+ return uniqueName;
1785
1637
  };
1786
1638
  /**
1787
- * Convert GQL to Filterset
1788
- * Note assumes all GqlOperators have one field: value
1789
- */ class ToOperationHandler {
1790
- constructor(){
1791
- this.handleEquals = (op)=>{
1792
- const [field, value] = Object.entries(op['='])[0];
1793
- return {
1794
- operator: '=',
1795
- field: field,
1796
- operand: value
1797
- };
1798
- };
1799
- this.handleNotEquals = (op)=>{
1800
- const [field, value] = Object.entries(op['!='])[0];
1801
- return {
1802
- operator: '!=',
1803
- field: field,
1804
- operand: value
1805
- };
1806
- };
1807
- this.handleLessThan = (op)=>{
1808
- const [field, value] = Object.entries(op['<'])[0];
1809
- return {
1810
- operator: '<',
1811
- field: field,
1812
- operand: value
1813
- };
1814
- };
1815
- this.handleLessThanOrEquals = (op)=>{
1816
- const [field, value] = Object.entries(op['<='])[0];
1817
- return {
1818
- operator: '<=',
1819
- field: field,
1820
- operand: value
1821
- };
1822
- };
1823
- this.handleGreaterThan = (op)=>{
1824
- const [field, value] = Object.entries(op['>'])[0];
1825
- return {
1826
- operator: '>',
1827
- field: field,
1828
- operand: value
1829
- };
1830
- };
1831
- this.handleGreaterThanOrEquals = (op)=>{
1832
- const [field, value] = Object.entries(op['>='])[0];
1833
- return {
1834
- operator: '>=',
1835
- field: field,
1836
- operand: value
1837
- };
1838
- };
1839
- this.handleIncludes = (op)=>{
1840
- const [field, value] = Object.entries(op.in)[0];
1841
- return {
1842
- operator: 'in',
1843
- field: field,
1844
- operands: value
1845
- };
1846
- };
1847
- this.handleExcludes = (op)=>{
1848
- const [field, value] = Object.entries(op.exclude)[0];
1849
- return {
1850
- operator: 'excludes',
1851
- field: field,
1852
- operands: value
1853
- };
1854
- };
1855
- this.handleExcludeIfAny = (op)=>{
1856
- const [field, value] = Object.entries(op.excludeifany)[0];
1857
- return {
1858
- operator: 'excludeifany',
1859
- field: field,
1860
- operands: value
1861
- };
1862
- };
1863
- this.handleIntersection = (op)=>({
1864
- operator: 'and',
1865
- operands: op.and.map(convertGqlFilterToFilter)
1866
- });
1867
- this.handleUnion = (op)=>({
1868
- operator: 'or',
1869
- operands: op.or.map(convertGqlFilterToFilter)
1870
- });
1871
- this.handleExists = (op)=>{
1872
- const [field, value] = Object.entries(op.not)[0];
1873
- return {
1874
- operator: 'exists',
1875
- field: field,
1876
- operand: value
1877
- };
1878
- };
1879
- this.handleMissing = (op)=>{
1880
- const field = Object.keys(op.is)[0];
1881
- return {
1882
- operator: 'missing',
1883
- field: field
1884
- };
1639
+ * This function takes a FilterSet object and a prefix string as input.
1640
+ * It filters the root property of the FilterSet object and returns a
1641
+ * new FilterSet object that only contains filters with field names
1642
+ * that start with the specified prefix.
1643
+ *
1644
+ * @param fs - The FilterSet object to filter
1645
+ * @param prefix - The prefix to filter by
1646
+ * @returns - A new FilterSet object that only contains filters with field names that start with the specified prefix
1647
+ * @category Filters
1648
+ */ const extractFiltersWithPrefixFromFilterSet = (fs, prefix)=>{
1649
+ if (fs === undefined || fs.root === undefined) {
1650
+ return {
1651
+ mode: 'and',
1652
+ root: {}
1885
1653
  };
1886
- this.handleNestedFilter = (op)=>({
1887
- operator: 'nested',
1888
- path: op.nested.path,
1889
- operand: convertGqlFilterToFilter(op.nested)
1890
- });
1891
- }
1892
- }
1893
- /**
1894
- * Extract the operand values, if operands themselves have values, otherwise undefined.
1895
- */ class ValueExtractorHandler {
1896
- constructor(){
1897
- this.handleEquals = (op)=>op.operand;
1898
- this.handleNotEquals = (op)=>op.operand;
1899
- this.handleIncludes = (op)=>op.operands;
1900
- this.handleExcludes = (op)=>op.operands;
1901
- this.handleExcludeIfAny = (op)=>op.operands;
1902
- this.handleGreaterThanOrEquals = (op)=>op.operand;
1903
- this.handleGreaterThan = (op)=>op.operand;
1904
- this.handleLessThan = (op)=>op.operand;
1905
- this.handleLessThanOrEquals = (op)=>op.operand;
1906
- this.handleIntersection = (_arg)=>undefined;
1907
- this.handleUnion = (_)=>undefined;
1908
- this.handleNestedFilter = (_)=>undefined;
1909
- this.handleExists = (_)=>undefined;
1910
- this.handleMissing = (_)=>undefined;
1911
1654
  }
1912
- }
1655
+ return Object.values(fs.root).reduce((acc, filter)=>{
1656
+ if (isIntersectionOrUnion(filter) || isNestedFilter(filter)) return acc;
1657
+ if (filter.field.startsWith(prefix)) {
1658
+ acc.root[filter.field] = filter;
1659
+ }
1660
+ return acc;
1661
+ }, {
1662
+ mode: 'and',
1663
+ root: {}
1664
+ });
1665
+ };
1666
+
1913
1667
  /**
1914
- * Extract the operand values, if operands themselves have values, otherwise undefined.
1915
- */ class EnumValueExtractorHandler {
1916
- constructor(){
1917
- this.handleEquals = (_)=>undefined;
1918
- this.handleNotEquals = (_)=>undefined;
1919
- this.handleIncludes = (op)=>op.operands;
1920
- this.handleExcludes = (op)=>op.operands;
1921
- this.handleExcludeIfAny = (op)=>op.operands;
1922
- this.handleGreaterThanOrEquals = (_)=>undefined;
1923
- this.handleGreaterThan = (_)=>undefined;
1924
- this.handleLessThan = (_)=>undefined;
1925
- this.handleLessThanOrEquals = (_)=>undefined;
1926
- this.handleIntersection = (_)=>undefined;
1927
- this.handleUnion = (_)=>undefined;
1928
- this.handleNestedFilter = (op)=>{
1929
- return extractEnumFilterValue(op.operand);
1930
- };
1931
- this.handleExists = (_)=>undefined;
1932
- this.handleMissing = (_)=>undefined;
1933
- }
1934
- }
1935
- const appendFilterToOperation = (filter, addition)=>{
1936
- if (filter === undefined && addition === undefined) return {
1937
- operator: 'and',
1938
- operands: []
1939
- };
1940
- if (addition === undefined && filter) return filter;
1941
- if (filter === undefined && addition) return addition;
1668
+ * Cohorts in Gen3 are defined as a set of filters for each index in the data.
1669
+ * This means one cohort id defined for all "tabs" in CohortBuilder (explorer)
1670
+ * Switching a cohort id means all the cohorts for the index are changed.
1671
+ */ const DEFAULT_COHORT_NAME = 'Cohort';
1672
+ const newCohort = ({ filters = {}, customName })=>{
1673
+ const ts = new Date().toISOString();
1674
+ const newName = customName ?? defaultCohortNameGenerator();
1675
+ const newId = createCohortId();
1942
1676
  return {
1943
- ...filter,
1944
- operands: [
1945
- ...filter?.operands || [],
1946
- addition
1947
- ]
1948
- };
1949
- };
1950
- const filterSetToOperation = (fs)=>{
1951
- if (!fs) return undefined;
1952
- switch(fs.mode){
1953
- case 'and':
1954
- return Object.keys(fs.root).length == 0 ? undefined : {
1955
- operator: fs.mode,
1956
- operands: Object.keys(fs.root).map((k)=>{
1957
- return fs.root[k];
1958
- })
1959
- };
1960
- }
1961
- return undefined;
1962
- };
1963
- /**
1964
- * Constructs a nested operation object based on the provided field and leaf operand.
1965
- * If the field does not contain a dot '.', it either assigns the field to the leaf operand (if applicable)
1966
- * or returns the leaf operand as is. When the field contains dots, it splits the field into parts,
1967
- * creates a "nested" operation for the root field, and recursively constructs the nested structure
1968
- * for the remaining portion of the field.
1969
- *
1970
- * @param {string} field - The hierarchical field path, with segments separated by dots (e.g., "root.child").
1971
- * @param {Operation} leafOperand - The operation to be nested within the specified path.
1972
- * @param parentPath - The parent path of the current field. Guppy nested filters require a parent path.
1973
- * @param depth
1974
- * @returns {Operation} A nested operation object that represents the structured path and operand.
1975
- */ const buildNestedGQLFilter = (field, leafOperand, parentPath = undefined)=>{
1976
- if (!field.includes('.')) {
1977
- return leafOperand;
1978
- }
1979
- const splitFieldArray = field.split('.');
1980
- const nextField = splitFieldArray.shift();
1981
- if (!nextField) {
1982
- console.warn('Invalid field path:', field);
1983
- return leafOperand;
1984
- }
1985
- const currentPath = parentPath ? `${parentPath}.${nextField}` : nextField;
1986
- return {
1987
- nested: {
1988
- path: currentPath,
1989
- ...buildNestedGQLFilter(splitFieldArray.join('.'), leafOperand, currentPath)
1990
- }
1991
- };
1992
- };
1993
- /**
1994
- * Constructs a nested operation object based on the provided field and leaf operand.
1995
- * If the field does not contain a dot '.', it either assigns the field to the leaf operand (if applicable)
1996
- * or returns the leaf operand as is. When the field contains dots, it splits the field into parts,
1997
- * creates a "nested" operation for the root field, and recursively constructs the nested structure
1998
- * for the remaining portion of the field.
1999
- *
2000
- * @param {string} field - The hierarchical field path, with segments separated by dots (e.g., "root.child").
2001
- * @param {Operation} leafOperand - The operation to be nested within the specified path.
2002
- * @param parentPath - The parent path of the current field. Guppy nested filters require a parent path.
2003
- * @returns {Operation} A nested operation object that represents the structured path and operand.
2004
- */ const buildNestedFilterForOperation = (field, leafOperand, parentPath = undefined)=>{
2005
- if (!field.includes('.')) {
2006
- // at the end of the path
2007
- if (ifOperationWithField(leafOperand)) {
2008
- // update the field for operations with a field
2009
- return {
2010
- ...leafOperand,
2011
- field
2012
- };
2013
- }
2014
- return leafOperand;
2015
- }
2016
- const splitFieldArray = field.split('.');
2017
- const nextField = splitFieldArray.shift();
2018
- if (!nextField) {
2019
- console.warn('Invalid field path:', field);
2020
- return leafOperand;
2021
- }
2022
- const currentPath = parentPath ? `${parentPath}.${nextField}` : nextField;
2023
- return {
2024
- operator: 'nested',
2025
- path: currentPath,
2026
- operand: buildNestedFilterForOperation(splitFieldArray.join('.'), leafOperand, currentPath)
1677
+ name: newName,
1678
+ id: newId,
1679
+ filters: filters ?? {},
1680
+ modified: false,
1681
+ saved: false,
1682
+ createdDatetime: ts,
1683
+ modifiedDatetime: ts,
1684
+ counts: {}
2027
1685
  };
2028
1686
  };
2029
-
2030
- const FieldNameOverrides = {
2031
- gender: 'Sex',
2032
- Gender: 'Sex'
2033
- };
2034
- const COMMON_PREPOSITIONS = [
2035
- 'a',
2036
- 'an',
2037
- 'and',
2038
- 'at',
2039
- 'but',
2040
- 'by',
2041
- 'for',
2042
- 'in',
2043
- 'is',
2044
- 'nor',
2045
- 'of',
2046
- 'on',
2047
- 'or',
2048
- 'out',
2049
- 'so',
2050
- 'the',
2051
- 'to',
2052
- 'up',
2053
- 'yet'
2054
- ];
2055
- const capitalize = (s)=>s.length > 0 ? s[0].toUpperCase() + s.slice(1) : '';
2056
- const trimFirstfieldNameToLabel = (fieldName, trim = false)=>{
2057
- if (trim) {
2058
- const source = fieldName.slice(fieldName.indexOf('.') + 1);
2059
- return fieldNameToLabel(source ? source : fieldName, 0);
2060
- }
2061
- return fieldNameToLabel(fieldName);
2062
- };
2063
- /**
2064
- * Converts a filter name to a title,
2065
- * For example files.input.experimental_strategy will get converted to Experimental Strategy
2066
- * if sections == 2 then the output would be Input Experimental Strategy
2067
- * @param fieldName input filter expected to be: string.firstpart_secondpart
2068
- * @param sections number of "sections" string.string.string to got back from the end of the field
2069
- */ const fieldNameToLabel = (fieldName, sections = 1)=>{
2070
- if (fieldName === undefined) return 'No Title';
2071
- return fieldName.split('.').slice(-sections).map((s)=>s.split('_')).flat().map((word)=>COMMON_PREPOSITIONS.includes(word) ? word : capitalize(word)).map((word)=>word in FieldNameOverrides ? FieldNameOverrides[word] : word).join(' ');
2072
- };
2073
- /**
2074
- * Extracts the index name from the field name
2075
- * @param fieldName
2076
- */ const extractIndexFromFullFieldName = (fieldName)=>fieldName.split('.')[0];
2077
- /**
2078
- * prepend the index name to the field name
2079
- */ const prependIndexToFieldName = (fieldName, index)=>`${index}.${fieldName}`;
2080
- /**
2081
- * extract the field name from the index.field name
2082
- */ const extractFieldNameFromFullFieldName = (fieldName)=>fieldName.split('.').slice(1).join('.');
2083
- /**
2084
- * extract the field name and the index from the index.field name returning as a tuple
2085
- */ const extractIndexAndFieldNameFromFullFieldName = (fieldName)=>{
2086
- const [index, ...rest] = fieldName.split('.');
2087
- return [
2088
- index,
2089
- rest.join('.')
2090
- ];
2091
- };
2092
-
1687
+ const nanoid = customAlphabet('1234567890abcdef', 16);
1688
+ const createCohortId = ()=>nanoid();
1689
+ const cohortsAdapter = createEntityAdapter({
1690
+ sortComparer: (a, b)=>{
1691
+ if (a.modifiedDatetime <= b.modifiedDatetime) return 1;
1692
+ else return -1;
1693
+ },
1694
+ selectId: (cohort)=>cohort.id
1695
+ });
1696
+ // Create an initial unsaved cohort
1697
+ const initialCohort = newCohort({
1698
+ customName: DEFAULT_COHORT_NAME
1699
+ });
1700
+ const emptyInitialState = cohortsAdapter.getInitialState({
1701
+ currentCohortId: initialCohort.id,
1702
+ message: undefined
1703
+ });
1704
+ // Set the initial cohort in the adapter state
1705
+ const initialState$7 = cohortsAdapter.setOne(emptyInitialState, initialCohort);
1706
+ const getCurrentCohortId = (state)=>state.currentCohortId;
2093
1707
  /**
2094
- * Constructs a nested operation object based on the provided field and leaf operand.
2095
- * If the field does not contain a dot '.', it either assigns the field to the leaf operand (if applicable)
2096
- * or returns the leaf operand as is. When the field contains dots, it splits the field into parts,
2097
- * creates a "nested" operation for the root field, and recursively constructs the nested structure
2098
- * for the remaining portion of the field.
2099
- *
2100
- * @param {string} field - The hierarchical field path, with segments separated by dots (e.g., "root.child").
2101
- * @param {Operation} leafOperand - The operation to be nested within the specified path.
2102
- * @param parentPath - The parent path of the current field. Guppy nested filters require a parent path.
2103
- * @param depth
2104
- * @returns {Operation} A nested operation object that represents the structured path and operand.
2105
- */ const buildNestedWithParentPathGQLFilter = (field, leafOperand, parentPath = undefined)=>{
2106
- if (!field.includes('.')) {
2107
- return leafOperand;
2108
- }
2109
- const splitFieldArray = field.split('.');
2110
- const nextField = splitFieldArray.shift();
2111
- if (!nextField) {
2112
- console.warn('Invalid field path:', field);
2113
- return leafOperand;
2114
- }
2115
- const currentPath = parentPath ? `${parentPath}.${nextField}` : nextField;
2116
- return {
2117
- nested: {
2118
- path: currentPath,
2119
- ...buildNestedGQLFilter(splitFieldArray.join('.'), leafOperand, currentPath)
2120
- }
2121
- };
2122
- };
2123
- const buildCohortGqlOperator = (fs)=>{
2124
- if (!fs || !fs.root) return undefined;
2125
- const fsKeys = Object.keys(fs.root);
2126
- // if no keys return undefined
2127
- if (fsKeys.length === 0) return undefined;
2128
- // TODO consider changing FilterSet: mode to support joinOrToAll as FilterSet mode
2129
- // find key using keyword "joinOrToAll"
2130
- const joinOrToAllKey = fsKeys.filter((x)=>x.includes('joinOrToAll'));
2131
- switch(fs.mode){
2132
- case 'and':
2133
- if (joinOrToAllKey.length === 1) {
2134
- const firstJoinOrToAllKey = joinOrToAllKey[0];
2135
- // Remove firstJoinOrToAllKey from Array
2136
- fsKeys.splice(fsKeys.indexOf(firstJoinOrToAllKey), 1);
2137
- const firstJoinOrToAllObj = fs.root[firstJoinOrToAllKey];
2138
- // make sure type is or/ Union
2139
- if (firstJoinOrToAllObj.operator === 'or') {
2140
- return {
2141
- or: firstJoinOrToAllObj?.operands.map((orObj)=>{
2142
- // go through each or statement and add all other filters to it
2143
- return {
2144
- and: [
2145
- convertFilterToNestedGqlFilter(orObj),
2146
- ...fsKeys.map((k)=>{
2147
- return convertFilterToNestedGqlFilter(fs.root[k]);
2148
- })
2149
- ]
2150
- };
2151
- })
2152
- };
2153
- } else {
2154
- console.error(`function buildCohortGqlOperator expecting "or" received "${firstJoinOrToAllObj.operator}" on key "${firstJoinOrToAllKey}"`);
2155
- }
2156
- } else if (joinOrToAllKey.length > 1) {
2157
- console.error(`function buildCohortGqlOperator expecting only one joinOrToAll received: ${joinOrToAllKey.length}`, fsKeys);
1708
+ * Redux slice for cohort filters
1709
+ */ const cohortManagerSlice = createSlice({
1710
+ name: 'cohort',
1711
+ initialState: initialState$7,
1712
+ reducers: {
1713
+ createNewCohort: (state, action)=>{
1714
+ const baseName = action.payload.name || `Cohort`;
1715
+ const uniqueName = generateUniqueName(Object.values(state.entities), baseName);
1716
+ const cohort = newCohort({
1717
+ filters: action.payload.filters,
1718
+ customName: uniqueName
1719
+ });
1720
+ cohortsAdapter.addOne(state, cohort);
1721
+ if (action.payload?.setAsCurrent) {
1722
+ state.currentCohortId = cohort.id;
2158
1723
  }
2159
- return {
2160
- // TODO: Replace fixed AND with cohort top level operation like Union or Intersection
2161
- [fs.mode]: fsKeys.map((k)=>{
2162
- return convertFilterToGqlFilter(fs.root[k]);
2163
- })
2164
- };
2165
- case 'or':
2166
- return {
2167
- [fs.mode]: fsKeys.map((k)=>{
2168
- return convertFilterToGqlFilter(fs.root[k]);
2169
- })
2170
- };
2171
- }
2172
- };
2173
- /**
2174
- * Merged two FilterSets returning the merged pair.
2175
- * @param a - first FilterSet
2176
- * @param b - other FilterSet
2177
- */ const joinFilters = (a, b)=>{
2178
- return {
2179
- mode: a.mode,
2180
- root: {
2181
- ...a.root,
2182
- ...b.root
2183
- }
2184
- };
2185
- };
2186
- const convertFilterSetToOperation = (fs)=>{
2187
- if (!fs) return undefined;
2188
- switch(fs.mode){
2189
- case 'and':
2190
- return Object.keys(fs.root).length == 0 ? undefined : {
2191
- operator: fs.mode,
2192
- operands: Object.keys(fs.root).map((k)=>{
2193
- return fs.root[k];
2194
- })
2195
- };
2196
- }
2197
- return undefined;
2198
- };
2199
- const extractContents = (filter)=>{
2200
- if (isGQLUnion(filter)) {
2201
- return filter.or;
2202
- }
2203
- if (isGQLIntersection(filter)) {
2204
- return filter.and;
2205
- }
2206
- return undefined;
2207
- };
2208
- class ToGqlAllNested {
2209
- constructor(){
2210
- this.handleEquals = (op)=>{
2211
- if (op.field.includes('.')) {
2212
- const leafField = op.field.split('.').at(-1) ?? 'unset';
2213
- return buildNestedGQLFilter(op.field, {
2214
- '=': {
2215
- [leafField]: op.operand
2216
- }
2217
- });
2218
- }
2219
- return {
2220
- '=': {
2221
- [op.field]: op.operand
1724
+ },
1725
+ updateCohortName: (state, action)=>{
1726
+ const { id, name } = action.payload;
1727
+ cohortsAdapter.updateOne(state, {
1728
+ id: id,
1729
+ changes: {
1730
+ name: name,
1731
+ modified: true,
1732
+ modifiedDatetime: new Date().toISOString()
2222
1733
  }
2223
- };
2224
- };
2225
- this.handleNotEquals = (op)=>{
2226
- if (op.field.includes('.')) {
2227
- const leafField = op.field.split('.').at(-1) ?? 'unset';
2228
- return buildNestedGQLFilter(op.field, {
2229
- '!=': {
2230
- [leafField]: op.operand
2231
- }
1734
+ });
1735
+ },
1736
+ removeCohort: (state, action)=>{
1737
+ const { id: cohortId } = action.payload;
1738
+ const removedCohortName = state.entities[cohortId].name;
1739
+ const totalCohorts = Object.keys(state.entities).length;
1740
+ if (totalCohorts <= 1) {
1741
+ cohortsAdapter.removeAll(state);
1742
+ const defaultCohort = newCohort({
1743
+ filters: {},
1744
+ customName: DEFAULT_COHORT_NAME
2232
1745
  });
2233
- }
2234
- return {
2235
- '!=': {
2236
- [op.field]: op.operand
1746
+ cohortsAdapter.addOne(state, defaultCohort);
1747
+ state.currentCohortId = defaultCohort.id;
1748
+ if (action?.payload.shouldShowMessage) {
1749
+ state.message = [
1750
+ `deleteCohort|${removedCohortName}|${state.currentCohortId}`
1751
+ ];
2237
1752
  }
2238
- };
2239
- };
2240
- this.handleLessThan = (op)=>{
2241
- if (op.field.includes('.')) {
2242
- const leafField = op.field.split('.').at(-1) ?? 'unset';
2243
- return buildNestedGQLFilter(op.field, {
2244
- '<': {
2245
- [leafField]: op.operand
2246
- }
2247
- });
1753
+ return;
2248
1754
  }
2249
- return {
2250
- '<': {
2251
- [op.field]: op.operand
2252
- }
2253
- };
2254
- };
2255
- this.handleLessThanOrEquals = (op)=>{
2256
- if (op.field.includes('.')) {
2257
- const leafField = op.field.split('.').at(-1) ?? 'unset';
2258
- return buildNestedGQLFilter(op.field, {
2259
- '<=': {
2260
- [leafField]: op.operand
2261
- }
2262
- });
1755
+ cohortsAdapter.removeOne(state, cohortId);
1756
+ // deleted the current cohort so set to the most recent cohort
1757
+ if (state.currentCohortId === cohortId) {
1758
+ const remainingIds = Object.keys(state.entities);
1759
+ state.currentCohortId = remainingIds[0];
2263
1760
  }
2264
- return {
2265
- '<=': {
2266
- [op.field]: op.operand
2267
- }
2268
- };
2269
- };
2270
- this.handleGreaterThan = (op)=>{
2271
- if (op.field.includes('.')) {
2272
- const leafField = op.field.split('.').at(-1) ?? 'unset';
2273
- return buildNestedGQLFilter(op.field, {
2274
- '>': {
2275
- [leafField]: op.operand
2276
- }
2277
- });
1761
+ if (action?.payload.shouldShowMessage) {
1762
+ state.message = [
1763
+ `deleteCohort|${removedCohortName}|${state.currentCohortId}`
1764
+ ];
2278
1765
  }
2279
- return {
2280
- '>': {
2281
- [op.field]: op.operand
2282
- }
2283
- };
2284
- };
2285
- this.handleGreaterThanOrEquals = (op)=>{
2286
- if (op.field.includes('.')) {
2287
- const leafField = op.field.split('.').at(-1) ?? 'unset';
2288
- return buildNestedGQLFilter(op.field, {
2289
- '>=': {
2290
- [leafField]: op.operand
2291
- }
2292
- });
1766
+ },
1767
+ // adds a filter to the cohort filter set at the given index
1768
+ updateCohortFilter: (state, action)=>{
1769
+ const { index, field, filter } = action.payload;
1770
+ const currentCohortId = getCurrentCohortId(state);
1771
+ if (!state.entities[currentCohortId]) {
1772
+ return;
2293
1773
  }
2294
- return {
2295
- '>=': {
2296
- [op.field]: op.operand
1774
+ cohortsAdapter.updateOne(state, {
1775
+ id: currentCohortId,
1776
+ changes: {
1777
+ filters: {
1778
+ ...state.entities[currentCohortId].filters,
1779
+ [index]: {
1780
+ mode: state.entities[currentCohortId]?.filters[index]?.mode ?? 'and',
1781
+ root: {
1782
+ ...state.entities[currentCohortId]?.filters[index]?.root,
1783
+ [field]: filter
1784
+ }
1785
+ }
1786
+ },
1787
+ modified: true,
1788
+ modifiedDatetime: new Date().toISOString()
2297
1789
  }
2298
- };
2299
- };
2300
- this.handleIncludes = (op)=>{
2301
- if (op.field.includes('.')) {
2302
- const leafField = op.field.split('.').at(-1) ?? 'unset';
2303
- return buildNestedGQLFilter(op.field, {
2304
- in: {
2305
- [leafField]: op.operands
2306
- }
2307
- });
1790
+ });
1791
+ },
1792
+ setCohortFilter: (state, action)=>{
1793
+ const { index, filters } = action.payload;
1794
+ const currentCohortId = getCurrentCohortId(state);
1795
+ if (!state.entities[currentCohortId]) {
1796
+ console.error(`no cohort with id=${currentCohortId} defined`);
1797
+ return;
2308
1798
  }
2309
- return {
2310
- in: {
2311
- [op.field]: op.operands
1799
+ cohortsAdapter.updateOne(state, {
1800
+ id: currentCohortId,
1801
+ changes: {
1802
+ filters: {
1803
+ ...state.entities[currentCohortId].filters,
1804
+ [index]: filters
1805
+ },
1806
+ modified: true,
1807
+ modifiedDatetime: new Date().toISOString()
2312
1808
  }
2313
- };
2314
- };
2315
- this.handleExcludes = (op)=>{
2316
- if (op.field.includes('.')) {
2317
- const leafField = op.field.split('.').at(-1) ?? 'unset';
2318
- return buildNestedGQLFilter(op.field, {
2319
- exclude: {
2320
- [leafField]: op.operands
2321
- }
2322
- });
1809
+ });
1810
+ },
1811
+ setCohortIndexFilters: (state, action)=>{
1812
+ const currentCohortId = getCurrentCohortId(state);
1813
+ if (!state.entities[currentCohortId]) {
1814
+ console.error(`no cohort with id=${currentCohortId} defined`);
1815
+ return;
2323
1816
  }
2324
- return {
2325
- exclude: {
2326
- [op.field]: op.operands
1817
+ cohortsAdapter.updateOne(state, {
1818
+ id: currentCohortId,
1819
+ changes: {
1820
+ filters: action.payload.filters,
1821
+ modified: true,
1822
+ modifiedDatetime: new Date().toISOString()
2327
1823
  }
2328
- };
2329
- };
2330
- this.handleExcludeIfAny = (op)=>{
2331
- if (op.field.includes('.')) {
2332
- const leafField = op.field.split('.').at(-1) ?? 'unset';
2333
- return buildNestedGQLFilter(op.field, {
2334
- excludeifany: {
2335
- [leafField]: op.operands
2336
- }
2337
- });
2338
- }
2339
- return {
2340
- excludeifany: {
2341
- [op.field]: op.operands
2342
- }
2343
- };
2344
- };
2345
- this.handleIntersection = (op)=>({
2346
- and: op.operands.map((x)=>convertFilterToGqlFilter(x))
2347
- });
2348
- this.handleUnion = (op)=>({
2349
- or: op.operands.map((x)=>convertFilterToGqlFilter(x))
2350
1824
  });
2351
- this.handleMissing = (op)=>{
2352
- if (op.field.includes('.')) {
2353
- const leafField = op.field.split('.').at(-1) ?? 'unset';
2354
- return buildNestedGQLFilter(op.field, {
2355
- is: {
2356
- [leafField]: 'MISSING'
2357
- }
2358
- });
2359
- }
2360
- return {
2361
- is: {
2362
- [op.field]: 'MISSING'
2363
- }
2364
- };
2365
- };
2366
- this.handleExists = (op)=>{
2367
- if (op.field.includes('.')) {
2368
- const leafField = op.field.split('.').at(-1) ?? 'unset';
2369
- return buildNestedGQLFilter(op.field, {
2370
- not: {
2371
- [leafField]: op?.operand ?? null
2372
- }
2373
- });
1825
+ },
1826
+ // removes a filter to the cohort filter set at the given index
1827
+ removeCohortFilter: (state, action)=>{
1828
+ const { index, field } = action.payload;
1829
+ const currentCohortId = getCurrentCohortId(state);
1830
+ if (!state.entities[currentCohortId]) {
1831
+ console.error(`no cohort with id=${currentCohortId} defined`);
1832
+ return;
2374
1833
  }
2375
- return {
2376
- not: {
2377
- [op.field]: op?.operand ?? null
2378
- }
2379
- };
2380
- };
2381
- this.handleNestedFilter = (op)=>{
2382
- const child = convertFilterToGqlFilter(op.operand);
2383
- return {
2384
- nested: {
2385
- path: op.path,
2386
- ...child
2387
- }
2388
- };
2389
- };
2390
- }
2391
- }
2392
- const convertFilterToNestedGqlFilter = (filter)=>{
2393
- const handler = new ToGqlAllNested();
2394
- return handleOperation(handler, filter);
2395
- };
2396
- const convertFilterSetToNestedGqlFilter = (fs, toplevelOp = 'and')=>{
2397
- const fsKeys = Object.keys(fs.root);
2398
- // if no keys return undefined
2399
- if (fsKeys.length === 0) return {
2400
- and: []
2401
- };
2402
- return toplevelOp === 'and' ? {
2403
- and: fsKeys.map((key)=>convertFilterToNestedGqlFilter(fs.root[key]))
2404
- } : {
2405
- or: fsKeys.map((key)=>convertFilterToNestedGqlFilter(fs.root[key]))
2406
- };
2407
- };
2408
-
2409
- const defaultCohortNameGenerator = ()=>`Custom cohort ${new Date().toLocaleString('en-CA', {
2410
- timeZone: 'America/Chicago',
2411
- hour12: false
2412
- }).replace(',', '')}`;
2413
- const isNameUnique = (entities, name, excludeId)=>{
2414
- const trimmedName = name.trim();
2415
- if (!trimmedName) return false;
2416
- return !entities.some((cohort)=>cohort && cohort.id !== excludeId && cohort.name.trim().toLowerCase() === trimmedName.toLowerCase());
2417
- };
2418
- const generateUniqueName = (entities, baseName)=>{
2419
- const trimmedBaseName = baseName.trim();
2420
- // If base name is unique, use it
2421
- if (isNameUnique(entities, trimmedBaseName)) {
2422
- return trimmedBaseName;
2423
- }
2424
- // Find a unique name by appending numbers
2425
- let counter = 1;
2426
- let uniqueName;
2427
- do {
2428
- uniqueName = `${trimmedBaseName} (${counter})`;
2429
- counter++;
2430
- }while (!isNameUnique(entities, uniqueName))
2431
- return uniqueName;
2432
- };
2433
- /**
2434
- * This function takes a FilterSet object and a prefix string as input.
2435
- * It filters the root property of the FilterSet object and returns a
2436
- * new FilterSet object that only contains filters with field names
2437
- * that start with the specified prefix.
2438
- *
2439
- * @param fs - The FilterSet object to filter
2440
- * @param prefix - The prefix to filter by
2441
- * @returns - A new FilterSet object that only contains filters with field names that start with the specified prefix
2442
- * @category Filters
2443
- */ const extractFiltersWithPrefixFromFilterSet = (fs, prefix)=>{
2444
- if (fs === undefined || fs.root === undefined) {
2445
- return {
2446
- mode: 'and',
2447
- root: {}
2448
- };
2449
- }
2450
- return Object.values(fs.root).reduce((acc, filter)=>{
2451
- if (isIntersectionOrUnion(filter) || isNestedFilter(filter)) return acc;
2452
- if (filter.field.startsWith(prefix)) {
2453
- acc.root[filter.field] = filter;
2454
- }
2455
- return acc;
2456
- }, {
2457
- mode: 'and',
2458
- root: {}
2459
- });
2460
- };
2461
-
2462
- /**
2463
- * Cohorts in Gen3 are defined as a set of filters for each index in the data.
2464
- * This means one cohort id defined for all "tabs" in CohortBuilder (explorer)
2465
- * Switching a cohort id means all the cohorts for the index are changed.
2466
- */ const DEFAULT_COHORT_NAME = 'Cohort';
2467
- const newCohort = ({ filters = {}, customName })=>{
2468
- const ts = new Date().toISOString();
2469
- const newName = customName ?? defaultCohortNameGenerator();
2470
- const newId = createCohortId();
2471
- return {
2472
- name: newName,
2473
- id: newId,
2474
- filters: filters ?? {},
2475
- modified: false,
2476
- saved: false,
2477
- createdDatetime: ts,
2478
- modifiedDatetime: ts,
2479
- counts: {}
2480
- };
2481
- };
2482
- const nanoid = customAlphabet('1234567890abcdef', 16);
2483
- const createCohortId = ()=>nanoid();
2484
- const cohortsAdapter = createEntityAdapter({
2485
- sortComparer: (a, b)=>{
2486
- if (a.modifiedDatetime <= b.modifiedDatetime) return 1;
2487
- else return -1;
2488
- },
2489
- selectId: (cohort)=>cohort.id
2490
- });
2491
- // Create an initial unsaved cohort
2492
- const initialCohort = newCohort({
2493
- customName: DEFAULT_COHORT_NAME
2494
- });
2495
- const emptyInitialState = cohortsAdapter.getInitialState({
2496
- currentCohortId: initialCohort.id,
2497
- message: undefined
2498
- });
2499
- // Set the initial cohort in the adapter state
2500
- const initialState$7 = cohortsAdapter.setOne(emptyInitialState, initialCohort);
2501
- const getCurrentCohortId = (state)=>state.currentCohortId;
2502
- /**
2503
- * Redux slice for cohort filters
2504
- */ const cohortManagerSlice = createSlice({
2505
- name: 'cohort',
2506
- initialState: initialState$7,
2507
- reducers: {
2508
- createNewCohort: (state, action)=>{
2509
- const baseName = action.payload.name || `Cohort`;
2510
- const uniqueName = generateUniqueName(Object.values(state.entities), baseName);
2511
- const cohort = newCohort({
2512
- filters: action.payload.filters,
2513
- customName: uniqueName
2514
- });
2515
- cohortsAdapter.addOne(state, cohort);
2516
- if (action.payload?.setAsCurrent) {
2517
- state.currentCohortId = cohort.id;
1834
+ const filters = state.entities[currentCohortId]?.filters[index]?.root;
1835
+ if (!filters) {
1836
+ return;
2518
1837
  }
2519
- },
2520
- updateCohortName: (state, action)=>{
2521
- const { id, name } = action.payload;
1838
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1839
+ const { [field]: _a, ...updated } = filters;
2522
1840
  cohortsAdapter.updateOne(state, {
2523
- id: id,
1841
+ id: currentCohortId,
2524
1842
  changes: {
2525
- name: name,
1843
+ filters: {
1844
+ ...state.entities[currentCohortId]?.filters,
1845
+ [index]: {
1846
+ mode: state.entities[currentCohortId].filters[index].mode,
1847
+ root: updated
1848
+ }
1849
+ },
2526
1850
  modified: true,
2527
1851
  modifiedDatetime: new Date().toISOString()
2528
1852
  }
2529
1853
  });
2530
1854
  },
2531
- removeCohort: (state, action)=>{
2532
- const { id: cohortId } = action.payload;
2533
- const removedCohortName = state.entities[cohortId].name;
2534
- const totalCohorts = Object.keys(state.entities).length;
2535
- if (totalCohorts <= 1) {
2536
- cohortsAdapter.removeAll(state);
2537
- const defaultCohort = newCohort({
2538
- filters: {},
2539
- customName: DEFAULT_COHORT_NAME
2540
- });
2541
- cohortsAdapter.addOne(state, defaultCohort);
2542
- state.currentCohortId = defaultCohort.id;
2543
- if (action?.payload.shouldShowMessage) {
2544
- state.message = [
2545
- `deleteCohort|${removedCohortName}|${state.currentCohortId}`
2546
- ];
1855
+ duplicateCohort: (state)=>{
1856
+ const currentCohortId = getCurrentCohortId(state);
1857
+ const currentCohort = state.entities[currentCohortId];
1858
+ const newName = generateUniqueName(Object.values(state.entities), currentCohort.name);
1859
+ const duplicatedCohort = newCohort({
1860
+ filters: {
1861
+ ...currentCohort.filters
1862
+ },
1863
+ customName: newName
1864
+ });
1865
+ cohortsAdapter.addOne(state, {
1866
+ ...duplicatedCohort,
1867
+ counts: {
1868
+ ...currentCohort.counts
2547
1869
  }
2548
- return;
2549
- }
2550
- cohortsAdapter.removeOne(state, cohortId);
2551
- // deleted the current cohort so set to the most recent cohort
2552
- if (state.currentCohortId === cohortId) {
2553
- const remainingIds = Object.keys(state.entities);
2554
- state.currentCohortId = remainingIds[0];
2555
- }
2556
- if (action?.payload.shouldShowMessage) {
2557
- state.message = [
2558
- `deleteCohort|${removedCohortName}|${state.currentCohortId}`
2559
- ];
2560
- }
1870
+ });
1871
+ state.currentCohortId = duplicatedCohort.id;
2561
1872
  },
2562
- // adds a filter to the cohort filter set at the given index
2563
- updateCohortFilter: (state, action)=>{
2564
- const { index, field, filter } = action.payload;
1873
+ // removes all filters from the cohort filter set at the given index
1874
+ clearCohortFilters: (state, action)=>{
1875
+ const { index } = action.payload;
2565
1876
  const currentCohortId = getCurrentCohortId(state);
2566
1877
  if (!state.entities[currentCohortId]) {
2567
- return;
2568
- }
2569
- cohortsAdapter.updateOne(state, {
2570
- id: currentCohortId,
2571
- changes: {
2572
- filters: {
2573
- ...state.entities[currentCohortId].filters,
2574
- [index]: {
2575
- mode: state.entities[currentCohortId]?.filters[index]?.mode ?? 'and',
2576
- root: {
2577
- ...state.entities[currentCohortId]?.filters[index]?.root,
2578
- [field]: filter
2579
- }
2580
- }
2581
- },
2582
- modified: true,
2583
- modifiedDatetime: new Date().toISOString()
2584
- }
2585
- });
2586
- },
2587
- setCohortFilter: (state, action)=>{
2588
- const { index, filters } = action.payload;
2589
- const currentCohortId = getCurrentCohortId(state);
2590
- if (!state.entities[currentCohortId]) {
2591
- console.error(`no cohort with id=${currentCohortId} defined`);
2592
- return;
2593
- }
2594
- cohortsAdapter.updateOne(state, {
2595
- id: currentCohortId,
2596
- changes: {
2597
- filters: {
2598
- ...state.entities[currentCohortId].filters,
2599
- [index]: filters
2600
- },
2601
- modified: true,
2602
- modifiedDatetime: new Date().toISOString()
2603
- }
2604
- });
2605
- },
2606
- setCohortIndexFilters: (state, action)=>{
2607
- const currentCohortId = getCurrentCohortId(state);
2608
- if (!state.entities[currentCohortId]) {
2609
- console.error(`no cohort with id=${currentCohortId} defined`);
2610
- return;
2611
- }
2612
- cohortsAdapter.updateOne(state, {
2613
- id: currentCohortId,
2614
- changes: {
2615
- filters: action.payload.filters,
2616
- modified: true,
2617
- modifiedDatetime: new Date().toISOString()
2618
- }
2619
- });
2620
- },
2621
- // removes a filter to the cohort filter set at the given index
2622
- removeCohortFilter: (state, action)=>{
2623
- const { index, field } = action.payload;
2624
- const currentCohortId = getCurrentCohortId(state);
2625
- if (!state.entities[currentCohortId]) {
2626
- console.error(`no cohort with id=${currentCohortId} defined`);
2627
- return;
2628
- }
2629
- const filters = state.entities[currentCohortId]?.filters[index]?.root;
2630
- if (!filters) {
2631
- return;
2632
- }
2633
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
2634
- const { [field]: _a, ...updated } = filters;
2635
- cohortsAdapter.updateOne(state, {
2636
- id: currentCohortId,
2637
- changes: {
2638
- filters: {
2639
- ...state.entities[currentCohortId]?.filters,
2640
- [index]: {
2641
- mode: state.entities[currentCohortId].filters[index].mode,
2642
- root: updated
2643
- }
2644
- },
2645
- modified: true,
2646
- modifiedDatetime: new Date().toISOString()
2647
- }
2648
- });
2649
- },
2650
- duplicateCohort: (state)=>{
2651
- const currentCohortId = getCurrentCohortId(state);
2652
- const currentCohort = state.entities[currentCohortId];
2653
- const newName = generateUniqueName(Object.values(state.entities), currentCohort.name);
2654
- const duplicatedCohort = newCohort({
2655
- filters: {
2656
- ...currentCohort.filters
2657
- },
2658
- customName: newName
2659
- });
2660
- cohortsAdapter.addOne(state, {
2661
- ...duplicatedCohort,
2662
- counts: {
2663
- ...currentCohort.counts
2664
- }
2665
- });
2666
- state.currentCohortId = duplicatedCohort.id;
2667
- },
2668
- // removes all filters from the cohort filter set at the given index
2669
- clearCohortFilters: (state, action)=>{
2670
- const { index } = action.payload;
2671
- const currentCohortId = getCurrentCohortId(state);
2672
- if (!state.entities[currentCohortId]) {
2673
- console.error(`no cohort with id=${currentCohortId} defined`);
2674
- return;
2675
- }
2676
- const filters = state.entities[currentCohortId]?.filters[index]?.root;
2677
- if (!filters) {
1878
+ console.error(`no cohort with id=${currentCohortId} defined`);
1879
+ return;
1880
+ }
1881
+ const filters = state.entities[currentCohortId]?.filters[index]?.root;
1882
+ if (!filters) {
2678
1883
  return;
2679
1884
  }
2680
1885
  cohortsAdapter.updateOne(state, {
@@ -3080,171 +2285,6 @@ const rootReducer = combineReducers({
3080
2285
  sower: sowerReducer
3081
2286
  });
3082
2287
 
3083
- /**
3084
- * Flattens a deep nested JSON object skipping
3085
- * the first level to avoid potentially flattening
3086
- * non-nested data.
3087
- * @param {JSON} json
3088
- */ function flattenJson(json) {
3089
- const flattenedJson = [];
3090
- Object.keys(json).forEach((key)=>{
3091
- flattenedJson.push(flatten(json[key], {
3092
- delimiter: '_'
3093
- }));
3094
- });
3095
- return flattenedJson;
3096
- }
3097
- /**
3098
- * Converts JSON based on a config.
3099
- * @param {JSON} json
3100
- * @param {Object} config
3101
- */ async function conversion(json, config) {
3102
- return Papa.unparse(json, config);
3103
- }
3104
- /**
3105
- * Converts JSON to a specified file format.
3106
- * Defaults to JSON if file format is not supported.
3107
- * @param {JSON} json
3108
- * @param {string} format
3109
- */ async function jsonToFormat(json, format) {
3110
- if (Object.keys(FILE_DELIMITERS).includes(format)) {
3111
- const flatJson = flattenJson(json);
3112
- const data = await conversion(flatJson, {
3113
- delimiter: FILE_DELIMITERS[format]
3114
- });
3115
- return data;
3116
- }
3117
- return json;
3118
- }
3119
-
3120
- /**
3121
- * Prepares a URL for downloading by appending '/download' to the provided apiUrl.
3122
- *
3123
- * @param {string} apiUrl - The base URL to be used for preparing the download URL.
3124
- * @returns {URL} - The prepared download URL as a URL object.
3125
- */ const prepareUrl = (apiUrl)=>`${apiUrl}/download`;
3126
- /**
3127
- * Prepares a fetch configuration object for downloading files from Guppy.
3128
- *
3129
- * @param {GuppyDownloadDataParams} parameters - The parameters to include in the request body.
3130
- * @param {string} csrfToken - The CSRF token to include in the request headers.
3131
- * @returns {FetchConfig} - The prepared fetch configuration object.
3132
- */ const prepareFetchConfig = (parameters, csrfToken)=>{
3133
- const headers = new Headers({
3134
- Accept: 'application/json',
3135
- 'Content-Type': 'application/json',
3136
- ...csrfToken !== undefined && {
3137
- 'X-CSRF-Token': csrfToken
3138
- }
3139
- });
3140
- if (process.env.NODE_ENV === 'development') {
3141
- // NOTE: This cookie can only be accessed from the client side
3142
- // in development mode. Otherwise, the cookie is set as httpOnly
3143
- const accessToken = getCookie('credentials_token');
3144
- if (accessToken) headers.set('Authorization', `Bearer ${accessToken}`);
3145
- }
3146
- return {
3147
- method: 'POST',
3148
- headers: headers,
3149
- body: JSON.stringify({
3150
- type: parameters.type,
3151
- filter: convertFilterSetToGqlFilter(parameters.filter),
3152
- accessibility: parameters.accessibility,
3153
- fields: parameters?.fields,
3154
- sort: parameters?.sort
3155
- })
3156
- };
3157
- };
3158
- /**
3159
- * Downloads a file from Guppy using the provided parameters.
3160
- * It will optionally convert the data to the specified format.
3161
- *
3162
- * @param {DownloadFromGuppyParams} parameters - The parameters to use for the download request.
3163
- * @param onStart - The function to call when the download starts.
3164
- * @param onDone - The function to call when the download is done.
3165
- * @param onError - The function to call when the download fails.
3166
- * @param onAbort - The function to call when the download is aborted.
3167
- * @param signal - AbortSignal to use for the request.
3168
- */ const downloadFromGuppyToBlob = async ({ parameters, onStart = ()=>null, onDone = (_)=>null, onError = (_)=>null, onAbort = ()=>null, signal = undefined })=>{
3169
- const csrfToken = selectCSRFToken(coreStore.getState());
3170
- onStart?.();
3171
- const url = prepareUrl(GEN3_GUPPY_API);
3172
- const fetchConfig = prepareFetchConfig(parameters, csrfToken);
3173
- fetch(url.toString(), {
3174
- ...fetchConfig,
3175
- ...signal ? {
3176
- signal: signal
3177
- } : {}
3178
- }).then(async (response)=>{
3179
- if (!response.ok) {
3180
- throw new Error(response.statusText);
3181
- }
3182
- let jsonData = await response.json();
3183
- if (parameters?.rootPath) {
3184
- // if rootPath is provided, extract the data from the rootPath
3185
- jsonData = JSONPath({
3186
- json: jsonData,
3187
- path: `$.[${parameters.rootPath}]`,
3188
- resultType: 'value'
3189
- });
3190
- }
3191
- // convert the data to the specified format and return a Blob
3192
- let str = '';
3193
- if (parameters.format === 'json') {
3194
- str = JSON.stringify(jsonData);
3195
- } else {
3196
- const convertedData = await jsonToFormat(jsonData, parameters.format);
3197
- if (isJSONObject(convertedData)) {
3198
- str = JSON.stringify(convertedData, null, 2);
3199
- } else {
3200
- str = convertedData;
3201
- }
3202
- }
3203
- return new Blob([
3204
- str
3205
- ], {
3206
- type: 'application/json'
3207
- });
3208
- }).then((blob)=>onDone?.(blob)).catch((error)=>{
3209
- // Abort is handle as an exception
3210
- if (error.name == 'AbortError') {
3211
- // handle abort()
3212
- onAbort?.();
3213
- }
3214
- onError?.(error);
3215
- });
3216
- };
3217
- const downloadJSONDataFromGuppy = async ({ parameters, onAbort = ()=>null, signal = undefined })=>{
3218
- const csrfToken = selectCSRFToken(coreStore.getState());
3219
- const url = prepareUrl(GEN3_GUPPY_API);
3220
- const fetchConfig = prepareFetchConfig(parameters, csrfToken);
3221
- try {
3222
- const response = await fetch(url.toString(), {
3223
- ...fetchConfig,
3224
- ...signal ? {
3225
- signal: signal
3226
- } : {}
3227
- });
3228
- let jsonData = await response.json();
3229
- if (parameters?.rootPath) {
3230
- // if rootPath is provided, extract the data from the rootPath
3231
- jsonData = JSONPath({
3232
- json: jsonData,
3233
- path: `$.[${parameters.rootPath}]`,
3234
- resultType: 'value'
3235
- });
3236
- }
3237
- // convert the data to the specified format and return a Blob
3238
- return jsonData;
3239
- } catch (error) {
3240
- // Abort is handle as an exception
3241
- if (error.name == 'AbortError') {
3242
- // handle abort()
3243
- onAbort?.();
3244
- }
3245
- throw new Error(error);
3246
- }
3247
- };
3248
2288
  const groupSharedFields = (data)=>{
3249
2289
  const reverseIndex = {};
3250
2290
  // Build reverse index: track which root keys contain each element
@@ -3882,145 +2922,940 @@ const useGetArrayTypes = ()=>{
3882
2922
  if (error) {
3883
2923
  return {};
3884
2924
  }
3885
- return data ? data['indices'] : {};
2925
+ return data ? data['indices'] : {};
2926
+ }
2927
+ };
2928
+ const useGetIndexFields = (index, indexPrefix = '')=>{
2929
+ const { data } = useGetFieldsForIndexQuery({
2930
+ index: index,
2931
+ indexPrefix: indexPrefix
2932
+ });
2933
+ return data ?? [];
2934
+ };
2935
+ const buildGetAggregationQuery = (type, fields, filters, accessibility = Accessibility.ALL, filterSelf = false, queryId, indexPrefix = '', filterName = 'filter')=>{
2936
+ const queryStart = isFilterEmpty(filters) ? `query getAggs${queryId ? `_${queryId}` : ''} {
2937
+ ${indexPrefix}_aggregation {
2938
+ ${type} (accessibility: ${accessibility}) {` : `query getAggs ($${filterName}: JSON) {
2939
+ ${indexPrefix}_aggregation {
2940
+ ${type} (filter: $${filterName}, filterSelf: ${filterSelf ? 'true' : 'false'}, accessibility: ${accessibility}) { _totalCount`;
2941
+ const query = `${queryStart}
2942
+ ${fields.map((field)=>histogramQueryStrForEachField(field))}
2943
+ }
2944
+ }
2945
+ }`;
2946
+ const queryBody = {
2947
+ query: query,
2948
+ variables: {
2949
+ [filterName]: convertFilterSetToGqlFilter(filters)
2950
+ }
2951
+ };
2952
+ return queryBody;
2953
+ };
2954
+ const buildGetStatsAggregationQuery = (type, fields, filters, accessibility = Accessibility.ALL, filterSelf = false, queryId, indexPrefix = '', filterName = 'filter')=>{
2955
+ const queryStart = isFilterEmpty(filters) ? `
2956
+ query getStatsAggs${queryId ? `_${queryId}` : ''} {
2957
+ ${indexPrefix}_aggregation {
2958
+ ${type} (accessibility: ${accessibility}) {` : `query getStatsAggs${queryId ? `_${queryId}` : ''} ($${filterName}: JSON) {
2959
+ ${indexPrefix}_aggregation {
2960
+ ${type} (filter: $${filterName}, filterSelf: ${filterSelf ? 'true' : 'false'}, accessibility: ${accessibility}) { _totalCount`;
2961
+ const query = `${queryStart}
2962
+ ${fields.map((field)=>statsQueryStrForEachField(field))}
2963
+ }
2964
+ }
2965
+ }`;
2966
+ const queryBody = {
2967
+ query: query,
2968
+ variables: {
2969
+ [filterName]: convertFilterSetToGqlFilter(filters)
2970
+ }
2971
+ };
2972
+ return queryBody;
2973
+ };
2974
+ const { useGetRawDataAndTotalCountsQuery, useGetAccessibleDataQuery, useGetAllFieldsForTypeQuery, useGetAggsQuery, useLazyGetAggsQuery, useGetStatsAggregationsQuery, useLazyGetStatsAggregationsQuery, useGetSubAggsQuery, useGetCountsQuery, useLazyGetCountsQuery, useGetFieldCountSummaryQuery, useGetFieldsForIndexQuery, useGetSharedFieldsForIndexQuery, useGeneralGQLQuery, useLazyGeneralGQLQuery, useCustomRangeQuery, useLazyCustomRangeQuery, useGetObjectIdsQuery, useLazyGetObjectIdsQuery } = explorerApi;
2975
+
2976
+ /**
2977
+ * Defines coreListeners for adding middleware.
2978
+ * used to update the current Cohort Case count
2979
+ */ const coreStoreListenerMiddleware = createListenerMiddleware();
2980
+ const startCoreListening = coreStoreListenerMiddleware.startListening;
2981
+ /**
2982
+ * When a getCounts is requested, it might be for a cohort.
2983
+ * If so, then update the cohort's counts for that index
2984
+ */ startCoreListening({
2985
+ matcher: isAnyOf(explorerApi.endpoints.getCounts.matchFulfilled),
2986
+ effect: async (action, listenerApi)=>{
2987
+ const counts = action.payload;
2988
+ const index = action?.meta?.arg?.originalArgs?.type; // note this is the guppy index name
2989
+ const cohortId = action?.meta?.arg?.originalArgs?.queryId;
2990
+ if (cohortId && index) {
2991
+ listenerApi.dispatch(updateCohortIndexCountById({
2992
+ index,
2993
+ cohortId,
2994
+ counts
2995
+ }));
2996
+ }
2997
+ }
2998
+ });
2999
+
3000
+ /**
3001
+ * Low-level helper to fetch Arborist resources for the current user.
3002
+ * Adds an Authorization header when a token is provided and normalizes the response
3003
+ * to a simple string[] of resource paths.
3004
+ *
3005
+ * token { string | null } - access token to use for authorization
3006
+ * useService { boolean } - use the arborist service endpoint instead of the public arborist API
3007
+ */ async function fetchArboristResources(token, useService = false) {
3008
+ const headers = {};
3009
+ if (token) {
3010
+ headers.Authorization = `Bearer ${token}`;
3011
+ }
3012
+ const url = useService ? `${GEN3_AUTHZ_SERVICE}/resource` : `${GEN3_AUTHZ_API}/resources`;
3013
+ const res = await fetch(url, {
3014
+ headers
3015
+ });
3016
+ if (!res.ok) {
3017
+ console.error('@gen3/core:fetchArboristResources /resource failed:', res.status, await res.text());
3018
+ return [];
3019
+ }
3020
+ const data = await res.json();
3021
+ return data.resources ?? [];
3022
+ }
3023
+
3024
+ const persistConfig = {
3025
+ key: `${GEN3_COMMONS_NAME}-root`,
3026
+ version: 1,
3027
+ storage,
3028
+ whitelist: [
3029
+ 'cohorts',
3030
+ 'activeWorkspace',
3031
+ 'cart',
3032
+ 'workspaceKernels',
3033
+ 'tieredWorkspace'
3034
+ ]
3035
+ };
3036
+ const persistedReducer = persistReducer(persistConfig, rootReducer);
3037
+ const setupCoreStore = (preloadedState)=>configureStore({
3038
+ reducer: persistedReducer,
3039
+ preloadedState,
3040
+ middleware: (getDefaultMiddleware)=>getDefaultMiddleware({
3041
+ serializableCheck: {
3042
+ ignoredActions: [
3043
+ FLUSH,
3044
+ REHYDRATE,
3045
+ PAUSE,
3046
+ PERSIST,
3047
+ PURGE,
3048
+ REGISTER
3049
+ ]
3050
+ }
3051
+ }).concat(gen3ServicesReducerMiddleware, guppyAPISliceMiddleware, userAuthApiMiddleware).prepend(coreStoreListenerMiddleware.middleware)
3052
+ });
3053
+ const coreStore = setupCoreStore();
3054
+ setupListeners(coreStore.dispatch);
3055
+
3056
+ const persistor = persistStore(coreStore);
3057
+ const CoreProvider = ({ children })=>{
3058
+ return /*#__PURE__*/ React__default.createElement(Provider, {
3059
+ store: coreStore
3060
+ }, /*#__PURE__*/ React__default.createElement(PersistGate, {
3061
+ loading: null,
3062
+ persistor: persistor
3063
+ }, children));
3064
+ };
3065
+
3066
+ /**
3067
+ * @description Creates a externalLoginApi for listing the configured identity providers
3068
+ * in workspace token service. Includes user token expiration time.
3069
+ * @see https://github.com/uc-cdis/workspace-token-service/tree/master
3070
+ */ const externalLoginApi = gen3Api.injectEndpoints({
3071
+ endpoints: (builder)=>({
3072
+ getExternalLogins: builder.query({
3073
+ query: ()=>({
3074
+ url: `${GEN3_WTS_API}/external_oidc/`
3075
+ })
3076
+ }),
3077
+ isExternalConnected: builder.query({
3078
+ query: (idp)=>({
3079
+ url: `${GEN3_WTS_API}/oauth2/connected?idp=${idp}`
3080
+ }),
3081
+ transformResponse: ()=>{
3082
+ return true; // if success then connected is true
3083
+ }
3084
+ })
3085
+ })
3086
+ });
3087
+ const { useGetExternalLoginsQuery, useLazyGetExternalLoginsQuery, useLazyIsExternalConnectedQuery, useIsExternalConnectedQuery } = externalLoginApi;
3088
+
3089
+ /**
3090
+ * Custom error class for resolving DRS object ID errors
3091
+ */ class ResolveDRSError extends Error {
3092
+ constructor(message){
3093
+ super(message);
3094
+ this.name = 'ResolveDRSError';
3095
+ }
3096
+ }
3097
+ /**
3098
+ * Resolves DRS object ID from a list of guids using dataguids.org
3099
+ * Use of this function should be limited
3100
+ * @param guidsForHostnameResolution
3101
+ */ const resolveDRSWithDataGUISOrg = async (guidsForHostnameResolution)=>{
3102
+ const INVALID_INPUT_ERROR = 'Invalid input: guidsForHostnameResolution must be an array of strings';
3103
+ const RESOLVE_DRS_ERROR = 'Failed to resolve DRS object ID';
3104
+ if (!Array.isArray(guidsForHostnameResolution) || !guidsForHostnameResolution.every((guid)=>typeof guid === 'string')) {
3105
+ throw new Error(INVALID_INPUT_ERROR);
3106
+ }
3107
+ try {
3108
+ const responses = await Promise.allSettled(guidsForHostnameResolution.map(async (guid)=>{
3109
+ try {
3110
+ const response = await fetch(`https://dataguids.org/index/${guid}`);
3111
+ if (response.status === 200) {
3112
+ try {
3113
+ return await response.json();
3114
+ // oxlint-disable-next-line no-unused-vars
3115
+ } catch (error) {
3116
+ throw new ResolveDRSError('Failed to parse response as JSON');
3117
+ }
3118
+ } else {
3119
+ throw new ResolveDRSError(RESOLVE_DRS_ERROR);
3120
+ }
3121
+ // oxlint-disable-next-line no-unused-vars
3122
+ } catch (error) {
3123
+ throw new ResolveDRSError(RESOLVE_DRS_ERROR);
3124
+ }
3125
+ }));
3126
+ return responses.reduce((acc, response, index)=>{
3127
+ if (response.status === 'fulfilled' && response.value.data) {
3128
+ return {
3129
+ ...acc,
3130
+ [guidsForHostnameResolution[index]]: response.value.data
3131
+ };
3132
+ } else {
3133
+ throw new ResolveDRSError(RESOLVE_DRS_ERROR);
3134
+ }
3135
+ }, {});
3136
+ // oxlint-disable-next-line no-unused-vars
3137
+ } catch (error) {
3138
+ throw new ResolveDRSError(RESOLVE_DRS_ERROR);
3139
+ }
3140
+ };
3141
+
3142
+ const resolveCachedDRS = async (guidsForHostnameResolution)=>{
3143
+ const state = coreStore.getState();
3144
+ const resolvedIds = {};
3145
+ for (const guid of guidsForHostnameResolution){
3146
+ const hostname = drsHostnamesSelector(guid, state);
3147
+ if (hostname) {
3148
+ resolvedIds[guid] = hostname;
3149
+ }
3150
+ }
3151
+ return resolvedIds;
3152
+ };
3153
+
3154
+ const DRS_Resolvers = {
3155
+ dataGUISOrg: resolveDRSWithDataGUISOrg,
3156
+ cached: resolveCachedDRS
3157
+ };
3158
+ // TODO - add to config file for DRS resolver
3159
+ const resolveDRSObjectId = (guidsForHostnameResolution, resolver = "cached")=>{
3160
+ return DRS_Resolvers[resolver](guidsForHostnameResolution);
3161
+ };
3162
+
3163
+ const isNotDefined = (x)=>{
3164
+ return x === undefined || x === null || x === void 0;
3165
+ };
3166
+ const isObject = (x)=>{
3167
+ return typeof x === 'object';
3168
+ };
3169
+ const isArray = (x)=>{
3170
+ return Array.isArray(x);
3171
+ };
3172
+ const isString = (x)=>{
3173
+ return typeof x === 'string';
3174
+ };
3175
+
3176
+ /**
3177
+ * Prepares a URL for downloading by appending '/download' to the provided apiUrl.
3178
+ *
3179
+ * @param {string} apiUrl - The base URL to be used for preparing the download URL.
3180
+ * @returns {URL} - The prepared download URL as a URL object.
3181
+ */ const prepareUrl$1 = (apiUrl)=>new URL(apiUrl + '/download');
3182
+
3183
+ var HttpMethod = /*#__PURE__*/ function(HttpMethod) {
3184
+ HttpMethod["GET"] = "GET";
3185
+ HttpMethod["POST"] = "POST";
3186
+ HttpMethod["PUT"] = "PUT";
3187
+ HttpMethod["DELETE"] = "DELETE";
3188
+ return HttpMethod;
3189
+ }({});
3190
+ const DEFAULT_METHOD = "GET";
3191
+ const CONTENT_TYPE_HEADER = 'Content-Type';
3192
+ const CONTENT_TYPE_JSON = 'application/json';
3193
+ const HTTPErrorMessages = {
3194
+ // 4xx Client Errors
3195
+ 400: 'Bad Request',
3196
+ 401: 'Unauthorized',
3197
+ 402: 'Payment Required',
3198
+ 403: 'Forbidden',
3199
+ 404: 'Not Found',
3200
+ 405: 'Method Not Allowed',
3201
+ 406: 'Not Acceptable',
3202
+ 407: 'Proxy Authentication Required',
3203
+ 408: 'Request Timeout',
3204
+ 409: 'Conflict',
3205
+ 410: 'Gone',
3206
+ 411: 'Length Required',
3207
+ 412: 'Precondition Failed',
3208
+ 413: 'Payload Too Large',
3209
+ 414: 'URI Too Long',
3210
+ 415: 'Unsupported Media Type',
3211
+ 416: 'Range Not Satisfiable',
3212
+ 417: 'Expectation Failed',
3213
+ 418: "I'm a teapot",
3214
+ 421: 'Misdirected Request',
3215
+ 422: 'Unprocessable Entity',
3216
+ 423: 'Locked',
3217
+ 424: 'Failed Dependency',
3218
+ 425: 'Too Early',
3219
+ 426: 'Upgrade Required',
3220
+ 428: 'Precondition Required',
3221
+ 429: 'Too Many Requests',
3222
+ 431: 'Request Header Fields Too Large',
3223
+ 451: 'Unavailable For Legal Reasons',
3224
+ // 5xx Server Errors
3225
+ 500: 'Internal Server Error',
3226
+ 501: 'Not Implemented',
3227
+ 502: 'Bad Gateway',
3228
+ 503: 'Service Unavailable',
3229
+ 504: 'Gateway Timeout',
3230
+ 505: 'HTTP Version Not Supported',
3231
+ 506: 'Variant Also Negotiates',
3232
+ 507: 'Insufficient Storage',
3233
+ 508: 'Loop Detected',
3234
+ 510: 'Not Extended',
3235
+ 511: 'Network Authentication Required'
3236
+ };
3237
+ /**
3238
+ * Represents an error that occurs during an HTTP request.
3239
+ * Extends the built-in `Error` class to provide additional information
3240
+ * about the HTTP status code and optional response data.
3241
+ */ class HTTPError extends Error {
3242
+ constructor(status, message, responseData){
3243
+ super(message), this.status = status, this.responseData = responseData;
3244
+ this.name = 'HTTPError';
3245
+ }
3246
+ }
3247
+ const fetchFencePresignedURL = async ({ guid, method = 'GET', onAbort = ()=>null, signal = undefined })=>{
3248
+ const csrfToken = selectCSRFToken(coreStore.getState());
3249
+ const headers = new Headers();
3250
+ headers.set('Content-Type', 'application/json');
3251
+ if (process.env.NODE_ENV === 'development') {
3252
+ // NOTE: This cookie can only be accessed from the client side
3253
+ // in development mode. Otherwise, the cookie is set as httpOnly
3254
+ const accessToken = getCookie('credentials_token');
3255
+ if (accessToken) {
3256
+ headers.set('Authorization', `Bearer ${accessToken}`);
3257
+ }
3258
+ }
3259
+ if (csrfToken) headers.set('X-CSRF-Token', csrfToken);
3260
+ const url = `${GEN3_FENCE_API}/data/download/${guid}`;
3261
+ try {
3262
+ const response = await fetch(url, {
3263
+ method: method,
3264
+ headers: headers,
3265
+ ...signal ? {
3266
+ signal: signal
3267
+ } : {}
3268
+ });
3269
+ // Check if the response is ok before proceeding
3270
+ if (!response.ok) {
3271
+ let errorMessage;
3272
+ let errorData;
3273
+ try {
3274
+ // Attempt to parse error response as JSON
3275
+ errorData = await response.json();
3276
+ errorMessage = errorData.message || response.statusText;
3277
+ } catch {
3278
+ // If JSON parsing fails, use status text
3279
+ errorMessage = response.statusText;
3280
+ }
3281
+ throw new HTTPError(response.status, errorMessage, errorData);
3282
+ }
3283
+ return (await response.json())['url'];
3284
+ } catch (error) {
3285
+ if (error instanceof Error) {
3286
+ if (error.name === 'AbortError') {
3287
+ onAbort?.();
3288
+ }
3289
+ }
3290
+ throw error;
3291
+ }
3292
+ };
3293
+ /**
3294
+ * Retrieves a CSRF token from the server.
3295
+ *
3296
+ * This asynchronous function sends a GET request to the server's status endpoint
3297
+ * to fetch the CSRF token in the response. The token is expected to be included
3298
+ * in the JSON response under the `csrf_token` field.
3299
+ *
3300
+ * @returns {Promise<string | null>} A promise that resolves to the CSRF token as a string if successfully retrieved,
3301
+ * or null if the token is not present in the response.
3302
+ * @throws {HTTPError} Throws an HTTPError if the server response is not successful.
3303
+ */ const getCSRFToken = async ()=>{
3304
+ const requestHeaders = new Headers({
3305
+ [CONTENT_TYPE_HEADER]: CONTENT_TYPE_JSON
3306
+ });
3307
+ const response = await fetch(`${GEN3_API}/_status`, {
3308
+ headers: requestHeaders
3309
+ });
3310
+ if (!response.ok) {
3311
+ throw new HTTPError(response.status, response.statusText);
3312
+ }
3313
+ const { csrf: csrfToken } = await response.json();
3314
+ return csrfToken || null;
3315
+ };
3316
+ /**
3317
+ * Fetches JSON data from a specified URL using the Fetch API.
3318
+ *
3319
+ * @param {string} url - The URL to fetch the JSON data from.
3320
+ * @param {boolean} [requiresCSRF=false] - Indicates whether a CSRF token is required for the request.
3321
+ * If true, the CSRF token will be added to the request headers.
3322
+ * @param {string} [method=DEFAULT_METHOD] - The HTTP method to use for the request (e.g., 'GET', 'POST').
3323
+ * @param {unknown} [body=undefined] - The request body to send, applicable when using methods like 'POST'.
3324
+ *
3325
+ * @param signal - Abort fetch
3326
+ * @returns {Promise<T>} A promise that resolves to the parsed JSON data from the response.
3327
+ *
3328
+ * @throws {HTTPError} Throws an error if the HTTP response status indicates a failure.
3329
+ */ const fetchJSONDataFromURL = async (url, requiresCSRF = false, method = DEFAULT_METHOD, body = undefined, signal)=>{
3330
+ const requestHeaders = new Headers({
3331
+ [CONTENT_TYPE_HEADER]: CONTENT_TYPE_JSON
3332
+ });
3333
+ if (requiresCSRF) {
3334
+ const csrfToken = await getCSRFToken();
3335
+ if (csrfToken) {
3336
+ requestHeaders.set('X-CSRF-Token', csrfToken);
3337
+ }
3338
+ }
3339
+ if (process.env.NODE_ENV === 'development') {
3340
+ const accessToken = getCookie('credentials_token');
3341
+ if (accessToken) {
3342
+ requestHeaders.set('Authorization', `Bearer ${accessToken}`);
3343
+ }
3344
+ }
3345
+ const response = await fetch(url, {
3346
+ method,
3347
+ headers: requestHeaders,
3348
+ body: [
3349
+ 'POST',
3350
+ 'PUT',
3351
+ 'PATCH'
3352
+ ].includes(method.toUpperCase()) ? body : undefined,
3353
+ ...signal ? {
3354
+ signal: signal
3355
+ } : {}
3356
+ });
3357
+ if (!response.ok) {
3358
+ const errorBody = await response.json();
3359
+ throw new HTTPError(response.status, response.statusText, errorBody);
3360
+ }
3361
+ if (response.status === 204) {
3362
+ // no content so returns null
3363
+ return null;
3364
+ }
3365
+ return await response.json();
3366
+ };
3367
+
3368
+ const extractValuesFromObject = (jsonPathMappings, obj)=>{
3369
+ const result = {};
3370
+ const extractObjectValue = (jsonPath, obj)=>{
3371
+ const extractedValues = JSONPath({
3372
+ path: jsonPath,
3373
+ json: obj
3374
+ });
3375
+ return extractedValues.length > 0 ? extractedValues[0] : undefined;
3376
+ };
3377
+ for(const key in jsonPathMappings){
3378
+ if (key in Object.keys(jsonPathMappings)) {
3379
+ // Extract value from an object and store it in the result.
3380
+ result[key] = extractObjectValue(jsonPathMappings[key], obj);
3381
+ }
3382
+ }
3383
+ return result;
3384
+ };
3385
+ const ExtractValueFromObject = (obj, key, valueIfNotFound)=>{
3386
+ return obj?.[key] ?? valueIfNotFound;
3387
+ };
3388
+
3389
+ // type guard functions
3390
+ const isHistogramRangeData = (key)=>{
3391
+ return Array.isArray(key) && key.length === 2 && key.every((item)=>typeof item === 'number');
3392
+ };
3393
+ const isJSONObject = (data)=>{
3394
+ return typeof data === 'object' && data !== null && !Array.isArray(data);
3395
+ };
3396
+ const isJSONValue = (data)=>{
3397
+ return typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean' || Array.isArray(data) && data.every(isJSONValue) || isJSONObject(data);
3398
+ };
3399
+ const isJSONValueArray = (data)=>{
3400
+ return Array.isArray(data) && data.every(isJSONValue);
3401
+ };
3402
+ const isValidObject = (input)=>typeof input === 'object' && input !== null;
3403
+ const isHistogramData = (data)=>{
3404
+ return isValidObject(data) && 'key' in data && 'count' in data;
3405
+ };
3406
+ const isHistogramDataArray = (input)=>{
3407
+ if (!isValidObject(input) || !Array.isArray(input.histogram)) {
3408
+ return false;
3409
+ }
3410
+ return input.histogram.every(isHistogramData);
3411
+ };
3412
+ const isHistogramDataCollection = (obj)=>{
3413
+ return isValidObject(obj) && 'histogram' in obj && isHistogramData(obj.histogram);
3414
+ };
3415
+ // Type guard function for GuppyAggregationData interface
3416
+ const isGuppyAggregationData = (obj)=>{
3417
+ if (!isValidObject(obj)) return false;
3418
+ for(const key in obj){
3419
+ if (!isHistogramDataCollection(obj[key])) {
3420
+ return false;
3421
+ }
3422
+ }
3423
+ return true;
3424
+ };
3425
+ const isHistogramDataAnEnum = (data)=>{
3426
+ return typeof data === 'object' && data !== null && 'key' in data && 'count' in data && typeof data.key === 'string' && typeof data.count === 'number';
3427
+ };
3428
+ const isStatsValue = (item)=>{
3429
+ if (typeof item !== 'object' || item === null) {
3430
+ return false;
3431
+ }
3432
+ const obj = item;
3433
+ // Check that all present properties have correct types
3434
+ const numericFields = [
3435
+ 'count',
3436
+ 'min',
3437
+ 'max',
3438
+ 'avg',
3439
+ 'sum',
3440
+ 'stddev',
3441
+ 'median'
3442
+ ];
3443
+ if (!numericFields.some((field)=>field in obj && typeof obj[field] !== 'number')) {
3444
+ return false;
3445
+ }
3446
+ // Check percentiles structure if present
3447
+ if ('percentiles' in obj) {
3448
+ const percentiles = obj.percentiles;
3449
+ if (typeof percentiles !== 'object' || percentiles === null) {
3450
+ return false;
3451
+ }
3452
+ }
3453
+ return true;
3454
+ };
3455
+ const isStatsValuesArray = (data)=>{
3456
+ return Array.isArray(data) && data.every(isStatsValue);
3457
+ };
3458
+ const isHistogramDataAArray = (data)=>{
3459
+ return Array.isArray(data) && data.every(isHistogramData);
3460
+ };
3461
+ const isHistogramDataArrayAnEnum = (data)=>{
3462
+ return Array.isArray(data) && data.every(isHistogramDataAnEnum);
3463
+ };
3464
+ const isHistogramDataArrayARange = (data)=>{
3465
+ return Array.isArray(data) && data.every((item)=>isHistogramRangeData(item.key));
3466
+ };
3467
+ /**
3468
+ * Type predicate to narrow an unknown error to `FetchBaseQueryError`
3469
+ */ function isFetchBaseQueryError(error) {
3470
+ return typeof error === 'object' && error != null && 'status' in error;
3471
+ }
3472
+ /**
3473
+ * Type predicate to narrow an unknown error to an object with a string 'message' property
3474
+ */ function isErrorWithMessage(error) {
3475
+ return typeof error === 'object' && error != null && 'message' in error && typeof error.message === 'string';
3476
+ }
3477
+ function isHttpStatusError(error) {
3478
+ return typeof error === 'object' && error != null && 'status' in error && typeof error.status === 'number';
3479
+ }
3480
+ /**
3481
+ * Type predicate to narrow an unknown error to an object with a string 'message' property
3482
+ */ function isFetchParseError(error) {
3483
+ return typeof error === 'object' && error != null && 'originalStatus' in error && 'status' in error && error['status'] === 'PARSING_ERROR';
3484
+ }
3485
+ /**
3486
+ * Type predicate to narrow an unknown error to a `SerializedError`
3487
+ */ function isSerializedError(error) {
3488
+ return typeof error === 'object' && error != null && !('status' in error) && ('message' in error || 'name' in error || 'code' in error || 'stack' in error);
3489
+ }
3490
+
3491
+ const HTTPUserFriendlyErrorMessages = {
3492
+ // 4xx Client Errors
3493
+ 400: 'Sorry, there was a problem with your request. Please check the information you provided and try again.',
3494
+ 401: 'Please sign in to access this content. If you need to create an account, please contact support.',
3495
+ 402: 'This feature requires payment to continue. Please update your subscription.',
3496
+ 403: "You don't have permission to access this. If you think this is a mistake, please contact support.",
3497
+ 404: "We couldn't find what you're looking for. The link might be broken or the content may have been moved.",
3498
+ 405: 'This action is not supported. Please try a different approach.',
3499
+ 406: "Your browser sent a request that this server couldn't understand. Try updating your browser.",
3500
+ 407: 'Please authenticate with the network proxy first.',
3501
+ 408: 'The request took too long to complete. Please check your internet connection and try again.',
3502
+ 409: 'There was a conflict with your request. Someone might have modified the same content.',
3503
+ 410: 'This content has been permanently removed and is no longer available.',
3504
+ 411: 'There was a technical problem with your request. Please try again.',
3505
+ 412: "Some conditions for this request weren't met. Please try again.",
3506
+ 413: "The file you're trying to upload is too large. Please choose a smaller file.",
3507
+ 414: 'The web address is too long. Please try a shorter URL.',
3508
+ 415: 'This type of file is not supported. Please try a different format.',
3509
+ 416: 'The requested content range is not available.',
3510
+ 417: "Your request couldn't be completed as expected. Please try again.",
3511
+ 418: 'Unusual error occurred. Please try again or contact support if the problem persists.',
3512
+ 421: 'Your request was sent to the wrong server. Please try again.',
3513
+ 422: "We couldn't process your request. Please check the information and try again.",
3514
+ 423: 'This resource is temporarily locked. Please try again in a few moments.',
3515
+ 424: 'The request failed because it depends on another request that failed.',
3516
+ 425: 'Your request was processed too early. Please try again in a moment.',
3517
+ 426: 'You need to upgrade your browser or app to continue.',
3518
+ 428: "Some required conditions weren't met. Please check all requirements and try again.",
3519
+ 429: "You've made too many requests. Please wait a while before trying again.",
3520
+ 431: 'Request failed because the headers are too large. Please simplify your request.',
3521
+ 451: 'This content is not available for legal reasons.',
3522
+ // 5xx Server Errors
3523
+ 500: 'Something went wrong with the service. Please try again later.',
3524
+ 501: "This feature isn't available yet. Check back later.",
3525
+ 502: "We're having trouble connecting to our servers. Please try again in a few minutes.",
3526
+ 503: "Our service is temporarily unavailable. We're working to restore it as quickly as possible.",
3527
+ 504: 'The server took too long to respond. Please try again later.',
3528
+ 505: "Your browser's HTTP version isn't supported. Please try updating your browser.",
3529
+ 506: 'We encountered a configuration error.',
3530
+ 507: "We're out of storage space.",
3531
+ 508: 'We detected an infinite loop in our servers.',
3532
+ 510: 'This server requires additional extensions to process your request.',
3533
+ 511: 'Please sign in to the network before continuing.'
3534
+ };
3535
+
3536
+ const MAX_ERROR_LENGTH = 256;
3537
+ const DEFAULT_API_ERROR_MESSAGE = 'API Query Error';
3538
+ /**
3539
+ * Checks if a string appears to be an HTML page
3540
+ */ function isHtmlContent(str) {
3541
+ const trimmed = str.trim().toLowerCase();
3542
+ return trimmed.startsWith('<!doctype html') || trimmed.startsWith('<html') || /<html[\s>]/i.test(str);
3543
+ }
3544
+ /**
3545
+ * Sanitizes an error message - returns DEFAULT_API_ERROR_MESSAGE if
3546
+ * the message is too long or contains HTML content
3547
+ */ function sanitizeErrorMessage(message) {
3548
+ if (!message) return DEFAULT_API_ERROR_MESSAGE;
3549
+ if (message.length > MAX_ERROR_LENGTH || isHtmlContent(message)) {
3550
+ return DEFAULT_API_ERROR_MESSAGE;
3551
+ }
3552
+ return message;
3553
+ }
3554
+ /** Best-effort message extraction from an HTTP error body */ function extractMessage(data) {
3555
+ if (typeof data === 'string') return data;
3556
+ if (typeof data === 'object' && data != null) {
3557
+ const obj = data;
3558
+ // common API error shapes: { message }, { error }, { detail } (FastAPI)
3559
+ for (const key of [
3560
+ 'message',
3561
+ 'error',
3562
+ 'detail'
3563
+ ]){
3564
+ const val = obj[key];
3565
+ if (typeof val === 'string') return val;
3566
+ }
3567
+ }
3568
+ return undefined;
3569
+ }
3570
+ function normalizeRtkError(error) {
3571
+ if (!error) {
3572
+ return {
3573
+ type: 'UNKNOWN_ERROR',
3574
+ message: 'Unknown error'
3575
+ };
3576
+ }
3577
+ if (isFetchBaseQueryError(error)) {
3578
+ // status is number for HTTP errors, string literal for the others
3579
+ if (typeof error.status === 'number') {
3580
+ const extractedMessage = extractMessage(error.data);
3581
+ return {
3582
+ type: 'HTTP_ERROR',
3583
+ status: error.status,
3584
+ message: sanitizeErrorMessage(extractedMessage) !== DEFAULT_API_ERROR_MESSAGE ? sanitizeErrorMessage(extractedMessage) : HTTPUserFriendlyErrorMessages[error.status] ?? `Request failed with status ${error.status}`,
3585
+ data: error.data
3586
+ };
3587
+ }
3588
+ switch(error.status){
3589
+ case 'FETCH_ERROR':
3590
+ return {
3591
+ type: 'FETCH_ERROR',
3592
+ message: error.error
3593
+ };
3594
+ case 'PARSING_ERROR':
3595
+ return {
3596
+ type: 'PARSING_ERROR',
3597
+ status: error.originalStatus,
3598
+ message: HTTPUserFriendlyErrorMessages[error.originalStatus] ?? `Request failed with status ${error.originalStatus}`,
3599
+ data: error.data
3600
+ };
3601
+ case 'TIMEOUT_ERROR':
3602
+ return {
3603
+ type: 'TIMEOUT_ERROR',
3604
+ message: sanitizeErrorMessage(error.error)
3605
+ };
3606
+ case 'CUSTOM_ERROR':
3607
+ return {
3608
+ type: 'CUSTOM_ERROR',
3609
+ message: sanitizeErrorMessage(error.error),
3610
+ data: error.data
3611
+ };
3612
+ }
3886
3613
  }
3614
+ // SerializedError — a JS error thrown somewhere in the pipeline
3615
+ if ('message' in error || 'name' in error) {
3616
+ return {
3617
+ type: 'SERIALIZED_ERROR',
3618
+ message: sanitizeErrorMessage(error.message) || 'An error occurred'
3619
+ };
3620
+ }
3621
+ return {
3622
+ type: 'UNKNOWN_ERROR',
3623
+ message: 'Unknown error'
3624
+ };
3625
+ }
3626
+
3627
+ const DAYS_IN_YEAR = 365.25;
3628
+ /**
3629
+ * Converts HistogramData to HistogramDataAsStringKey by ensuring the key is a string.
3630
+ * If the key is already a string, it's used as is.
3631
+ * If the key is a number tuple [min, max], it's converted to a string in the format "min-max".
3632
+ *
3633
+ * @param data The HistogramData object to convert
3634
+ * @returns HistogramDataAsStringKey with the converted key
3635
+ */ const convertToHistogramDataAsStringKey = (data)=>{
3636
+ return {
3637
+ key: typeof data.key === 'string' ? data.key : `${data.key[0]}-${data.key[1]}`,
3638
+ count: data.count
3639
+ };
3887
3640
  };
3888
- const useGetIndexFields = (index, indexPrefix = '')=>{
3889
- const { data } = useGetFieldsForIndexQuery({
3890
- index: index,
3891
- indexPrefix: indexPrefix
3892
- });
3893
- return data ?? [];
3894
- };
3895
- const buildGetAggregationQuery = (type, fields, filters, accessibility = Accessibility.ALL, filterSelf = false, queryId, indexPrefix = '', filterName = 'filter')=>{
3896
- const queryStart = isFilterEmpty(filters) ? `query getAggs${queryId ? `_${queryId}` : ''} {
3897
- ${indexPrefix}_aggregation {
3898
- ${type} (accessibility: ${accessibility}) {` : `query getAggs ($${filterName}: JSON) {
3899
- ${indexPrefix}_aggregation {
3900
- ${type} (filter: $${filterName}, filterSelf: ${filterSelf ? 'true' : 'false'}, accessibility: ${accessibility}) { _totalCount`;
3901
- const query = `${queryStart}
3902
- ${fields.map((field)=>histogramQueryStrForEachField(field))}
3903
- }
3904
- }
3905
- }`;
3906
- const queryBody = {
3907
- query: query,
3908
- variables: {
3909
- [filterName]: convertFilterSetToGqlFilter(filters)
3910
- }
3641
+ const calculatePercentageAsNumber = (count, total)=>count ? count / total * 100 : 0;
3642
+ const calculatePercentageAsString = (count, total)=>`${(count / total * 100).toFixed(2)}%`;
3643
+ const capitalize = (original)=>{
3644
+ const customCapitalizations = {
3645
+ id: 'ID',
3646
+ uuid: 'UUID',
3647
+ dna: 'DNA',
3648
+ dbsnp: 'dbSNP',
3649
+ cosmic: 'COSMIC',
3650
+ civic: 'CIViC',
3651
+ dbgap: 'dbGaP',
3652
+ ecog: 'ECOG',
3653
+ bmi: 'BMI',
3654
+ gdc: 'GDC',
3655
+ cnv: 'CNV',
3656
+ ssm: 'SSM',
3657
+ aa: 'AA'
3911
3658
  };
3912
- return queryBody;
3659
+ return original.split(' ').map((word)=>customCapitalizations[word.toLowerCase()] || `${word.charAt(0).toUpperCase()}${word.slice(1)}`).join(' ');
3913
3660
  };
3914
- const buildGetStatsAggregationQuery = (type, fields, filters, accessibility = Accessibility.ALL, filterSelf = false, queryId, indexPrefix = '', filterName = 'filter')=>{
3915
- const queryStart = isFilterEmpty(filters) ? `
3916
- query getStatsAggs${queryId ? `_${queryId}` : ''} {
3917
- ${indexPrefix}_aggregation {
3918
- ${type} (accessibility: ${accessibility}) {` : `query getStatsAggs${queryId ? `_${queryId}` : ''} ($${filterName}: JSON) {
3919
- ${indexPrefix}_aggregation {
3920
- ${type} (filter: $${filterName}, filterSelf: ${filterSelf ? 'true' : 'false'}, accessibility: ${accessibility}) { _totalCount`;
3921
- const query = `${queryStart}
3922
- ${fields.map((field)=>statsQueryStrForEachField(field))}
3923
- }
3924
- }
3925
- }`;
3926
- const queryBody = {
3927
- query: query,
3928
- variables: {
3929
- [filterName]: convertFilterSetToGqlFilter(filters)
3661
+ const humanify = ({ term = '', capitalize: cap = true, facetTerm = false })=>{
3662
+ let original;
3663
+ let humanified;
3664
+ if (facetTerm) {
3665
+ // Splits on capital letters followed by lowercase letters to find
3666
+ // words squished together in a string.
3667
+ original = term?.split(/(?=[A-Z][a-z])/).join(' ');
3668
+ humanified = term?.replace(/\./g, ' ').replace(/_/g, ' ').trim();
3669
+ } else {
3670
+ const split = (original || term)?.split('.');
3671
+ humanified = split[split.length - 1]?.replace(/_/g, ' ').trim();
3672
+ // Special case 'name' to include any parent nested for sake of
3673
+ // specificity in the UI
3674
+ if (humanified === 'name' && split?.length > 1) {
3675
+ humanified = `${split[split?.length - 2]} ${humanified}`;
3930
3676
  }
3931
- };
3932
- return queryBody;
3677
+ }
3678
+ return cap ? capitalize(humanified) : humanified;
3679
+ };
3680
+ /*https://github.com/NCI-GDC/portal-ui/blob/develop/src/packages/%40ncigdc/utils/ageDisplay.js*/ /**
3681
+ * Converts age in days into a human-readable format.
3682
+ *
3683
+ * @param ageInDays - The age in days.
3684
+ * @param yearsOnly - If true, only display years.
3685
+ * @defaultValue false
3686
+ * @param defaultValue - The default value to return if ageInDays is falsy.
3687
+ * @defaultValue "--"
3688
+ * @returns The formatted age string.
3689
+ */ const ageDisplay = (ageInDays, yearsOnly = false, defaultValue = '--')=>{
3690
+ if (ageInDays !== 0 && !ageInDays) {
3691
+ return defaultValue;
3692
+ }
3693
+ const calculateYearsAndDays = (years, days)=>days === 365 ? [
3694
+ years + 1,
3695
+ 0
3696
+ ] : [
3697
+ years,
3698
+ days
3699
+ ];
3700
+ const ABS_AGE_DAYS = Math.abs(ageInDays);
3701
+ const [years, remainingDays] = calculateYearsAndDays(Math.floor(ABS_AGE_DAYS / DAYS_IN_YEAR), Math.ceil(ABS_AGE_DAYS % DAYS_IN_YEAR));
3702
+ const formattedYears = years === 0 ? '' : `${years} ${years === 1 ? 'year' : 'years'}`;
3703
+ const formattedDays = !yearsOnly && remainingDays > 0 ? `${remainingDays} ${remainingDays === 1 ? 'day' : 'days'}` : years === 0 && remainingDays === 0 ? '0 days' : '';
3704
+ const ageString = [
3705
+ formattedYears,
3706
+ formattedDays
3707
+ ].filter(Boolean).join(' ');
3708
+ return ageInDays >= 0 ? ageString : `-${ageString}`;
3933
3709
  };
3934
- const { useGetRawDataAndTotalCountsQuery, useGetAccessibleDataQuery, useGetAllFieldsForTypeQuery, useGetAggsQuery, useLazyGetAggsQuery, useGetStatsAggregationsQuery, useLazyGetStatsAggregationsQuery, useGetSubAggsQuery, useGetCountsQuery, useLazyGetCountsQuery, useGetFieldCountSummaryQuery, useGetFieldsForIndexQuery, useGetSharedFieldsForIndexQuery, useGeneralGQLQuery, useLazyGeneralGQLQuery, useCustomRangeQuery, useLazyCustomRangeQuery, useGetObjectIdsQuery, useLazyGetObjectIdsQuery } = explorerApi;
3935
-
3936
- /**
3937
- * Defines coreListeners for adding middleware.
3938
- * used to update the current Cohort Case count
3939
- */ const coreStoreListenerMiddleware = createListenerMiddleware();
3940
- const startCoreListening = coreStoreListenerMiddleware.startListening;
3941
3710
  /**
3942
- * When a getCounts is requested, it might be for a cohort.
3943
- * If so, then update the cohort's counts for that index
3944
- */ startCoreListening({
3945
- matcher: isAnyOf(explorerApi.endpoints.getCounts.matchFulfilled),
3946
- effect: async (action, listenerApi)=>{
3947
- const counts = action.payload;
3948
- const index = action?.meta?.arg?.originalArgs?.type; // note this is the guppy index name
3949
- const cohortId = action?.meta?.arg?.originalArgs?.queryId;
3950
- if (cohortId && index) {
3951
- listenerApi.dispatch(updateCohortIndexCountById({
3952
- index,
3953
- cohortId,
3954
- counts
3955
- }));
3711
+ * Given an object of JSON, stringify it into a string.
3712
+ * @param obj - the object to stringify
3713
+ * @param defaults - the default value to return if the object is undefined
3714
+ * @category Utility
3715
+ */ const stringifyJSONParam = (obj, defaults = '{}')=>obj ? JSON.stringify(obj) : defaults;
3716
+
3717
+ const queryWTSFederatedLoginStatus = async (signal)=>{
3718
+ try {
3719
+ const results = await fetchJSONDataFromURL(`${GEN3_WTS_API}/external_oidc/`, false, HttpMethod.GET, undefined, signal);
3720
+ // Handle both null response and response without providers
3721
+ if (!results || !results.providers) {
3722
+ return {
3723
+ providers: []
3724
+ };
3956
3725
  }
3726
+ return results;
3727
+ } catch (error) {
3728
+ return {
3729
+ providers: [],
3730
+ error: error instanceof Error ? error : new Error('Unknown error')
3731
+ };
3957
3732
  }
3958
- });
3733
+ };
3959
3734
 
3960
3735
  /**
3961
- * Low-level helper to fetch Arborist resources for the current user.
3962
- * Adds an Authorization header when a token is provided and normalizes the response
3963
- * to a simple string[] of resource paths.
3964
- *
3965
- * token { string | null } - access token to use for authorization
3966
- * useService { boolean } - use the arborist service endpoint instead of the public arborist API
3967
- */ async function fetchArboristResources(token, useService = false) {
3968
- const headers = {};
3969
- if (token) {
3970
- headers.Authorization = `Bearer ${token}`;
3736
+ * Input is a list of files selected for download.
3737
+ * Output is a array of GUID prefixes
3738
+ * @param selectedFiles
3739
+ */ const gatherGUIDPrefixes = (selectedFiles)=>{
3740
+ return selectedFiles.reduce((acc, selectedFile)=>{
3741
+ if (selectedFile.object_id) {
3742
+ const guidDomainPrefix = (selectedFile.object_id.match(GUID_PREFIX_PATTERN) || []).shift();
3743
+ if (guidDomainPrefix) {
3744
+ if (!acc.includes(guidDomainPrefix)) {
3745
+ acc.push(guidDomainPrefix);
3746
+ }
3747
+ }
3748
+ }
3749
+ return acc;
3750
+ }, []);
3751
+ };
3752
+ /**
3753
+ * Resolves DRS object ID from a list of guids using dataguids.org or other DRS resolver
3754
+ * @param guidPrefixes
3755
+ */ const resolveDRSPrefixes = async (guidPrefixes)=>{
3756
+ return await resolveDRSObjectId(guidPrefixes);
3757
+ };
3758
+ /**
3759
+ * Given a list of object ids, extracts and DRS prefixes and resolves them to DRS server hostnames
3760
+ * then returns a list of resolved GUIDs, and a list of unresolved GUIDs
3761
+ * @param selectedFiles
3762
+ * @returns - {
3763
+ * resolvedDRSPrefixes: Record<string, string> - map of GUID prefixes to DRS server hostnames
3764
+ * resolvedFiles: ResolvedGUID[] - list of resolved GUIDs
3765
+ * unresolvedFiles: ResolvedGUID[] - list of unresolved GUIDs
3766
+ * }
3767
+ */ const resolveGUIDsInSelectedFiles = async (selectedFiles)=>{
3768
+ if (!selectedFiles || selectedFiles.length === 0) {
3769
+ return {
3770
+ externalHosts: {},
3771
+ resolvedFiles: [],
3772
+ unresolvedFiles: []
3773
+ };
3971
3774
  }
3972
- const url = useService ? `${GEN3_AUTHZ_SERVICE}/resource` : `${GEN3_AUTHZ_API}/resources`;
3973
- const res = await fetch(url, {
3974
- headers
3775
+ const guidPrefixes = gatherGUIDPrefixes(selectedFiles);
3776
+ const resolvedDRSPrefixes = await resolveDRSPrefixes(guidPrefixes);
3777
+ const resolvedIds = [];
3778
+ const unresolvedIds = [];
3779
+ selectedFiles.forEach((selectedFile)=>{
3780
+ if (selectedFile.object_id) {
3781
+ const guidDomainPrefix = (selectedFile.object_id.match(GUID_PREFIX_PATTERN) || []).shift();
3782
+ if (guidDomainPrefix && resolvedDRSPrefixes[guidDomainPrefix]) {
3783
+ resolvedIds.push({
3784
+ ...selectedFile,
3785
+ drsPrefix: guidDomainPrefix,
3786
+ url: resolvedDRSPrefixes[guidDomainPrefix]
3787
+ });
3788
+ }
3789
+ } else {
3790
+ unresolvedIds.push(selectedFile);
3791
+ }
3975
3792
  });
3976
- if (!res.ok) {
3977
- console.error('@gen3/core:fetchArboristResources /resource failed:', res.status, await res.text());
3978
- return [];
3793
+ return {
3794
+ externalHosts: resolvedDRSPrefixes,
3795
+ resolvedFiles: resolvedIds,
3796
+ unresolvedFiles: unresolvedIds
3797
+ };
3798
+ };
3799
+ const fetchExternalLogins = async (externalProviders, selectedFiles)=>{
3800
+ const providers = externalProviders ?? [];
3801
+ // find all the providers that do not have tokens
3802
+ const unauthenticatedProviders = providers.filter((provider)=>!provider.refresh_token_expiration);
3803
+ try {
3804
+ const guidResolutions = await resolveGUIDsInSelectedFiles(selectedFiles);
3805
+ const providersToAuthenticate = unauthenticatedProviders.filter((unauthenticatedProvider)=>Object.values(guidResolutions.externalHosts).includes(new URL(unauthenticatedProvider.base_url).hostname));
3806
+ const missingProviders = providersToAuthenticate.filter((provider)=>!Object.values(guidResolutions.externalHosts).includes(new URL(provider.base_url).hostname));
3807
+ return {
3808
+ providersToAuthenticate: providersToAuthenticate,
3809
+ missingProviders: missingProviders
3810
+ };
3811
+ } catch (error) {
3812
+ if (error instanceof Error) {
3813
+ return {
3814
+ error
3815
+ };
3816
+ }
3817
+ return {
3818
+ error: new Error('Unknown error')
3819
+ };
3979
3820
  }
3980
- const data = await res.json();
3981
- return data.resources ?? [];
3982
- }
3983
-
3984
- const persistConfig = {
3985
- key: `${GEN3_COMMONS_NAME}-root`,
3986
- version: 1,
3987
- storage,
3988
- whitelist: [
3989
- 'cohorts',
3990
- 'activeWorkspace',
3991
- 'cart',
3992
- 'workspaceKernels',
3993
- 'tieredWorkspace'
3994
- ]
3995
3821
  };
3996
- const persistedReducer = persistReducer(persistConfig, rootReducer);
3997
- const setupCoreStore = (preloadedState)=>configureStore({
3998
- reducer: persistedReducer,
3999
- preloadedState,
4000
- middleware: (getDefaultMiddleware)=>getDefaultMiddleware({
4001
- serializableCheck: {
4002
- ignoredActions: [
4003
- FLUSH,
4004
- REHYDRATE,
4005
- PAUSE,
4006
- PERSIST,
4007
- PURGE,
4008
- REGISTER
4009
- ]
4010
- }
4011
- }).concat(gen3ServicesReducerMiddleware, guppyAPISliceMiddleware, userAuthApiMiddleware).prepend(coreStoreListenerMiddleware.middleware)
4012
- });
4013
- const coreStore = setupCoreStore();
4014
- setupListeners(coreStore.dispatch);
4015
-
4016
- const persistor = persistStore(coreStore);
4017
- const CoreProvider = ({ children })=>{
4018
- return /*#__PURE__*/ React__default.createElement(Provider, {
4019
- store: coreStore
4020
- }, /*#__PURE__*/ React__default.createElement(PersistGate, {
4021
- loading: null,
4022
- persistor: persistor
4023
- }, children));
3822
+ const useGetFederatedLoginStatus = ({ selectedFiles })=>{
3823
+ const { data: wtsResults, isLoading: wstIsLoading, error: wtsError } = useGetExternalLoginsQuery();
3824
+ // State to manage the asynchronous results
3825
+ const [result, setResult] = useState(null);
3826
+ useEffect(()=>{
3827
+ const fetchData = async ()=>{
3828
+ if (wtsError || !wtsResults) {
3829
+ if (wtsError instanceof Error) setResult({
3830
+ error: wtsError
3831
+ });
3832
+ else setResult({
3833
+ error: new Error('Unknown error')
3834
+ });
3835
+ return;
3836
+ }
3837
+ const results = await fetchExternalLogins(wtsResults.providers, selectedFiles);
3838
+ setResult(results);
3839
+ };
3840
+ // Only run if there's data to act on
3841
+ if (!wstIsLoading && wtsResults) {
3842
+ void fetchData();
3843
+ }
3844
+ }, [
3845
+ selectedFiles,
3846
+ wstIsLoading,
3847
+ wtsError,
3848
+ wtsResults
3849
+ ]);
3850
+ return {
3851
+ isLoading: wstIsLoading,
3852
+ data: result,
3853
+ error: result?.error || wtsError
3854
+ };
3855
+ };
3856
+ const getFederatedLoginStatus = async (selectedFiles)=>{
3857
+ const providers = await queryWTSFederatedLoginStatus();
3858
+ return await fetchExternalLogins(providers.providers, selectedFiles);
4024
3859
  };
4025
3860
 
4026
3861
  const usePrevious = (value)=>{
@@ -6018,6 +5853,141 @@ const graphQLWithTags = gen3Api.enhanceEndpoints({
6018
5853
  });
6019
5854
  const { useGraphQLQuery } = graphQLAPI;
6020
5855
 
5856
+ /**
5857
+ * Flattens a deep nested JSON object skipping
5858
+ * the first level to avoid potentially flattening
5859
+ * non-nested data.
5860
+ * @param {JSON} json
5861
+ */ function flattenJson(json) {
5862
+ const flattenedJson = [];
5863
+ Object.keys(json).forEach((key)=>{
5864
+ flattenedJson.push(flatten(json[key], {
5865
+ delimiter: '_'
5866
+ }));
5867
+ });
5868
+ return flattenedJson;
5869
+ }
5870
+ /**
5871
+ * Converts JSON based on a config.
5872
+ * @param {JSON} json
5873
+ * @param {Object} config
5874
+ */ async function conversion(json, config) {
5875
+ return Papa.unparse(json, config);
5876
+ }
5877
+ /**
5878
+ * Converts JSON to a specified file format.
5879
+ * Defaults to JSON if file format is not supported.
5880
+ * @param {JSON} json
5881
+ * @param {string} format
5882
+ */ async function jsonToFormat(json, format) {
5883
+ if (Object.keys(FILE_DELIMITERS).includes(format)) {
5884
+ const flatJson = flattenJson(json);
5885
+ const data = await conversion(flatJson, {
5886
+ delimiter: FILE_DELIMITERS[format]
5887
+ });
5888
+ return data;
5889
+ }
5890
+ return json;
5891
+ }
5892
+
5893
+ const prepareUrl = (apiUrl)=>`${apiUrl}/download`;
5894
+ const prepareFetchConfig = (parameters, csrfToken)=>{
5895
+ const headers = new Headers({
5896
+ Accept: 'application/json',
5897
+ 'Content-Type': 'application/json',
5898
+ ...csrfToken !== undefined && {
5899
+ 'X-CSRF-Token': csrfToken
5900
+ }
5901
+ });
5902
+ if (process.env.NODE_ENV === 'development') {
5903
+ const accessToken = getCookie('credentials_token');
5904
+ if (accessToken) headers.set('Authorization', `Bearer ${accessToken}`);
5905
+ }
5906
+ return {
5907
+ method: 'POST',
5908
+ headers: headers,
5909
+ body: JSON.stringify({
5910
+ type: parameters.type,
5911
+ filter: convertFilterSetToGqlFilter(parameters.filter),
5912
+ accessibility: parameters.accessibility,
5913
+ fields: parameters?.fields,
5914
+ sort: parameters?.sort
5915
+ })
5916
+ };
5917
+ };
5918
+ const downloadFromGuppyToBlob = async ({ parameters, onStart = ()=>null, onDone = (_)=>null, onError = (_)=>null, onAbort = ()=>null, signal = undefined })=>{
5919
+ const csrfToken = selectCSRFToken(coreStore.getState());
5920
+ onStart?.();
5921
+ const url = prepareUrl(GEN3_GUPPY_API);
5922
+ const fetchConfig = prepareFetchConfig(parameters, csrfToken);
5923
+ fetch(url.toString(), {
5924
+ ...fetchConfig,
5925
+ ...signal ? {
5926
+ signal: signal
5927
+ } : {}
5928
+ }).then(async (response)=>{
5929
+ if (!response.ok) {
5930
+ throw new Error(response.statusText);
5931
+ }
5932
+ let jsonData = await response.json();
5933
+ if (parameters?.rootPath) {
5934
+ jsonData = JSONPath({
5935
+ json: jsonData,
5936
+ path: `$.[${parameters.rootPath}]`,
5937
+ resultType: 'value'
5938
+ });
5939
+ }
5940
+ let str = '';
5941
+ if (parameters.format === 'json') {
5942
+ str = JSON.stringify(jsonData);
5943
+ } else {
5944
+ const convertedData = await jsonToFormat(jsonData, parameters.format);
5945
+ if (isJSONObject(convertedData)) {
5946
+ str = JSON.stringify(convertedData, null, 2);
5947
+ } else {
5948
+ str = convertedData;
5949
+ }
5950
+ }
5951
+ return new Blob([
5952
+ str
5953
+ ], {
5954
+ type: 'application/json'
5955
+ });
5956
+ }).then((blob)=>onDone?.(blob)).catch((error)=>{
5957
+ if (error.name == 'AbortError') {
5958
+ onAbort?.();
5959
+ }
5960
+ onError?.(error);
5961
+ });
5962
+ };
5963
+ const downloadJSONDataFromGuppy = async ({ parameters, onAbort = ()=>null, signal = undefined })=>{
5964
+ const csrfToken = selectCSRFToken(coreStore.getState());
5965
+ const url = prepareUrl(GEN3_GUPPY_API);
5966
+ const fetchConfig = prepareFetchConfig(parameters, csrfToken);
5967
+ try {
5968
+ const response = await fetch(url.toString(), {
5969
+ ...fetchConfig,
5970
+ ...signal ? {
5971
+ signal: signal
5972
+ } : {}
5973
+ });
5974
+ let jsonData = await response.json();
5975
+ if (parameters?.rootPath) {
5976
+ jsonData = JSONPath({
5977
+ json: jsonData,
5978
+ path: `$.[${parameters.rootPath}]`,
5979
+ resultType: 'value'
5980
+ });
5981
+ }
5982
+ return jsonData;
5983
+ } catch (error) {
5984
+ if (error.name === 'AbortError') {
5985
+ onAbort?.();
5986
+ }
5987
+ throw new Error(error);
5988
+ }
5989
+ };
5990
+
6021
5991
  /**
6022
5992
  * Creates a Guppy API for fetching bulk (> 10K rows) elasticsearch data
6023
5993
  * @see https://github.com/uc-cdis/guppy/blob/master/doc/download.md
@@ -7049,5 +7019,5 @@ const showNotification = (id, title, message, type, options)=>{
7049
7019
  notificationService.showNotification(id, title, message, type, options);
7050
7020
  };
7051
7021
 
7052
- export { Accessibility, CART_LIMIT, CohortStorage, CoreProvider, DAYS_IN_YEAR, DataLibraryStoreMode, EmptyFilterSet, EmptyWorkspaceStatusResponse, EnumValueExtractorHandler, ExtractValueFromObject, FILE_DELIMITERS, FILE_FORMATS, GEN3_ANALYSIS_API, GEN3_API, GEN3_AUTHZ_API, GEN3_AUTHZ_SERVICE, GEN3_COMMONS_NAME, GEN3_CROSSWALK_API, GEN3_DOMAIN, GEN3_DOWNLOADS_ENDPOINT, GEN3_FENCE_API, GEN3_FENCE_SERVICE, GEN3_GUPPY_API, GEN3_INDEXD_API, GEN3_MANIFEST_API, GEN3_MDS_API, GEN3_REDIRECT_URL, GEN3_SOWER_API, GEN3_SUBMISSION_API, GEN3_WORKSPACE_API, HTTPError, HTTPErrorMessages, HTTPUserFriendlyErrorMessages, HttpMethod, MissingServiceConfigurationError, Modals, PodConditionType, PodStatus, RequestedWorkspaceStatus, ToGqlAllNested, ToGqlHandler, ValueExtractorHandler, WorkspaceStatus, addItemsToCart, addJEGActiveKernel, ageDisplay, appendFilterToOperation, buildCohortGqlOperator, buildGetAggregationQuery, buildGetStatsAggregationQuery, buildListItemsGroupedByDataset, buildNestedFilterForOperation, buildNestedGQLFilter, buildNestedWithParentPathGQLFilter, buildRangeQuery, calculatePercentageAsNumber, calculatePercentageAsString, capitalize$1 as capitalize, cartReducer, cartReducerPath, cedarApi, clearActiveWorkspaceId, clearCohortFilters, clearJEGActiveKernels, clearJEGActiveWorkspaceId, cohortReducer, configRegistry, conversion, convertFilterSetToGqlFilter, convertFilterSetToNestedGqlFilter, convertFilterSetToOperation, convertFilterToGqlFilter, convertFilterToNestedGqlFilter, convertGqlFilterToFilter, convertToHistogramDataAsStringKey, convertToQueryString, coreStore, createAppApiForRTKQ, createAppStore, createCEDARInstance, createGen3App, createGen3AppWithOwnStore, createNewCohort, createUseCoreDataHook, customQueryStrForField, defaultCohortNameGenerator, downloadFromGuppyToBlob, downloadJSONDataFromGuppy, drsHostnamesReducer, duplicateCohort, explorerApi, explorerTags, extractContents, extractEnumFilterValue, extractFieldNameFromFullFieldName, extractFileDatasetsInRecords, extractFilterValue, extractFiltersWithPrefixFromFilterSet, extractIndexAndFieldNameFromFullFieldName, extractIndexFromDataLibraryCohort, extractIndexFromFullFieldName, fetchArboristResources, fetchFence, fetchFencePresignedURL, fetchJSONDataFromURL, fetchJson, fetchUserState, fieldNameToLabel, filterSetToOperation, formatUptimeInMinutes, gen3Api, generateUniqueName, getCurrentTimestamp, getFederatedLoginStatus, getGen3AppId, getNumberOfItemsInDatalist, getRemoteSupportServiceRegistry, getTimestamp, graphQLAPI, graphQLWithTags, groupSharedFields, guppyAPISliceMiddleware, guppyApi, guppyApiReducer, guppyApiSliceReducerPath, guppyDownloadApi, handleGqlOperation, handleOperation, hideModal, histogramQueryStrForEachField, humanify, ifOperationWithField, isAdditionalDataItem, isArray, isAuthenticated, isCohortItem, isDataLibraryAPIResponse, isDatalistAPI, isErrorWithMessage, isFetchBaseQueryError, isFetchError, isFetchParseError, isFileItem, isFilterEmpty, isFilterSet, isGQLIntersection, isGQLUnion, isGuppyAggregationData, isHistogramData, isHistogramDataAArray, isHistogramDataAnEnum, isHistogramDataArray, isHistogramDataArrayARange, isHistogramDataArrayAnEnum, isHistogramDataCollection, isHistogramRangeData, isHttpStatusError, isIncludes, isIndexedFilterSetEmpty, isIntersection, isIntersectionOrUnion, isJSONObject, isJSONValue, isJSONValueArray, isNameUnique, isNestedFilter, isNotDefined, isObject, isOperandsType, isOperationWithField, isOperatorWithFieldAndArrayOfOperands, isPending, isProgramUrl, isRootUrl, isSerializedError, isStatsValue, isStatsValuesArray, isString, isTimeGreaterThan, isUnion, isWorkspaceActive, isWorkspaceRunningOrStopping, joinFilters, jsonToFormat, listifyMethodsFromMapping, logoutFence, manifestApi, manifestTags, mdsApi, nestedHistogramQueryStrForEachField, normalizeRtkError, notificationService, prepareUrl$1 as prepareUrl, prependIndexToFieldName, printRegistry, processHistogramResponse, projectCodeFromResourcePath, queryMultipleMDSRecords, rawDataQueryStrForEachField$1 as rawDataQueryStrForEachField, registerDefaultRemoteSupport, removeCohort, removeCohortFilter, removeItemsFromCart, removeJEGActiveKernel, removeManyJEGActiveKernels, requestorApi, resetUserState, resourcePathFromProjectID, roundHistogramResponse, selectActiveWorkspaceId, selectActiveWorkspaceStatus, selectAllCohortFiltersCollapsed, selectAllCohorts, selectAllJEGKernels, selectAuthzMappingData, selectAvailableCohortByName, selectAvailableCohorts, selectCSRFToken, selectCSRFTokenData, selectCart, selectCartCount, selectCartItem, selectCartItems, selectCohortById, selectCohortFilterCombineMode, selectCohortFilterExpanded, selectCohortFilters, selectCohortIds, selectCurrentCohort, selectCurrentCohortFilters, selectCurrentCohortId, selectCurrentCohortModified, selectCurrentCohortName, selectCurrentCohortSaved, selectCurrentMessage, selectCurrentModal, selectGen3AppByName, selectGen3AppMetadataByName, selectHeadersWithCSRFToken, selectIndexFilters, selectIndexedFilterByName, selectJEGActiveWorkspaceId, selectJEGActiveWorkspaceStatus, selectJEGKernelById, selectJEGKernelIds, selectJEGRequestedWorkspaceStatus, selectJEGRequestedWorkspaceStatusTimestamp, selectPaymodelStatus, selectRequestedWorkspaceStatus, selectRequestedWorkspaceStatusTimestamp, selectSharedFilters, selectSharedFiltersForFields, selectShouldShareFilters, selectSowerJobDatetimeCache, selectTotalCohorts, selectUser, selectUserAuthStatus, selectUserData, selectUserDetails, selectUserLoginStatus, selectWorkspaceFullscreen, selectWorkspaceStatus, selectWorkspaceStatusFromService, selectWorkspaceTier, setActiveWorkspace, setActiveWorkspaceId, setActiveWorkspaceStatus, setCohortFilter, setCohortFilterCombineMode, setCohortIndexFilters, setCohortList, setCurrentCohortId, setDRSHostnames, setJEGActiveWorkspace, setJEGActiveWorkspaceId, setJEGActiveWorkspaceStatus, setJEGRequestedWorkspaceStatus, setRequestedWorkspaceStatus, setSharedFilters, setShouldShareFilters, setWorkspaceFullscreen, setWorkspaceTier, setupCoreStore, showModal, showNotification, statsQueryStrForEachField, stringifyJSONParam, submissionApi, toggleCohortBuilderAllFilters, toggleCohortBuilderCategoryFilter, trimFirstfieldNameToLabel, updateCohortFilter, updateCohortName, updateJEGActionKernelStatus, updateStudyInMDS, upsertManyJEGActiveKernels, useAddCohortManifestMutation, useAddFileManifestMutation, useAddMetadataManifestMutation, useAddNewCredentialMutation, useAskQuestionMutation, useAuthorizeFromCredentialsMutation, useCohortFacetsQuery, useCoreDispatch, useCoreSelector, useCreateAuthzResourceMutation, useCreateRequestMutation, useCustomRangeQuery, useDataLibrary, useDownloadFromGuppyQuery, useFetchUserDetailsQuery, useGeneralGQLQuery, useGetAISearchStatusQuery, useGetAISearchVersionQuery, useGetAccessibleDataQuery, useGetActivePayModelQuery, useGetAggMDSQuery, useGetAggsQuery, useGetAllFieldsForTypeQuery, useGetArrayTypes, useGetAuthzMappingsQuery, useGetAuthzResourcesQuery, useGetCSRFQuery, useGetCohortManifestQuery, useGetCountsQuery, useGetCredentialsQuery, useGetCrosswalkDataQuery, useGetDataQuery, useGetDictionaryFromUrlQuery, useGetDictionaryQuery, useGetDownloadQuery, useGetExternalLoginsQuery, useGetFederatedLoginStatus, useGetFieldCountSummaryQuery, useGetFieldsForIndexQuery, useGetFileFromManifestQuery, useGetFileManifestQuery, useGetIndexAggMDSQuery, useGetIndexFields, useGetIndexObjectQuery, useGetIndexdMetdataQuery, useGetJWKKeysQuery, useGetLoginProvidersQuery, useGetMDSQuery, useGetManifestServiceStatusQuery, useGetMetadataByIdQuery, useGetMetadataByUrlQuery, useGetMetadataFromManifestQuery, useGetMetadataManifestQuery, useGetObjectIdsQuery, useGetPresignedUrlQuery, useGetProjectsDetailsQuery, useGetProjectsQuery, useGetRawDataAndTotalCountsQuery, useGetSharedFieldsForIndexQuery, useGetSowerJobListQuery, useGetSowerJobStatusQuery, useGetSowerOutputQuery, useGetSowerServiceStatusQuery, useGetStatsAggregationsQuery, useGetStatus, useGetSubAggsQuery, useGetSubmissionGraphQLQuery, useGetSubmissionsQuery, useGetTagsQuery, useGetUserDetailsRequestStatus, useGetWorkspaceOptionsQuery, useGetWorkspacePayModelsQuery, useGetWorkspaceStatusQuery, useGraphQLQuery, useIsExternalConnectedQuery, useIsUserLoggedIn, useLaunchWorkspaceMutation, useLazyCustomRangeQuery, useLazyDownloadFromGuppyQuery, useLazyFetchUserDetailsQuery, useLazyGeneralGQLQuery, useLazyGetAggsQuery, useLazyGetAuthzMappingsQuery, useLazyGetAuthzResourcesQuery, useLazyGetCSRFQuery, useLazyGetCountsQuery, useLazyGetCrosswalkDataQuery, useLazyGetDownloadQuery, useLazyGetExternalLoginsQuery, useLazyGetIndexObjectQuery, useLazyGetIndexdMetdataQuery, useLazyGetJWKKeysQuery, useLazyGetManifestServiceStatusQuery, useLazyGetMultipleSowerJobStatusQuery, useLazyGetObjectIdsQuery, useLazyGetPresignedUrlQuery, useLazyGetProjectsQuery, useLazyGetSowerJobListQuery, useLazyGetSowerJobStatusQuery, useLazyGetSowerOutputQuery, useLazyGetStatsAggregationsQuery, useLazyGetSubmissionGraphQLQuery, useLazyIsExternalConnectedQuery, useLazyRequestQuery, usePValueQuery, usePrevious, useRemoveCredentialMutation, useRequestByIdQuery, useRequestQuery, useRequestorStatusQuery, useSetCurrentPayModelMutation, useSubmitSowerJobMutation, useTerminateWorkspaceMutation, useUpdateStudyInMDSMutation, useUserAuth, useUserRequestQuery, useVennDiagramQuery, userHasCreateOrUpdateOnAnyProject, userHasDataUpload, userHasMethodForServiceOnProject, userHasMethodForServiceOnResource, userHasMethodOnAnyProject, userHasSheepdogProgramAdmin, userHasSheepdogProjectAdmin };
7022
+ export { Accessibility, CART_LIMIT, CohortStorage, CoreProvider, DAYS_IN_YEAR, DataLibraryStoreMode, EmptyFilterSet, EmptyWorkspaceStatusResponse, EnumValueExtractorHandler, ExtractValueFromObject, FILE_DELIMITERS, FILE_FORMATS, GEN3_ANALYSIS_API, GEN3_API, GEN3_AUTHZ_API, GEN3_AUTHZ_SERVICE, GEN3_COMMONS_NAME, GEN3_CROSSWALK_API, GEN3_DOMAIN, GEN3_DOWNLOADS_ENDPOINT, GEN3_FENCE_API, GEN3_FENCE_SERVICE, GEN3_GUPPY_API, GEN3_INDEXD_API, GEN3_MANIFEST_API, GEN3_MDS_API, GEN3_REDIRECT_URL, GEN3_SOWER_API, GEN3_SUBMISSION_API, GEN3_WORKSPACE_API, HTTPError, HTTPErrorMessages, HTTPUserFriendlyErrorMessages, HttpMethod, MissingServiceConfigurationError, Modals, PodConditionType, PodStatus, RequestedWorkspaceStatus, ToGqlAllNested, ToGqlHandler, ValueExtractorHandler, WorkspaceStatus, addItemsToCart, addJEGActiveKernel, ageDisplay, appendFilterToOperation, buildCohortGqlOperator, buildGetAggregationQuery, buildGetStatsAggregationQuery, buildListItemsGroupedByDataset, buildNestedFilterForOperation, buildNestedGQLFilter, buildNestedWithParentPathGQLFilter, buildRangeQuery, calculatePercentageAsNumber, calculatePercentageAsString, capitalize, cartReducer, cartReducerPath, cedarApi, clearActiveWorkspaceId, clearCohortFilters, clearJEGActiveKernels, clearJEGActiveWorkspaceId, cohortReducer, configRegistry, conversion, convertFilterSetToGqlFilter, convertFilterSetToNestedGqlFilter, convertFilterSetToOperation, convertFilterToGqlFilter, convertFilterToNestedGqlFilter, convertGqlFilterToFilter, convertToHistogramDataAsStringKey, convertToQueryString, coreStore, createAppApiForRTKQ, createAppStore, createCEDARInstance, createGen3App, createGen3AppWithOwnStore, createNewCohort, createUseCoreDataHook, customQueryStrForField, defaultCohortNameGenerator, downloadFromGuppyToBlob, downloadJSONDataFromGuppy, drsHostnamesReducer, duplicateCohort, explorerApi, explorerTags, extractContents, extractEnumFilterValue, extractFieldNameFromFullFieldName, extractFileDatasetsInRecords, extractFilterValue, extractFiltersWithPrefixFromFilterSet, extractIndexAndFieldNameFromFullFieldName, extractIndexFromDataLibraryCohort, extractIndexFromFullFieldName, fetchArboristResources, fetchFence, fetchFencePresignedURL, fetchJSONDataFromURL, fetchJson, fetchUserState, fieldNameToLabel, filterSetToOperation, formatUptimeInMinutes, gen3Api, generateUniqueName, getCurrentTimestamp, getFederatedLoginStatus, getGen3AppId, getNumberOfItemsInDatalist, getRemoteSupportServiceRegistry, getTimestamp, graphQLAPI, graphQLWithTags, groupSharedFields, guppyAPISliceMiddleware, guppyApi, guppyApiReducer, guppyApiSliceReducerPath, guppyDownloadApi, handleGqlOperation, handleOperation, hideModal, histogramQueryStrForEachField, humanify, ifOperationWithField, isAdditionalDataItem, isArray, isAuthenticated, isCohortItem, isDataLibraryAPIResponse, isDatalistAPI, isErrorWithMessage, isFetchBaseQueryError, isFetchError, isFetchParseError, isFileItem, isFilterEmpty, isFilterSet, isGQLIntersection, isGQLUnion, isGuppyAggregationData, isHistogramData, isHistogramDataAArray, isHistogramDataAnEnum, isHistogramDataArray, isHistogramDataArrayARange, isHistogramDataArrayAnEnum, isHistogramDataCollection, isHistogramRangeData, isHttpStatusError, isIncludes, isIndexedFilterSetEmpty, isIntersection, isIntersectionOrUnion, isJSONObject, isJSONValue, isJSONValueArray, isNameUnique, isNestedFilter, isNotDefined, isObject, isOperandsType, isOperationWithField, isOperatorWithFieldAndArrayOfOperands, isPending, isProgramUrl, isRootUrl, isSerializedError, isStatsValue, isStatsValuesArray, isString, isTimeGreaterThan, isUnion, isWorkspaceActive, isWorkspaceRunningOrStopping, joinFilters, jsonToFormat, listifyMethodsFromMapping, logoutFence, manifestApi, manifestTags, mdsApi, nestedHistogramQueryStrForEachField, normalizeRtkError, notificationService, prepareUrl$1 as prepareUrl, prependIndexToFieldName, printRegistry, processHistogramResponse, projectCodeFromResourcePath, queryMultipleMDSRecords, rawDataQueryStrForEachField$1 as rawDataQueryStrForEachField, registerDefaultRemoteSupport, removeCohort, removeCohortFilter, removeItemsFromCart, removeJEGActiveKernel, removeManyJEGActiveKernels, requestorApi, resetUserState, resourcePathFromProjectID, roundHistogramResponse, selectActiveWorkspaceId, selectActiveWorkspaceStatus, selectAllCohortFiltersCollapsed, selectAllCohorts, selectAllJEGKernels, selectAuthzMappingData, selectAvailableCohortByName, selectAvailableCohorts, selectCSRFToken, selectCSRFTokenData, selectCart, selectCartCount, selectCartItem, selectCartItems, selectCohortById, selectCohortFilterCombineMode, selectCohortFilterExpanded, selectCohortFilters, selectCohortIds, selectCurrentCohort, selectCurrentCohortFilters, selectCurrentCohortId, selectCurrentCohortModified, selectCurrentCohortName, selectCurrentCohortSaved, selectCurrentMessage, selectCurrentModal, selectGen3AppByName, selectGen3AppMetadataByName, selectHeadersWithCSRFToken, selectIndexFilters, selectIndexedFilterByName, selectJEGActiveWorkspaceId, selectJEGActiveWorkspaceStatus, selectJEGKernelById, selectJEGKernelIds, selectJEGRequestedWorkspaceStatus, selectJEGRequestedWorkspaceStatusTimestamp, selectPaymodelStatus, selectRequestedWorkspaceStatus, selectRequestedWorkspaceStatusTimestamp, selectSharedFilters, selectSharedFiltersForFields, selectShouldShareFilters, selectSowerJobDatetimeCache, selectTotalCohorts, selectUser, selectUserAuthStatus, selectUserData, selectUserDetails, selectUserLoginStatus, selectWorkspaceFullscreen, selectWorkspaceStatus, selectWorkspaceStatusFromService, selectWorkspaceTier, setActiveWorkspace, setActiveWorkspaceId, setActiveWorkspaceStatus, setCohortFilter, setCohortFilterCombineMode, setCohortIndexFilters, setCohortList, setCurrentCohortId, setDRSHostnames, setJEGActiveWorkspace, setJEGActiveWorkspaceId, setJEGActiveWorkspaceStatus, setJEGRequestedWorkspaceStatus, setRequestedWorkspaceStatus, setSharedFilters, setShouldShareFilters, setWorkspaceFullscreen, setWorkspaceTier, setupCoreStore, showModal, showNotification, statsQueryStrForEachField, stringifyJSONParam, submissionApi, toggleCohortBuilderAllFilters, toggleCohortBuilderCategoryFilter, trimFirstfieldNameToLabel, updateCohortFilter, updateCohortName, updateJEGActionKernelStatus, updateStudyInMDS, upsertManyJEGActiveKernels, useAddCohortManifestMutation, useAddFileManifestMutation, useAddMetadataManifestMutation, useAddNewCredentialMutation, useAskQuestionMutation, useAuthorizeFromCredentialsMutation, useCohortFacetsQuery, useCoreDispatch, useCoreSelector, useCreateAuthzResourceMutation, useCreateRequestMutation, useCustomRangeQuery, useDataLibrary, useDownloadFromGuppyQuery, useFetchUserDetailsQuery, useGeneralGQLQuery, useGetAISearchStatusQuery, useGetAISearchVersionQuery, useGetAccessibleDataQuery, useGetActivePayModelQuery, useGetAggMDSQuery, useGetAggsQuery, useGetAllFieldsForTypeQuery, useGetArrayTypes, useGetAuthzMappingsQuery, useGetAuthzResourcesQuery, useGetCSRFQuery, useGetCohortManifestQuery, useGetCountsQuery, useGetCredentialsQuery, useGetCrosswalkDataQuery, useGetDataQuery, useGetDictionaryFromUrlQuery, useGetDictionaryQuery, useGetDownloadQuery, useGetExternalLoginsQuery, useGetFederatedLoginStatus, useGetFieldCountSummaryQuery, useGetFieldsForIndexQuery, useGetFileFromManifestQuery, useGetFileManifestQuery, useGetIndexAggMDSQuery, useGetIndexFields, useGetIndexObjectQuery, useGetIndexdMetdataQuery, useGetJWKKeysQuery, useGetLoginProvidersQuery, useGetMDSQuery, useGetManifestServiceStatusQuery, useGetMetadataByIdQuery, useGetMetadataByUrlQuery, useGetMetadataFromManifestQuery, useGetMetadataManifestQuery, useGetObjectIdsQuery, useGetPresignedUrlQuery, useGetProjectsDetailsQuery, useGetProjectsQuery, useGetRawDataAndTotalCountsQuery, useGetSharedFieldsForIndexQuery, useGetSowerJobListQuery, useGetSowerJobStatusQuery, useGetSowerOutputQuery, useGetSowerServiceStatusQuery, useGetStatsAggregationsQuery, useGetStatus, useGetSubAggsQuery, useGetSubmissionGraphQLQuery, useGetSubmissionsQuery, useGetTagsQuery, useGetUserDetailsRequestStatus, useGetWorkspaceOptionsQuery, useGetWorkspacePayModelsQuery, useGetWorkspaceStatusQuery, useGraphQLQuery, useIsExternalConnectedQuery, useIsUserLoggedIn, useLaunchWorkspaceMutation, useLazyCustomRangeQuery, useLazyDownloadFromGuppyQuery, useLazyFetchUserDetailsQuery, useLazyGeneralGQLQuery, useLazyGetAggsQuery, useLazyGetAuthzMappingsQuery, useLazyGetAuthzResourcesQuery, useLazyGetCSRFQuery, useLazyGetCountsQuery, useLazyGetCrosswalkDataQuery, useLazyGetDownloadQuery, useLazyGetExternalLoginsQuery, useLazyGetIndexObjectQuery, useLazyGetIndexdMetdataQuery, useLazyGetJWKKeysQuery, useLazyGetManifestServiceStatusQuery, useLazyGetMultipleSowerJobStatusQuery, useLazyGetObjectIdsQuery, useLazyGetPresignedUrlQuery, useLazyGetProjectsQuery, useLazyGetSowerJobListQuery, useLazyGetSowerJobStatusQuery, useLazyGetSowerOutputQuery, useLazyGetStatsAggregationsQuery, useLazyGetSubmissionGraphQLQuery, useLazyIsExternalConnectedQuery, useLazyRequestQuery, usePValueQuery, usePrevious, useRemoveCredentialMutation, useRequestByIdQuery, useRequestQuery, useRequestorStatusQuery, useSetCurrentPayModelMutation, useSubmitSowerJobMutation, useTerminateWorkspaceMutation, useUpdateStudyInMDSMutation, useUserAuth, useUserRequestQuery, useVennDiagramQuery, userHasCreateOrUpdateOnAnyProject, userHasDataUpload, userHasMethodForServiceOnProject, userHasMethodForServiceOnResource, userHasMethodOnAnyProject, userHasSheepdogProgramAdmin, userHasSheepdogProjectAdmin };
7053
7023
  //# sourceMappingURL=index.js.map