@hestia-earth/ui-components 0.42.10 → 0.42.11

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.
@@ -9362,6 +9362,10 @@ const identitySpec = (type, nodeType, nodeKey) => {
9362
9362
  const properties = [...new Set([...(def.properties || []), ...parentFields])].filter((field) => !excludedIdentityFields.includes(field) && !relations.includes(field.split('.')[0]));
9363
9363
  return { def, properties, relations };
9364
9364
  };
9365
+ // some node keys distinguish blank nodes by their recorded `model` (e.g. an Indicator's `methodModel.@id`
9366
+ // is a uniqueness field): models that ran in parallel for the same term's value are then separate rows
9367
+ // (one per model), not extra model columns on a single row
9368
+ const modelIsUniquenessField = (spec) => spec.properties.includes('methodModel.@id');
9365
9369
  const isPresent = (value) => value !== undefined && value !== null && value !== '';
9366
9370
  // the joined uniqueness-field ids of each item in a relation (e.g. an emission's `inputs[].@id`)
9367
9371
  const relationItemIds = (items, fields) => Array.isArray(items)
@@ -9671,7 +9675,10 @@ const mapValueModel = (models, valueModelId, condition, update) => models.map(mo
9671
9675
  * When the value came from the original (no model, no config), the row shows no model.
9672
9676
  */
9673
9677
  // eslint-disable-next-line complexity -- orchestrates the value-model resolution (delegated to helpers)
9674
- const valueModelRuns = (entry, value, originalValue, nodeType, termId, nodeKey, config) => {
9678
+ const valueModelRuns = (entry, value, originalValue, nodeType, termId, nodeKey, config,
9679
+ // when the recorded `model` is a uniqueness field, parallel siblings are their own rows (see
9680
+ // `parallelSiblingRows`), so they are dropped from this value row's models
9681
+ splitParallelModels = false) => {
9675
9682
  const valueModelId = blankNodeModelId(value);
9676
9683
  const isRecalculated = isRecalculatedValue(value, originalValue);
9677
9684
  const hasOriginal = !!originalValue && typeof originalValue.value !== 'undefined';
@@ -9685,9 +9692,21 @@ const valueModelRuns = (entry, value, originalValue, nodeType, termId, nodeKey,
9685
9692
  const isValueLog = (log) => !isBackgroundLog(log) && !isSubRowOrchestrator(log?.orchestrator?.value, subRowValues);
9686
9693
  const valueLog = selectValueLog(logs, valueModelId, isValueLog, isRecalculated);
9687
9694
  const valueOrchestrator = valueLog?.orchestrator?.value;
9688
- const rawLogModels = valueLog
9689
- ? jLogModelRuns(logs.filter((log) => log?.orchestrator?.value === valueOrchestrator && !isBackgroundLog(log)), nodeType, termId, false)
9690
- : [];
9695
+ const matchesValueOrchestrator = (log) => log?.orchestrator?.value === valueOrchestrator && !isBackgroundLog(log);
9696
+ // keep the parallel grouping (a nested `logs` array): parallel siblings are independent, so a model
9697
+ // that did not run failed rather than being "skipped" by a sibling that ran (a higher-tier hierarchy
9698
+ // model). `jLogModelColumns` applies the pre-group success state to each parallel sibling; flatten the
9699
+ // resulting columns back to the flat run list this function returns
9700
+ const valueLogSteps = (entry?.logs || [])
9701
+ .map((step) => Array.isArray(step) ? step.filter(matchesValueOrchestrator) : matchesValueOrchestrator(step) ? step : undefined)
9702
+ .filter((step) => (Array.isArray(step) ? step.length > 0 : !!step));
9703
+ const allLogModels = valueLog ? flattenColumns(jLogModelColumns(valueLogSteps, nodeType, termId, false)) : [];
9704
+ // when models are split into their own rows, this value row keeps only its own model (parallel siblings
9705
+ // become separate rows); otherwise parallel siblings stay as fallback/sibling columns on this row
9706
+ const siblingIds = splitParallelModels
9707
+ ? new Set(parallelSiblingLogs(entry, valueModelId).map((log) => log.model))
9708
+ : new Set();
9709
+ const rawLogModels = allLogModels.filter(model => !siblingIds.has(model.methodId));
9691
9710
  // the recorded value model (the blank node `methodModel`, e.g. `hestiaAggregatedData`) can differ from
9692
9711
  // the single orchestrator model in the logs (e.g. `linkedImpactAssessment`); relabel that run to the
9693
9712
  // recorded model so it shows under the expected name, but keep the orchestrator run's logs and its
@@ -9778,6 +9797,22 @@ const allSubRows = (entry, value, originalValue, nodeType, type, termId, getTerm
9778
9797
  ];
9779
9798
  };
9780
9799
  const flattenColumns = (columns) => columns.flatMap(column => (Array.isArray(column) ? column : [column]));
9800
+ // the distinct models (other than the value's own `model`) that ran in parallel with it - a nested
9801
+ // `logs` group holding more than one model. When `model` is a uniqueness field each becomes its own row,
9802
+ // so they are split off the value row (a sibling that did not win is its own failed/separate run, not a
9803
+ // fallback column). Keeps the first log per model.
9804
+ const parallelSiblingLogs = (entry, valueModelId) => {
9805
+ const parallelGroups = (entry?.logs || []).filter((step) => Array.isArray(step) && new Set(step.filter((e) => !!e?.model).map((e) => e.model)).size > 1);
9806
+ const byModel = new Map();
9807
+ parallelGroups
9808
+ .flat()
9809
+ .filter((log) => !!log?.model && log.model !== valueModelId)
9810
+ .forEach((log) => {
9811
+ if (!byModel.has(log.model))
9812
+ byModel.set(log.model, log);
9813
+ });
9814
+ return [...byModel.values()];
9815
+ };
9781
9816
  // a `<key>-failed` entry has no value: its identity/sub-rows are empty and the models that tried are
9782
9817
  // errors; parallel models (a nested `logs` array) are kept grouped (`modelColumns`) so they stack in one
9783
9818
  // column, while `models` stays flat for the status checks
@@ -9807,7 +9842,7 @@ const valueBlankNodeContent = (entry, value, originalValue, termId, { nodeType,
9807
9842
  subRows: allSubRows(entry, value, originalValue, nodeType, type, termId, getTerm || defaultGetTerm),
9808
9843
  isRecalculated: isRecalculatedValue(value, originalValue),
9809
9844
  recalculated: value ? [value] : [],
9810
- models: valueModelRuns(entry, value, originalValue, nodeType, termId, nodeKey, config)
9845
+ models: valueModelRuns(entry, value, originalValue, nodeType, termId, nodeKey, config, modelIsUniquenessField(spec))
9811
9846
  };
9812
9847
  };
9813
9848
  const buildBlankNode = (entry, value, originalValue, index, term, isFailed, context) => {
@@ -9829,6 +9864,42 @@ const buildBlankNode = (entry, value, originalValue, index, term, isFailed, cont
9829
9864
  : valueBlankNodeContent(entry, value, originalValue, termId, context))
9830
9865
  };
9831
9866
  };
9867
+ // a parallel model that ran for a term's value but did not produce its own blank node (e.g. it failed,
9868
+ // while a sibling set the value). When `model` is a uniqueness field it is shown as its own row -
9869
+ // distinguished by its `methodModel`, with no value and a single model column carrying its status
9870
+ const buildParallelSiblingRow = (log, term, index, context) => {
9871
+ const termId = term['@id'];
9872
+ const run = toModelRun(log, jLogModelStatus(log, false, false), context.nodeType, termId);
9873
+ // carry the model as a `methodModel` Term so the row is distinguished by a methodModel link (like a
9874
+ // value row), resolved through the same `getTerm` the component uses for names; it has no `value`, so
9875
+ // it contributes nothing to the group's combined value
9876
+ const methodModel = context.getTerm?.(log.model) ?? { '@type': NodeType.Term, '@id': log.model };
9877
+ const syntheticValue = { '@type': context.type, term, methodModel };
9878
+ return {
9879
+ index,
9880
+ term,
9881
+ termId,
9882
+ type: context.type,
9883
+ originalValue: null,
9884
+ isOriginal: false,
9885
+ isRequired: true,
9886
+ isFailed: false,
9887
+ original: [],
9888
+ recalculatedValue: null,
9889
+ identity: { 'methodModel.@id': log.model },
9890
+ identityLabel: '',
9891
+ identitySegments: [],
9892
+ isRecalculated: false,
9893
+ recalculated: [syntheticValue],
9894
+ subRows: [],
9895
+ models: [run],
9896
+ modelColumns: [run],
9897
+ // `allSucceeded` is recomputed by `withDisplayValues` at the end of the pipeline
9898
+ allSucceeded: false,
9899
+ canOpen: false,
9900
+ isOpen: false
9901
+ };
9902
+ };
9832
9903
  /**
9833
9904
  * Group the `.jlog` logs for one node key into rows, one per term, keeping every blank node distinct.
9834
9905
  *
@@ -9853,15 +9924,36 @@ const groupJLogByTerm = (jlog, nodeKey, recalculatedValues = [], originalValues
9853
9924
  const key = identityKey(blankNodeIdentity(value, spec));
9854
9925
  return (originalValues || []).find(original => original.term?.['@id'] === value.term?.['@id'] && identityKey(blankNodeIdentity(original, spec)) === key);
9855
9926
  };
9927
+ // each recalculated blank node, attaching its `.jlog` logs when present (a node may exist without logs,
9928
+ // e.g. a value provided in the original)
9929
+ const valueEntries = (recalculatedValues || [])
9930
+ .map((value, index) => ({ value, index, term: value?.term }))
9931
+ .filter(({ term }) => !!term?.['@id']);
9932
+ const valueNodes = valueEntries.map(({ value, index, term }) => buildBlankNode(keyLogs[index] || {}, value, matchOriginal(value), index, term, false, context));
9933
+ // when the recorded `model` is a uniqueness field, models that ran in parallel for a term's value but
9934
+ // produced no blank node (e.g. a sibling that failed while another set the value) are shown as their
9935
+ // own rows - skipping any model already represented by a value row for that term (avoiding duplicates)
9936
+ const seenModelsByTerm = new Map();
9937
+ valueEntries.forEach(({ value, term }) => {
9938
+ const set = seenModelsByTerm.get(term['@id']) ?? new Set();
9939
+ const modelId = blankNodeModelId(value);
9940
+ if (modelId)
9941
+ set.add(modelId);
9942
+ seenModelsByTerm.set(term['@id'], set);
9943
+ });
9944
+ let siblingIndex = recalculatedValues?.length ?? 0;
9945
+ const siblingNodes = modelIsUniquenessField(spec)
9946
+ ? valueEntries.flatMap(({ value, index, term }) => parallelSiblingLogs(keyLogs[index] || {}, blankNodeModelId(value)).flatMap(log => {
9947
+ const seen = seenModelsByTerm.get(term['@id']);
9948
+ if (seen?.has(log.model))
9949
+ return [];
9950
+ seen?.add(log.model);
9951
+ return [buildParallelSiblingRow(log, term, siblingIndex++, context)];
9952
+ }))
9953
+ : [];
9856
9954
  const blankNodes = [
9857
- // list every blank node, attaching its `.jlog` logs when present (a node may exist without logs,
9858
- // e.g. a value provided in the original)
9859
- ...(recalculatedValues || []).map((value, index) => {
9860
- const term = value?.term;
9861
- return term?.['@id']
9862
- ? buildBlankNode(keyLogs[index] || {}, value, matchOriginal(value), index, term, false, context)
9863
- : null;
9864
- }),
9955
+ ...valueNodes,
9956
+ ...siblingNodes,
9865
9957
  ...Object.entries(failedLogs).map(([termId, entry], i) => buildBlankNode(entry, undefined, undefined, -1 - i, getTerm(termId), true, context))
9866
9958
  ].filter(Boolean);
9867
9959
  return orderGroups(groupBlankNodesByTerm(blankNodes).map(withCollapsible).map(withRowLabels).map(withDisplayValues), nodeKey);
@@ -10089,7 +10181,9 @@ class NodeJLogModelsComponent {
10089
10181
  if (!this.isBlankNodes()) {
10090
10182
  return groupJLogByField(this.scopedJlog(), this.nodeKey(), this.originalValues(), this.recalculatedValues(), this.nodeType());
10091
10183
  }
10092
- const groups = groupJLogByTerm(this.scopedJlog(), this.nodeKey(), this.recalculatedValues(), this.originalValues(), this.nodeType(), id => this.failedTerms()[id] || { '@type': NodeType.Term, '@id': id, name: termTypeLabel(id) }, this.config());
10184
+ const groups = groupJLogByTerm(this.scopedJlog(), this.nodeKey(), this.recalculatedValues(), this.originalValues(), this.nodeType(), id => this.failedTerms()[id] ||
10185
+ this.methodsById()?.[id] ||
10186
+ { '@type': NodeType.Term, '@id': id, name: termTypeLabel(id) }, this.config());
10093
10187
  // a combined node key (e.g. `emissionsResourceUse`) holds several term types; keep only the requested
10094
10188
  // ones (like the legacy `computeTerms`), keeping terms whose type is not yet resolved
10095
10189
  const termTypes = this.filterTermTypes();