@gen3/core 0.11.32 → 0.11.33

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.
package/dist/cjs/index.js CHANGED
@@ -6,18 +6,18 @@ var cookiesNext = require('cookies-next');
6
6
  var query = require('@reduxjs/toolkit/query');
7
7
  var reactRedux = require('react-redux');
8
8
  var React = require('react');
9
+ var graphql = require('graphql');
10
+ var jsonpathPlus = require('jsonpath-plus');
9
11
  var nanoid$1 = require('nanoid');
10
12
  var useSWR = require('swr');
11
13
  var lodash = require('lodash');
12
14
  var flat = require('flat');
13
15
  var Papa = require('papaparse');
14
- var jsonpathPlus = require('jsonpath-plus');
15
16
  var reduxPersist = require('redux-persist');
16
17
  var createWebStorage = require('redux-persist/lib/storage/createWebStorage');
17
18
  var react$1 = require('redux-persist/integration/react');
18
19
  var idb = require('idb');
19
20
  var useDeepCompare = require('use-deep-compare');
20
- var graphql = require('graphql');
21
21
  var uuid = require('uuid');
22
22
  var reactCookie = require('react-cookie');
23
23
  var Queue = require('queue');
@@ -806,6 +806,15 @@ const selectRequestedWorkspaceStatusTimestamp = (state)=>state.activeWorkspace.r
806
806
  data: await response.json()
807
807
  };
808
808
  } catch (e) {
809
+ if (e instanceof graphql.GraphQLError) {
810
+ return {
811
+ error: {
812
+ message: e.message,
813
+ locations: e.locations,
814
+ path: e.path
815
+ }
816
+ };
817
+ }
809
818
  if (e instanceof Error) return {
810
819
  error: e.message
811
820
  };
@@ -1615,6 +1624,36 @@ const filterSetToOperation = (fs)=>{
1615
1624
  }
1616
1625
  return undefined;
1617
1626
  };
1627
+ /**
1628
+ * Constructs a nested operation object based on the provided field and leaf operand.
1629
+ * If the field does not contain a dot '.', it either assigns the field to the leaf operand (if applicable)
1630
+ * or returns the leaf operand as is. When the field contains dots, it splits the field into parts,
1631
+ * creates a "nested" operation for the root field, and recursively constructs the nested structure
1632
+ * for the remaining portion of the field.
1633
+ *
1634
+ * @param {string} field - The hierarchical field path, with segments separated by dots (e.g., "root.child").
1635
+ * @param {Operation} leafOperand - The operation to be nested within the specified path.
1636
+ * @param parentPath - The parent path of the current field. Guppy nested filters require a parent path.
1637
+ * @param depth
1638
+ * @returns {Operation} A nested operation object that represents the structured path and operand.
1639
+ */ const buildNestedGQLFilter = (field, leafOperand, parentPath = undefined)=>{
1640
+ if (!field.includes('.')) {
1641
+ return leafOperand;
1642
+ }
1643
+ const splitFieldArray = field.split('.');
1644
+ const nextField = splitFieldArray.shift();
1645
+ if (!nextField) {
1646
+ console.warn('Invalid field path:', field);
1647
+ return leafOperand;
1648
+ }
1649
+ const currentPath = parentPath ? `${parentPath}.${nextField}` : nextField;
1650
+ return {
1651
+ nested: {
1652
+ path: currentPath,
1653
+ ...buildNestedGQLFilter(splitFieldArray.join('.'), leafOperand, currentPath)
1654
+ }
1655
+ };
1656
+ };
1618
1657
 
1619
1658
  const isFilterSet = (input)=>{
1620
1659
  if (typeof input !== 'object' || input === null) {
@@ -1669,7 +1708,7 @@ const COMMON_PREPOSITIONS = [
1669
1708
  'up',
1670
1709
  'yet'
1671
1710
  ];
1672
- const capitalize = (s)=>s.length > 0 ? s[0].toUpperCase() + s.slice(1) : '';
1711
+ const capitalize$1 = (s)=>s.length > 0 ? s[0].toUpperCase() + s.slice(1) : '';
1673
1712
  const trimFirstFieldNameToTitle = (fieldName, trim = false)=>{
1674
1713
  if (trim) {
1675
1714
  const source = fieldName.slice(fieldName.indexOf('.') + 1);
@@ -1688,7 +1727,7 @@ const trimFirstFieldNameToTitle = (fieldName, trim = false)=>{
1688
1727
  return FieldNameOverrides[fieldName];
1689
1728
  }
1690
1729
  if (fieldName === undefined) return 'No Title';
1691
- return fieldName.split('.').slice(-sections).map((s)=>s.split('_')).flat().map((word)=>COMMON_PREPOSITIONS.includes(word) ? word : capitalize(word)).join(' ');
1730
+ return fieldName.split('.').slice(-sections).map((s)=>s.split('_')).flat().map((word)=>COMMON_PREPOSITIONS.includes(word) ? word : capitalize$1(word)).join(' ');
1692
1731
  };
1693
1732
  /**
1694
1733
  * Extracts the index name from the field name
@@ -1870,14 +1909,22 @@ function isHttpStatusError(error) {
1870
1909
  * @param {string} csrfToken - The CSRF token to include in the request headers.
1871
1910
  * @returns {FetchConfig} - The prepared fetch configuration object.
1872
1911
  */ const prepareFetchConfig = (parameters, csrfToken)=>{
1912
+ const headers = new Headers({
1913
+ Accept: 'application/json',
1914
+ 'Content-Type': 'application/json',
1915
+ ...csrfToken !== undefined && {
1916
+ 'X-CSRF-Token': csrfToken
1917
+ }
1918
+ });
1919
+ if (process.env.NODE_ENV === 'development') {
1920
+ // NOTE: This cookie can only be accessed from the client side
1921
+ // in development mode. Otherwise, the cookie is set as httpOnly
1922
+ const accessToken = cookiesNext.getCookie('credentials_token');
1923
+ if (accessToken) headers.set('Authorization', `Bearer ${accessToken}`);
1924
+ }
1873
1925
  return {
1874
1926
  method: 'POST',
1875
- headers: {
1876
- 'Content-Type': 'application/json',
1877
- ...csrfToken !== undefined && {
1878
- 'X-CSRF-Token': csrfToken
1879
- }
1880
- },
1927
+ headers: headers,
1881
1928
  body: JSON.stringify({
1882
1929
  type: parameters.type,
1883
1930
  filter: convertFilterSetToGqlFilter(parameters.filter),
@@ -2062,6 +2109,88 @@ const groupSharedFields = (data)=>{
2062
2109
  return data;
2063
2110
  };
2064
2111
 
2112
+ const customQueryStrForField = (field, query, depth = 0)=>{
2113
+ const indent = ' '.repeat(depth);
2114
+ const splittedFieldArray = field.split('.');
2115
+ const splittedField = splittedFieldArray.shift();
2116
+ if (splittedFieldArray.length === 0) {
2117
+ return `${indent}${splittedField} ${query}`;
2118
+ }
2119
+ return `${indent}${splittedField} {
2120
+ ${customQueryStrForField(splittedFieldArray.join('.'), query, depth + 1)}
2121
+ ${indent}}`;
2122
+ };
2123
+ // TODO: refactor the function below using customQueryStrForEachField and a wrapper function that passes the query
2124
+ const histogramQueryStrForEachField = (field)=>{
2125
+ const splittedFieldArray = field.split('.');
2126
+ const splittedField = splittedFieldArray.shift();
2127
+ if (splittedFieldArray.length === 0) {
2128
+ return `
2129
+ ${splittedField} {
2130
+ histogram {
2131
+ key
2132
+ count
2133
+ }
2134
+ }`;
2135
+ }
2136
+ return `
2137
+ ${splittedField} {
2138
+ ${histogramQueryStrForEachField(splittedFieldArray.join('.'))}
2139
+ }`;
2140
+ };
2141
+ const statsQueryStrForEachField = (field)=>{
2142
+ const splittedFieldArray = field.split('.');
2143
+ const splittedField = splittedFieldArray.shift();
2144
+ if (splittedFieldArray.length === 0) {
2145
+ return `
2146
+ ${splittedField} {
2147
+ histogram {
2148
+ count
2149
+ min
2150
+ max
2151
+ avg
2152
+ sum
2153
+ }
2154
+ }`;
2155
+ }
2156
+ return `
2157
+ ${splittedField} {
2158
+ ${statsQueryStrForEachField(splittedFieldArray.join('.'))}
2159
+ }`;
2160
+ };
2161
+ const nestedHistogramQueryStrForEachField = (mainField, numericAggAsText)=>`
2162
+ ${mainField} {
2163
+ ${numericAggAsText ? 'asTextHistogram' : 'histogram'} {
2164
+ key
2165
+ count
2166
+ missingFields {
2167
+ field
2168
+ count
2169
+ }
2170
+ termsFields {
2171
+ field
2172
+ count
2173
+ terms {
2174
+ key
2175
+ count
2176
+ }
2177
+ }
2178
+ }
2179
+ }`;
2180
+ const rawDataQueryStrForEachField = (field)=>{
2181
+ const splitFieldArray = field.split('.');
2182
+ const splitField = splitFieldArray.shift();
2183
+ if (splitFieldArray.length === 0) {
2184
+ return `
2185
+ ${splitField}
2186
+ `;
2187
+ }
2188
+ return `
2189
+ ${splitField} {
2190
+ ${rawDataQueryStrForEachField(splitFieldArray.join('.'))}
2191
+ }`;
2192
+ };
2193
+
2065
2194
  const statusEndpoint = '/_status';
2066
2195
  const fetchJson = async (url)=>{
2067
2196
  const res = await fetch(url, {
@@ -2342,75 +2471,6 @@ const explorerTags = guppyApi.enhanceEndpoints({
2342
2471
  })
2343
2472
  })
2344
2473
  });
2345
- const histogramQueryStrForEachField = (field)=>{
2346
- const splittedFieldArray = field.split('.');
2347
- const splittedField = splittedFieldArray.shift();
2348
- if (splittedFieldArray.length === 0) {
2349
- return `
2350
- ${splittedField} {
2351
- histogram {
2352
- key
2353
- count
2354
- }
2355
- }`;
2356
- }
2357
- return `
2358
- ${splittedField} {
2359
- ${histogramQueryStrForEachField(splittedFieldArray.join('.'))}
2360
- }`;
2361
- };
2362
- const statsQueryStrForEachField = (field)=>{
2363
- const splittedFieldArray = field.split('.');
2364
- const splittedField = splittedFieldArray.shift();
2365
- if (splittedFieldArray.length === 0) {
2366
- return `
2367
- ${splittedField} {
2368
- histogram {
2369
- count
2370
- min
2371
- max
2372
- avg
2373
- sum
2374
- }
2375
- }`;
2376
- }
2377
- return `
2378
- ${splittedField} {
2379
- ${statsQueryStrForEachField(splittedFieldArray.join('.'))}
2380
- }`;
2381
- };
2382
- const nestedHistogramQueryStrForEachField = (mainField, numericAggAsText)=>`
2383
- ${mainField} {
2384
- ${numericAggAsText ? 'asTextHistogram' : 'histogram'} {
2385
- key
2386
- count
2387
- missingFields {
2388
- field
2389
- count
2390
- }
2391
- termsFields {
2392
- field
2393
- count
2394
- terms {
2395
- key
2396
- count
2397
- }
2398
- }
2399
- }
2400
- }`;
2401
- const rawDataQueryStrForEachField = (field)=>{
2402
- const splitFieldArray = field.split('.');
2403
- const splitField = splitFieldArray.shift();
2404
- if (splitFieldArray.length === 0) {
2405
- return `
2406
- ${splitField}
2407
- `;
2408
- }
2409
- return `
2410
- ${splitField} {
2411
- ${rawDataQueryStrForEachField(splitFieldArray.join('.'))}
2412
- }`;
2413
- };
2414
2474
  const useGetArrayTypes = ()=>{
2415
2475
  {
2416
2476
  const { data, error } = useGetStatus();
@@ -2762,6 +2822,28 @@ const fetchFencePresignedURL = async ({ guid, method = 'GET', onAbort = ()=>null
2762
2822
  return await response.json();
2763
2823
  };
2764
2824
 
2825
+ const extractValuesFromObject = (jsonPathMappings, obj)=>{
2826
+ const result = {};
2827
+ const extractObjectValue = (jsonPath, obj)=>{
2828
+ const extractedValues = jsonpathPlus.JSONPath({
2829
+ path: jsonPath,
2830
+ json: obj
2831
+ });
2832
+ return extractedValues.length > 0 ? extractedValues[0] : undefined;
2833
+ };
2834
+ for(const key in jsonPathMappings){
2835
+ if (key in Object.keys(jsonPathMappings)) {
2836
+ // Extract value from an object and store it in the result.
2837
+ result[key] = extractObjectValue(jsonPathMappings[key], obj);
2838
+ }
2839
+ }
2840
+ return result;
2841
+ };
2842
+ const ExtractValueFromObject = (obj, key, valueIfNotFound)=>{
2843
+ return obj?.[key] ?? valueIfNotFound;
2844
+ };
2845
+
2846
+ const DAYS_IN_YEAR = 365.25;
2765
2847
  /**
2766
2848
  * Converts HistogramData to HistogramDataAsStringKey by ensuring the key is a string.
2767
2849
  * If the key is already a string, it's used as is.
@@ -2777,6 +2859,79 @@ const fetchFencePresignedURL = async ({ guid, method = 'GET', onAbort = ()=>null
2777
2859
  };
2778
2860
  const calculatePercentageAsNumber = (count, total)=>count ? count / total * 100 : 0;
2779
2861
  const calculatePercentageAsString = (count, total)=>`${(count / total * 100).toFixed(2)}%`;
2862
+ const capitalize = (original)=>{
2863
+ const customCapitalizations = {
2864
+ id: 'ID',
2865
+ uuid: 'UUID',
2866
+ dna: 'DNA',
2867
+ dbsnp: 'dbSNP',
2868
+ cosmic: 'COSMIC',
2869
+ civic: 'CIViC',
2870
+ dbgap: 'dbGaP',
2871
+ ecog: 'ECOG',
2872
+ bmi: 'BMI',
2873
+ gdc: 'GDC',
2874
+ cnv: 'CNV',
2875
+ ssm: 'SSM',
2876
+ aa: 'AA'
2877
+ };
2878
+ return original.split(' ').map((word)=>customCapitalizations[word.toLowerCase()] || `${word.charAt(0).toUpperCase()}${word.slice(1)}`).join(' ');
2879
+ };
2880
+ const humanify = ({ term = '', capitalize: cap = true, facetTerm = false })=>{
2881
+ let original;
2882
+ let humanified;
2883
+ if (facetTerm) {
2884
+ // Splits on capital letters followed by lowercase letters to find
2885
+ // words squished together in a string.
2886
+ original = term?.split(/(?=[A-Z][a-z])/).join(' ');
2887
+ humanified = term?.replace(/\./g, ' ').replace(/_/g, ' ').trim();
2888
+ } else {
2889
+ const split = (original || term)?.split('.');
2890
+ humanified = split[split.length - 1]?.replace(/_/g, ' ').trim();
2891
+ // Special case 'name' to include any parent nested for sake of
2892
+ // specificity in the UI
2893
+ if (humanified === 'name' && split?.length > 1) {
2894
+ humanified = `${split[split?.length - 2]} ${humanified}`;
2895
+ }
2896
+ }
2897
+ return cap ? capitalize(humanified) : humanified;
2898
+ };
2899
+ /*https://github.com/NCI-GDC/portal-ui/blob/develop/src/packages/%40ncigdc/utils/ageDisplay.js*/ /**
2900
+ * Converts age in days into a human-readable format.
2901
+ *
2902
+ * @param ageInDays - The age in days.
2903
+ * @param yearsOnly - If true, only display years.
2904
+ * @defaultValue false
2905
+ * @param defaultValue - The default value to return if ageInDays is falsy.
2906
+ * @defaultValue "--"
2907
+ * @returns The formatted age string.
2908
+ */ const ageDisplay = (ageInDays, yearsOnly = false, defaultValue = '--')=>{
2909
+ if (ageInDays !== 0 && !ageInDays) {
2910
+ return defaultValue;
2911
+ }
2912
+ const calculateYearsAndDays = (years, days)=>days === 365 ? [
2913
+ years + 1,
2914
+ 0
2915
+ ] : [
2916
+ years,
2917
+ days
2918
+ ];
2919
+ const ABS_AGE_DAYS = Math.abs(ageInDays);
2920
+ const [years, remainingDays] = calculateYearsAndDays(Math.floor(ABS_AGE_DAYS / DAYS_IN_YEAR), Math.ceil(ABS_AGE_DAYS % DAYS_IN_YEAR));
2921
+ const formattedYears = years === 0 ? '' : `${years} ${years === 1 ? 'year' : 'years'}`;
2922
+ const formattedDays = !yearsOnly && remainingDays > 0 ? `${remainingDays} ${remainingDays === 1 ? 'day' : 'days'}` : years === 0 && remainingDays === 0 ? '0 days' : '';
2923
+ const ageString = [
2924
+ formattedYears,
2925
+ formattedDays
2926
+ ].filter(Boolean).join(' ');
2927
+ return ageInDays >= 0 ? ageString : `-${ageString}`;
2928
+ };
2929
+ /**
2930
+ * Given an object of JSON, stringify it into a string.
2931
+ * @param obj - the object to stringify
2932
+ * @param defaults - the default value to return if the object is undefined
2933
+ * @category Utility
2934
+ */ const stringifyJSONParam = (obj, defaults = '{}')=>obj ? JSON.stringify(obj) : defaults;
2780
2935
 
2781
2936
  const queryWTSFederatedLoginStatus = async (signal)=>{
2782
2937
  try {
@@ -4768,7 +4923,7 @@ const { useGraphQLQuery } = graphQLAPI;
4768
4923
  return {
4769
4924
  url: `${GEN3_GUPPY_API}/download`,
4770
4925
  method: 'POST',
4771
- body: JSON.stringify(queryBody),
4926
+ body: queryBody,
4772
4927
  cache: 'no-cache'
4773
4928
  };
4774
4929
  },
@@ -5321,24 +5476,6 @@ const userHasMethodOnAnyProject = (method, userAuthMapping = {})=>{
5321
5476
  };
5322
5477
  const userHasCreateOrUpdateOnAnyProject = (userAuthMapping = {})=>userHasMethodOnAnyProject('create', userAuthMapping) || userHasMethodOnAnyProject('update', userAuthMapping);
5323
5478
 
5324
- const extractValuesFromObject = (jsonPathMappings, obj)=>{
5325
- const result = {};
5326
- const extractObjectValue = (jsonPath, obj)=>{
5327
- const extractedValues = jsonpathPlus.JSONPath({
5328
- path: jsonPath,
5329
- json: obj
5330
- });
5331
- return extractedValues.length > 0 ? extractedValues[0] : undefined;
5332
- };
5333
- for(const key in jsonPathMappings){
5334
- if (key in Object.keys(jsonPathMappings)) {
5335
- // Extract value from an object and store it in the result.
5336
- result[key] = extractObjectValue(jsonPathMappings[key], obj);
5337
- }
5338
- }
5339
- return result;
5340
- };
5341
-
5342
5479
  const SubmissionGraphqlQuery = `query transactionList {
5343
5480
  transactionList: transaction_log(last: 20) {
5344
5481
  id
@@ -5624,10 +5761,12 @@ const isWorkspaceRunningOrStopping = (status)=>status === WorkspaceStatus.Runnin
5624
5761
  exports.Accessibility = Accessibility;
5625
5762
  exports.CohortStorage = CohortStorage;
5626
5763
  exports.CoreProvider = CoreProvider;
5764
+ exports.DAYS_IN_YEAR = DAYS_IN_YEAR;
5627
5765
  exports.DataLibraryStoreMode = DataLibraryStoreMode;
5628
5766
  exports.EmptyFilterSet = EmptyFilterSet;
5629
5767
  exports.EmptyWorkspaceStatusResponse = EmptyWorkspaceStatusResponse;
5630
5768
  exports.EnumValueExtractorHandler = EnumValueExtractorHandler;
5769
+ exports.ExtractValueFromObject = ExtractValueFromObject;
5631
5770
  exports.GEN3_API = GEN3_API;
5632
5771
  exports.GEN3_AUTHZ_API = GEN3_AUTHZ_API;
5633
5772
  exports.GEN3_COMMONS_NAME = GEN3_COMMONS_NAME;
@@ -5653,12 +5792,15 @@ exports.RequestedWorkspaceStatus = RequestedWorkspaceStatus;
5653
5792
  exports.ToGqlHandler = ToGqlHandler;
5654
5793
  exports.ValueExtractorHandler = ValueExtractorHandler;
5655
5794
  exports.WorkspaceStatus = WorkspaceStatus;
5795
+ exports.ageDisplay = ageDisplay;
5656
5796
  exports.appendFilterToOperation = appendFilterToOperation;
5657
5797
  exports.buildGetAggregationQuery = buildGetAggregationQuery;
5658
5798
  exports.buildGetStatsAggregationQuery = buildGetStatsAggregationQuery;
5659
5799
  exports.buildListItemsGroupedByDataset = buildListItemsGroupedByDataset;
5800
+ exports.buildNestedGQLFilter = buildNestedGQLFilter;
5660
5801
  exports.calculatePercentageAsNumber = calculatePercentageAsNumber;
5661
5802
  exports.calculatePercentageAsString = calculatePercentageAsString;
5803
+ exports.capitalize = capitalize;
5662
5804
  exports.clearActiveWorkspaceId = clearActiveWorkspaceId;
5663
5805
  exports.clearCohortFilters = clearCohortFilters;
5664
5806
  exports.cohortReducer = cohortReducer;
@@ -5674,6 +5816,7 @@ exports.createGen3App = createGen3App;
5674
5816
  exports.createGen3AppWithOwnStore = createGen3AppWithOwnStore;
5675
5817
  exports.createNewCohort = createNewCohort;
5676
5818
  exports.createUseCoreDataHook = createUseCoreDataHook;
5819
+ exports.customQueryStrForField = customQueryStrForField;
5677
5820
  exports.defaultCohortNameGenerator = defaultCohortNameGenerator;
5678
5821
  exports.downloadFromGuppyToBlob = downloadFromGuppyToBlob;
5679
5822
  exports.downloadJSONDataFromGuppy = downloadJSONDataFromGuppy;
@@ -5714,6 +5857,7 @@ exports.handleGqlOperation = handleGqlOperation;
5714
5857
  exports.handleOperation = handleOperation;
5715
5858
  exports.hideModal = hideModal;
5716
5859
  exports.histogramQueryStrForEachField = histogramQueryStrForEachField;
5860
+ exports.humanify = humanify;
5717
5861
  exports.isAdditionalDataItem = isAdditionalDataItem;
5718
5862
  exports.isArray = isArray;
5719
5863
  exports.isAuthenticated = isAuthenticated;
@@ -5834,6 +5978,7 @@ exports.setShouldShareFilters = setShouldShareFilters;
5834
5978
  exports.setupCoreStore = setupCoreStore;
5835
5979
  exports.showModal = showModal;
5836
5980
  exports.statsQueryStrForEachField = statsQueryStrForEachField;
5981
+ exports.stringifyJSONParam = stringifyJSONParam;
5837
5982
  exports.submissionApi = submissionApi;
5838
5983
  exports.toggleCohortBuilderAllFilters = toggleCohortBuilderAllFilters;
5839
5984
  exports.toggleCohortBuilderCategoryFilter = toggleCohortBuilderCategoryFilter;