@gen3/core 0.12.51 → 0.12.53

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
@@ -997,96 +997,6 @@ const ExtractValueFromObject = (obj, key, valueIfNotFound)=>{
997
997
  return obj?.[key] ?? valueIfNotFound;
998
998
  };
999
999
 
1000
- const DAYS_IN_YEAR = 365.25;
1001
- /**
1002
- * Converts HistogramData to HistogramDataAsStringKey by ensuring the key is a string.
1003
- * If the key is already a string, it's used as is.
1004
- * If the key is a number tuple [min, max], it's converted to a string in the format "min-max".
1005
- *
1006
- * @param data The HistogramData object to convert
1007
- * @returns HistogramDataAsStringKey with the converted key
1008
- */ const convertToHistogramDataAsStringKey = (data)=>{
1009
- return {
1010
- key: typeof data.key === 'string' ? data.key : `${data.key[0]}-${data.key[1]}`,
1011
- count: data.count
1012
- };
1013
- };
1014
- const calculatePercentageAsNumber = (count, total)=>count ? count / total * 100 : 0;
1015
- const calculatePercentageAsString = (count, total)=>`${(count / total * 100).toFixed(2)}%`;
1016
- const capitalize$1 = (original)=>{
1017
- const customCapitalizations = {
1018
- id: 'ID',
1019
- uuid: 'UUID',
1020
- dna: 'DNA',
1021
- dbsnp: 'dbSNP',
1022
- cosmic: 'COSMIC',
1023
- civic: 'CIViC',
1024
- dbgap: 'dbGaP',
1025
- ecog: 'ECOG',
1026
- bmi: 'BMI',
1027
- gdc: 'GDC',
1028
- cnv: 'CNV',
1029
- ssm: 'SSM',
1030
- aa: 'AA'
1031
- };
1032
- return original.split(' ').map((word)=>customCapitalizations[word.toLowerCase()] || `${word.charAt(0).toUpperCase()}${word.slice(1)}`).join(' ');
1033
- };
1034
- const humanify = ({ term = '', capitalize: cap = true, facetTerm = false })=>{
1035
- let original;
1036
- let humanified;
1037
- if (facetTerm) {
1038
- // Splits on capital letters followed by lowercase letters to find
1039
- // words squished together in a string.
1040
- original = term?.split(/(?=[A-Z][a-z])/).join(' ');
1041
- humanified = term?.replace(/\./g, ' ').replace(/_/g, ' ').trim();
1042
- } else {
1043
- const split = (original || term)?.split('.');
1044
- humanified = split[split.length - 1]?.replace(/_/g, ' ').trim();
1045
- // Special case 'name' to include any parent nested for sake of
1046
- // specificity in the UI
1047
- if (humanified === 'name' && split?.length > 1) {
1048
- humanified = `${split[split?.length - 2]} ${humanified}`;
1049
- }
1050
- }
1051
- return cap ? capitalize$1(humanified) : humanified;
1052
- };
1053
- /*https://github.com/NCI-GDC/portal-ui/blob/develop/src/packages/%40ncigdc/utils/ageDisplay.js*/ /**
1054
- * Converts age in days into a human-readable format.
1055
- *
1056
- * @param ageInDays - The age in days.
1057
- * @param yearsOnly - If true, only display years.
1058
- * @defaultValue false
1059
- * @param defaultValue - The default value to return if ageInDays is falsy.
1060
- * @defaultValue "--"
1061
- * @returns The formatted age string.
1062
- */ const ageDisplay = (ageInDays, yearsOnly = false, defaultValue = '--')=>{
1063
- if (ageInDays !== 0 && !ageInDays) {
1064
- return defaultValue;
1065
- }
1066
- const calculateYearsAndDays = (years, days)=>days === 365 ? [
1067
- years + 1,
1068
- 0
1069
- ] : [
1070
- years,
1071
- days
1072
- ];
1073
- const ABS_AGE_DAYS = Math.abs(ageInDays);
1074
- const [years, remainingDays] = calculateYearsAndDays(Math.floor(ABS_AGE_DAYS / DAYS_IN_YEAR), Math.ceil(ABS_AGE_DAYS % DAYS_IN_YEAR));
1075
- const formattedYears = years === 0 ? '' : `${years} ${years === 1 ? 'year' : 'years'}`;
1076
- const formattedDays = !yearsOnly && remainingDays > 0 ? `${remainingDays} ${remainingDays === 1 ? 'day' : 'days'}` : years === 0 && remainingDays === 0 ? '0 days' : '';
1077
- const ageString = [
1078
- formattedYears,
1079
- formattedDays
1080
- ].filter(Boolean).join(' ');
1081
- return ageInDays >= 0 ? ageString : `-${ageString}`;
1082
- };
1083
- /**
1084
- * Given an object of JSON, stringify it into a string.
1085
- * @param obj - the object to stringify
1086
- * @param defaults - the default value to return if the object is undefined
1087
- * @category Utility
1088
- */ const stringifyJSONParam = (obj, defaults = '{}')=>obj ? JSON.stringify(obj) : defaults;
1089
-
1090
1000
  // type guard functions
1091
1001
  const isHistogramRangeData = (key)=>{
1092
1002
  return Array.isArray(key) && key.length === 2 && key.every((item)=>typeof item === 'number');
@@ -1183,7 +1093,75 @@ function isHttpStatusError(error) {
1183
1093
  */ function isFetchParseError(error) {
1184
1094
  return typeof error === 'object' && error != null && 'originalStatus' in error && 'status' in error && error['status'] === 'PARSING_ERROR';
1185
1095
  }
1096
+ /**
1097
+ * Type predicate to narrow an unknown error to a `SerializedError`
1098
+ */ function isSerializedError(error) {
1099
+ return typeof error === 'object' && error != null && !('status' in error) && ('message' in error || 'name' in error || 'code' in error || 'stack' in error);
1100
+ }
1101
+
1102
+ const HTTPUserFriendlyErrorMessages = {
1103
+ // 4xx Client Errors
1104
+ 400: 'Sorry, there was a problem with your request. Please check the information you provided and try again.',
1105
+ 401: 'Please sign in to access this content. If you need to create an account, please contact support.',
1106
+ 402: 'This feature requires payment to continue. Please update your subscription.',
1107
+ 403: "You don't have permission to access this. If you think this is a mistake, please contact support.",
1108
+ 404: "We couldn't find what you're looking for. The link might be broken or the content may have been moved.",
1109
+ 405: 'This action is not supported. Please try a different approach.',
1110
+ 406: "Your browser sent a request that this server couldn't understand. Try updating your browser.",
1111
+ 407: 'Please authenticate with the network proxy first.',
1112
+ 408: 'The request took too long to complete. Please check your internet connection and try again.',
1113
+ 409: 'There was a conflict with your request. Someone might have modified the same content.',
1114
+ 410: 'This content has been permanently removed and is no longer available.',
1115
+ 411: 'There was a technical problem with your request. Please try again.',
1116
+ 412: "Some conditions for this request weren't met. Please try again.",
1117
+ 413: "The file you're trying to upload is too large. Please choose a smaller file.",
1118
+ 414: 'The web address is too long. Please try a shorter URL.',
1119
+ 415: 'This type of file is not supported. Please try a different format.',
1120
+ 416: 'The requested content range is not available.',
1121
+ 417: "Your request couldn't be completed as expected. Please try again.",
1122
+ 418: 'Unusual error occurred. Please try again or contact support if the problem persists.',
1123
+ 421: 'Your request was sent to the wrong server. Please try again.',
1124
+ 422: "We couldn't process your request. Please check the information and try again.",
1125
+ 423: 'This resource is temporarily locked. Please try again in a few moments.',
1126
+ 424: 'The request failed because it depends on another request that failed.',
1127
+ 425: 'Your request was processed too early. Please try again in a moment.',
1128
+ 426: 'You need to upgrade your browser or app to continue.',
1129
+ 428: "Some required conditions weren't met. Please check all requirements and try again.",
1130
+ 429: "You've made too many requests. Please wait a while before trying again.",
1131
+ 431: 'Request failed because the headers are too large. Please simplify your request.',
1132
+ 451: 'This content is not available for legal reasons.',
1133
+ // 5xx Server Errors
1134
+ 500: 'Something went wrong with the service. Please try again later.',
1135
+ 501: "This feature isn't available yet. Check back later.",
1136
+ 502: "We're having trouble connecting to our servers. Please try again in a few minutes.",
1137
+ 503: "Our service is temporarily unavailable. We're working to restore it as quickly as possible.",
1138
+ 504: 'The server took too long to respond. Please try again later.',
1139
+ 505: "Your browser's HTTP version isn't supported. Please try updating your browser.",
1140
+ 506: 'We encountered a configuration error.',
1141
+ 507: "We're out of storage space.",
1142
+ 508: 'We detected an infinite loop in our servers.',
1143
+ 510: 'This server requires additional extensions to process your request.',
1144
+ 511: 'Please sign in to the network before continuing.'
1145
+ };
1186
1146
 
1147
+ const MAX_ERROR_LENGTH = 256;
1148
+ const DEFAULT_API_ERROR_MESSAGE = 'API Query Error';
1149
+ /**
1150
+ * Checks if a string appears to be an HTML page
1151
+ */ function isHtmlContent(str) {
1152
+ const trimmed = str.trim().toLowerCase();
1153
+ return trimmed.startsWith('<!doctype html') || trimmed.startsWith('<html') || /<html[\s>]/i.test(str);
1154
+ }
1155
+ /**
1156
+ * Sanitizes an error message - returns DEFAULT_API_ERROR_MESSAGE if
1157
+ * the message is too long or contains HTML content
1158
+ */ function sanitizeErrorMessage(message) {
1159
+ if (!message) return DEFAULT_API_ERROR_MESSAGE;
1160
+ if (message.length > MAX_ERROR_LENGTH || isHtmlContent(message)) {
1161
+ return DEFAULT_API_ERROR_MESSAGE;
1162
+ }
1163
+ return message;
1164
+ }
1187
1165
  /** Best-effort message extraction from an HTTP error body */ function extractMessage(data) {
1188
1166
  if (typeof data === 'string') return data;
1189
1167
  if (typeof data === 'object' && data != null) {
@@ -1210,10 +1188,11 @@ function normalizeRtkError(error) {
1210
1188
  if (isFetchBaseQueryError(error)) {
1211
1189
  // status is number for HTTP errors, string literal for the others
1212
1190
  if (typeof error.status === 'number') {
1191
+ const extractedMessage = extractMessage(error.data);
1213
1192
  return {
1214
1193
  type: 'HTTP_ERROR',
1215
1194
  status: error.status,
1216
- message: extractMessage(error.data) ?? `Request failed with status ${error.status}`,
1195
+ message: sanitizeErrorMessage(extractedMessage) !== DEFAULT_API_ERROR_MESSAGE ? sanitizeErrorMessage(extractedMessage) : HTTPUserFriendlyErrorMessages[error.status] ?? `Request failed with status ${error.status}`,
1217
1196
  data: error.data
1218
1197
  };
1219
1198
  }
@@ -1227,18 +1206,18 @@ function normalizeRtkError(error) {
1227
1206
  return {
1228
1207
  type: 'PARSING_ERROR',
1229
1208
  status: error.originalStatus,
1230
- message: error.error,
1209
+ message: HTTPUserFriendlyErrorMessages[error.originalStatus] ?? `Request failed with status ${error.originalStatus}`,
1231
1210
  data: error.data
1232
1211
  };
1233
1212
  case 'TIMEOUT_ERROR':
1234
1213
  return {
1235
1214
  type: 'TIMEOUT_ERROR',
1236
- message: error.error
1215
+ message: sanitizeErrorMessage(error.error)
1237
1216
  };
1238
1217
  case 'CUSTOM_ERROR':
1239
1218
  return {
1240
1219
  type: 'CUSTOM_ERROR',
1241
- message: error.error,
1220
+ message: sanitizeErrorMessage(error.error),
1242
1221
  data: error.data
1243
1222
  };
1244
1223
  }
@@ -1247,7 +1226,7 @@ function normalizeRtkError(error) {
1247
1226
  if ('message' in error || 'name' in error) {
1248
1227
  return {
1249
1228
  type: 'SERIALIZED_ERROR',
1250
- message: error.message ?? 'An error occurred'
1229
+ message: sanitizeErrorMessage(error.message) || 'An error occurred'
1251
1230
  };
1252
1231
  }
1253
1232
  return {
@@ -1256,6 +1235,96 @@ function normalizeRtkError(error) {
1256
1235
  };
1257
1236
  }
1258
1237
 
1238
+ const DAYS_IN_YEAR = 365.25;
1239
+ /**
1240
+ * Converts HistogramData to HistogramDataAsStringKey by ensuring the key is a string.
1241
+ * If the key is already a string, it's used as is.
1242
+ * If the key is a number tuple [min, max], it's converted to a string in the format "min-max".
1243
+ *
1244
+ * @param data The HistogramData object to convert
1245
+ * @returns HistogramDataAsStringKey with the converted key
1246
+ */ const convertToHistogramDataAsStringKey = (data)=>{
1247
+ return {
1248
+ key: typeof data.key === 'string' ? data.key : `${data.key[0]}-${data.key[1]}`,
1249
+ count: data.count
1250
+ };
1251
+ };
1252
+ const calculatePercentageAsNumber = (count, total)=>count ? count / total * 100 : 0;
1253
+ const calculatePercentageAsString = (count, total)=>`${(count / total * 100).toFixed(2)}%`;
1254
+ const capitalize$1 = (original)=>{
1255
+ const customCapitalizations = {
1256
+ id: 'ID',
1257
+ uuid: 'UUID',
1258
+ dna: 'DNA',
1259
+ dbsnp: 'dbSNP',
1260
+ cosmic: 'COSMIC',
1261
+ civic: 'CIViC',
1262
+ dbgap: 'dbGaP',
1263
+ ecog: 'ECOG',
1264
+ bmi: 'BMI',
1265
+ gdc: 'GDC',
1266
+ cnv: 'CNV',
1267
+ ssm: 'SSM',
1268
+ aa: 'AA'
1269
+ };
1270
+ return original.split(' ').map((word)=>customCapitalizations[word.toLowerCase()] || `${word.charAt(0).toUpperCase()}${word.slice(1)}`).join(' ');
1271
+ };
1272
+ const humanify = ({ term = '', capitalize: cap = true, facetTerm = false })=>{
1273
+ let original;
1274
+ let humanified;
1275
+ if (facetTerm) {
1276
+ // Splits on capital letters followed by lowercase letters to find
1277
+ // words squished together in a string.
1278
+ original = term?.split(/(?=[A-Z][a-z])/).join(' ');
1279
+ humanified = term?.replace(/\./g, ' ').replace(/_/g, ' ').trim();
1280
+ } else {
1281
+ const split = (original || term)?.split('.');
1282
+ humanified = split[split.length - 1]?.replace(/_/g, ' ').trim();
1283
+ // Special case 'name' to include any parent nested for sake of
1284
+ // specificity in the UI
1285
+ if (humanified === 'name' && split?.length > 1) {
1286
+ humanified = `${split[split?.length - 2]} ${humanified}`;
1287
+ }
1288
+ }
1289
+ return cap ? capitalize$1(humanified) : humanified;
1290
+ };
1291
+ /*https://github.com/NCI-GDC/portal-ui/blob/develop/src/packages/%40ncigdc/utils/ageDisplay.js*/ /**
1292
+ * Converts age in days into a human-readable format.
1293
+ *
1294
+ * @param ageInDays - The age in days.
1295
+ * @param yearsOnly - If true, only display years.
1296
+ * @defaultValue false
1297
+ * @param defaultValue - The default value to return if ageInDays is falsy.
1298
+ * @defaultValue "--"
1299
+ * @returns The formatted age string.
1300
+ */ const ageDisplay = (ageInDays, yearsOnly = false, defaultValue = '--')=>{
1301
+ if (ageInDays !== 0 && !ageInDays) {
1302
+ return defaultValue;
1303
+ }
1304
+ const calculateYearsAndDays = (years, days)=>days === 365 ? [
1305
+ years + 1,
1306
+ 0
1307
+ ] : [
1308
+ years,
1309
+ days
1310
+ ];
1311
+ const ABS_AGE_DAYS = Math.abs(ageInDays);
1312
+ const [years, remainingDays] = calculateYearsAndDays(Math.floor(ABS_AGE_DAYS / DAYS_IN_YEAR), Math.ceil(ABS_AGE_DAYS % DAYS_IN_YEAR));
1313
+ const formattedYears = years === 0 ? '' : `${years} ${years === 1 ? 'year' : 'years'}`;
1314
+ const formattedDays = !yearsOnly && remainingDays > 0 ? `${remainingDays} ${remainingDays === 1 ? 'day' : 'days'}` : years === 0 && remainingDays === 0 ? '0 days' : '';
1315
+ const ageString = [
1316
+ formattedYears,
1317
+ formattedDays
1318
+ ].filter(Boolean).join(' ');
1319
+ return ageInDays >= 0 ? ageString : `-${ageString}`;
1320
+ };
1321
+ /**
1322
+ * Given an object of JSON, stringify it into a string.
1323
+ * @param obj - the object to stringify
1324
+ * @param defaults - the default value to return if the object is undefined
1325
+ * @category Utility
1326
+ */ const stringifyJSONParam = (obj, defaults = '{}')=>obj ? JSON.stringify(obj) : defaults;
1327
+
1259
1328
  const queryWTSFederatedLoginStatus = async (signal)=>{
1260
1329
  try {
1261
1330
  const results = await fetchJSONDataFromURL(`${GEN3_WTS_API}/external_oidc/`, false, HttpMethod.GET, undefined, signal);
@@ -6956,6 +7025,7 @@ exports.GEN3_SUBMISSION_API = GEN3_SUBMISSION_API;
6956
7025
  exports.GEN3_WORKSPACE_API = GEN3_WORKSPACE_API;
6957
7026
  exports.HTTPError = HTTPError;
6958
7027
  exports.HTTPErrorMessages = HTTPErrorMessages;
7028
+ exports.HTTPUserFriendlyErrorMessages = HTTPUserFriendlyErrorMessages;
6959
7029
  exports.HttpMethod = HttpMethod;
6960
7030
  exports.MissingServiceConfigurationError = MissingServiceConfigurationError;
6961
7031
  exports.Modals = Modals;
@@ -7095,6 +7165,7 @@ exports.isOperatorWithFieldAndArrayOfOperands = isOperatorWithFieldAndArrayOfOpe
7095
7165
  exports.isPending = isPending;
7096
7166
  exports.isProgramUrl = isProgramUrl;
7097
7167
  exports.isRootUrl = isRootUrl;
7168
+ exports.isSerializedError = isSerializedError;
7098
7169
  exports.isStatsValue = isStatsValue;
7099
7170
  exports.isStatsValuesArray = isStatsValuesArray;
7100
7171
  exports.isString = isString;