@hestia-earth/ui-components 0.43.33 → 0.43.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7334,16 +7334,16 @@ const groupLog = (group, { logger, term, model, key, should_run, should_merge, r
7334
7334
  };
7335
7335
  const groupLogSubValue = (group, log, key) => {
7336
7336
  const id = log[key];
7337
- const data = groupLog(group[id] || {}, log);
7338
- group[id] = data;
7339
- if (subValueKeys.includes(key)) {
7340
- // add value to the same term/model on parent group
7341
- Object.entries(group[id]).map(([term, models]) => Object.keys(models).map(model => {
7342
- if (typeof group?.[term]?.[model] === 'object') {
7343
- group[term][model][key] = unique([...(group[term][model][key] || []), id]);
7344
- }
7345
- }));
7346
- }
7337
+ group[id] = groupLog(group[id] || {}, log);
7338
+ return group;
7339
+ };
7340
+ // add each subValue to the same term/model on the parent group
7341
+ const linkSubValues = (group, subValues) => {
7342
+ subValues.forEach(([key, id]) => Object.entries(group[id]).forEach(([term, models]) => Object.keys(models).forEach(model => {
7343
+ if (typeof group?.[term]?.[model] === 'object') {
7344
+ group[term][model][key] = unique([...(group[term][model][key] || []), id]);
7345
+ }
7346
+ })));
7347
7347
  return group;
7348
7348
  };
7349
7349
  const csvValue = (value) => (value || '').replace('[', '').replace(']', '');
@@ -7353,50 +7353,64 @@ const parseLogMessage = (message) => {
7353
7353
  // Split on a comma (and optional space) ONLY if it's followed by "key="
7354
7354
  // \w+ matches alphanumeric characters and underscores (standard log keys)
7355
7355
  const pairs = data.message.split(/,\s*(?=\w+=)/);
7356
- return pairs.reduce((prev, parts) => {
7357
- // Use indexOf instead of split('=') just in case your values ever contain an '='
7358
- const firstEqualsIndex = parts.indexOf('=');
7359
- if (firstEqualsIndex === -1)
7360
- return prev;
7361
- const key = parts.substring(0, firstEqualsIndex).trim();
7362
- const value = parts.substring(firstEqualsIndex + 1).trim();
7363
- const val = csvValue(value);
7364
- return {
7365
- ...prev,
7366
- ...(key && val ? { [key]: val } : {})
7367
- };
7368
- }, { logger: data.logger });
7356
+ return Object.fromEntries([
7357
+ ['logger', data.logger],
7358
+ ...pairs
7359
+ // Use indexOf instead of split('=') just in case your values ever contain an '='
7360
+ .map((parts) => [parts, parts.indexOf('=')])
7361
+ .filter(([, index]) => index !== -1)
7362
+ .map(([parts, index]) => [parts.substring(0, index).trim(), csvValue(parts.substring(index + 1).trim())])
7363
+ .filter(([key, value]) => key && value)
7364
+ ]);
7369
7365
  }
7370
7366
  catch {
7371
7367
  // make sure it works even if one log is malformed
7372
7368
  return {};
7373
7369
  }
7374
7370
  };
7375
- const groupLogsByModel = (data = '', groupBySubValue = false, groupByAnimal = false) => data
7376
- .trim()
7377
- .split('\n')
7378
- .map(parseLogMessage)
7379
- .filter(v => [hasValue(v?.model), ['term', 'key', 'animalId'].some(k => hasValue(v?.[k]))].every(Boolean))
7380
- .reduce((group, log) => {
7381
- const subValue = subValueKeys.find(v => !!log[v] && log[v] != noValue);
7382
- return subValue && groupBySubValue && !log.cycle
7383
- ? groupLogSubValue(group, log, subValue)
7384
- : groupLog(group, log, groupByAnimal);
7385
- }, {});
7371
+ const subValueOf = (log, groupBySubValue) => {
7372
+ const key = groupBySubValue && !log.cycle ? subValueKeys.find(v => !!log[v] && log[v] != noValue) : undefined;
7373
+ return key ? [key, log[key]] : undefined;
7374
+ };
7375
+ const groupLogsByModel = (data = '', groupBySubValue = false, groupByAnimal = false) => {
7376
+ const logs = data
7377
+ .trim()
7378
+ .split('\n')
7379
+ .map(parseLogMessage)
7380
+ .filter(v => [hasValue(v?.model), ['term', 'key', 'animalId'].some(k => hasValue(v?.[k]))].every(Boolean));
7381
+ const subValues = logs.map(log => subValueOf(log, groupBySubValue));
7382
+ const group = logs.reduce((prev, log, index) => subValues[index] ? groupLogSubValue(prev, log, subValues[index][0]) : groupLog(prev, log, groupByAnimal), {});
7383
+ const uniqueSubValues = [...new Map(subValues.filter(Boolean).map(v => [v.join('.'), v])).values()];
7384
+ return linkSubValues(group, uniqueSubValues);
7385
+ };
7386
7386
  const logNodeFor = (node) => ({
7387
7387
  '@type': node['@type'],
7388
7388
  '@id': node['@id'],
7389
7389
  dataState: node.aggregated && node['@type'] !== NodeType.ImpactAssessment ? DataState.original : DataState.recalculated,
7390
7390
  dataVersion: node.dataVersion
7391
7391
  });
7392
+ const logsCacheSize = 3;
7393
+ const logsCacheKey = ({ '@type': type, '@id': id, dataState, dataVersion }, groupByAnimal) => [type, id, dataState, dataVersion, groupByAnimal].join('/');
7394
+ /**
7395
+ * Store `value` as the most recent entry, dropping the oldest ones beyond `size`.
7396
+ */
7397
+ const lruSet = (cache, key, value, size) => new Map([...[...cache].filter(([k]) => k !== key), [key, value]].slice(-size));
7392
7398
  class NodeLogsModelsService {
7393
7399
  constructor() {
7394
7400
  this.nodeService = inject(HeNodeService);
7401
+ // parsing a large text log takes seconds, so keep the last few parsed logs for components showing the same node
7402
+ this.logsCache = new Map();
7395
7403
  }
7396
7404
  getNodeLogs$(node, groupByAnimal = false) {
7397
- return this.nodeService
7398
- .getLog$(logNodeFor(node))
7399
- .pipe(map(logs => logs ? groupLogsByModel(logs, node['@type'] === NodeType.Cycle, groupByAnimal) : {}));
7405
+ const key = logsCacheKey(logNodeFor(node), groupByAnimal);
7406
+ const logs$ = this.logsCache.get(key) ?? this.fetchNodeLogs$(node, groupByAnimal, key);
7407
+ this.logsCache = lruSet(this.logsCache, key, logs$, logsCacheSize);
7408
+ return logs$;
7409
+ }
7410
+ fetchNodeLogs$(node, groupByAnimal, key) {
7411
+ return this.nodeService.getLog$(logNodeFor(node)).pipe(map(logs => logs ? groupLogsByModel(logs, node['@type'] === NodeType.Cycle, groupByAnimal) : {}),
7412
+ // a failed request also ends up empty: do not keep it, so the next component retries
7413
+ tap(logs => isEmpty(logs) && this.logsCache.delete(key)), shareReplay(1));
7400
7414
  }
7401
7415
  /**
7402
7416
  * Fetch the new `.jlog` (JSON) recalculation logs, following the representation of the node.