@isrd-isi-edu/ermrestjs 2.5.0 → 2.6.1
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/js/core.js +30 -4
- package/js/export.js +1 -1
- package/js/utils/pseudocolumn_helpers.js +2 -2
- package/package.json +8 -3
- package/src/models/active-list-condition.ts +216 -0
- package/src/models/reference/reference.ts +70 -175
- package/src/models/reference-column/asset-pseudo-column.ts +1 -1
- package/src/models/reference-column/foreign-key-pseudo-column.ts +4 -8
- package/src/models/reference-column/key-pseudo-column.ts +3 -4
- package/src/models/reference-column/pseudo-column.ts +3 -34
- package/src/models/reference-column/reference-column.ts +79 -8
- package/src/models/source-object-wrapper.ts +1 -1
- package/src/models/table-source-definitions.ts +37 -0
- package/src/services/active-list.ts +432 -0
- package/src/utils/constants.ts +3 -0
- package/src/utils/reference-utils.ts +6 -1
- package/src/utils/template-utils.ts +117 -0
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
RelatedReference,
|
|
34
34
|
BulkCreateForeignKeyObject,
|
|
35
35
|
} from '@isrd-isi-edu/ermrestjs/src/models/reference';
|
|
36
|
+
import ActiveListCondition from '@isrd-isi-edu/ermrestjs/src/models/active-list-condition';
|
|
36
37
|
|
|
37
38
|
// services
|
|
38
39
|
import ConfigService from '@isrd-isi-edu/ermrestjs/src/services/config';
|
|
@@ -41,7 +42,7 @@ import ErrorService from '@isrd-isi-edu/ermrestjs/src/services/error';
|
|
|
41
42
|
import $log from '@isrd-isi-edu/ermrestjs/src/services/logger';
|
|
42
43
|
|
|
43
44
|
// utils
|
|
44
|
-
import { createPseudoColumn,
|
|
45
|
+
import { createPseudoColumn, isRelatedColumn } from '@isrd-isi-edu/ermrestjs/src/utils/column-utils';
|
|
45
46
|
import {
|
|
46
47
|
computeReadPath,
|
|
47
48
|
generateFacetColumns,
|
|
@@ -71,7 +72,6 @@ import { onload } from '@isrd-isi-edu/ermrestjs/js/setup/node';
|
|
|
71
72
|
import {
|
|
72
73
|
_getPagingValues,
|
|
73
74
|
_getRecursiveAnnotationValue,
|
|
74
|
-
_isEntryContext,
|
|
75
75
|
_isValidForeignKeyName,
|
|
76
76
|
_isValidSortElement,
|
|
77
77
|
compareColumnPositions,
|
|
@@ -81,6 +81,14 @@ import { _compressFacetObject, _sourceColumnHelpers } from '@isrd-isi-edu/ermres
|
|
|
81
81
|
|
|
82
82
|
import type { CommentType } from '@isrd-isi-edu/ermrestjs/src/models/comment';
|
|
83
83
|
import type { DisplayName } from '@isrd-isi-edu/ermrestjs/src/models/display-name';
|
|
84
|
+
import {
|
|
85
|
+
ActiveListBuilder,
|
|
86
|
+
ActiveListRequestTypes,
|
|
87
|
+
type ActiveList,
|
|
88
|
+
type ActiveListRequest,
|
|
89
|
+
type ActiveListRelatedEntityRequest,
|
|
90
|
+
} from '@isrd-isi-edu/ermrestjs/src/services/active-list';
|
|
91
|
+
|
|
84
92
|
import { _exportHelpers, _getDefaultExportTemplate, _referenceExportOutput, validateExportTemplate } from '@isrd-isi-edu/ermrestjs/js/export';
|
|
85
93
|
|
|
86
94
|
/**
|
|
@@ -117,7 +125,7 @@ import { _exportHelpers, _getDefaultExportTemplate, _referenceExportOutput, vali
|
|
|
117
125
|
* @return Promise when resolved passes the
|
|
118
126
|
* {@link Reference} object. If rejected, passes one of the ermrest errors
|
|
119
127
|
*/
|
|
120
|
-
export const resolve = async (uri: string, contextHeaderParams?:
|
|
128
|
+
export const resolve = async (uri: string, contextHeaderParams?: unknown): Promise<Reference> => {
|
|
121
129
|
verify(uri, "'uri' must be specified");
|
|
122
130
|
// make sure all the dependencies are loaded
|
|
123
131
|
await onload();
|
|
@@ -148,22 +156,6 @@ export const _createPage = (reference: Reference, etag: string, data: any, hasPr
|
|
|
148
156
|
return new Page(reference, etag, data, hasPrevious, hasNext);
|
|
149
157
|
};
|
|
150
158
|
|
|
151
|
-
type ActiveList = {
|
|
152
|
-
// TODO
|
|
153
|
-
// requests: {
|
|
154
|
-
// column: ReferenceColumn;
|
|
155
|
-
// objects: Array<{ index: number; column?: boolean; related?: boolean; inline?: boolean; citation?: boolean }>;
|
|
156
|
-
// type: 'column' | 'related' | 'inline' | 'citation';
|
|
157
|
-
// // TODO backwards compatibility:
|
|
158
|
-
// inline?: boolean;
|
|
159
|
-
// related?: boolean;
|
|
160
|
-
// citation?: boolean;
|
|
161
|
-
// };
|
|
162
|
-
requests: any[];
|
|
163
|
-
allOutBounds: Array<ForeignKeyPseudoColumn | PseudoColumn>;
|
|
164
|
-
selfLinks: Array<KeyPseudoColumn>;
|
|
165
|
-
};
|
|
166
|
-
|
|
167
159
|
export type VisibleColumn =
|
|
168
160
|
| ReferenceColumn
|
|
169
161
|
| PseudoColumn
|
|
@@ -229,9 +221,9 @@ export class Reference {
|
|
|
229
221
|
private _canUseTRS?: boolean;
|
|
230
222
|
private _canUseTCRS?: boolean;
|
|
231
223
|
private _readPath?: string;
|
|
232
|
-
private _readAttributeGroupPathProps_cached?:
|
|
224
|
+
private _readAttributeGroupPathProps_cached?: unknown;
|
|
233
225
|
private _display?: ReferenceDisplay;
|
|
234
|
-
private _defaultExportTemplate?:
|
|
226
|
+
private _defaultExportTemplate?: unknown;
|
|
235
227
|
private _csvDownloadLink?: string | null;
|
|
236
228
|
private _searchColumns?: Array<VisibleColumn> | false;
|
|
237
229
|
private _cascadingDeletedItems?: Array<Table | RelatedReference | Reference>;
|
|
@@ -239,7 +231,7 @@ export class Reference {
|
|
|
239
231
|
private _related?: Array<RelatedReference>;
|
|
240
232
|
private _facetColumns?: Array<FacetColumn>;
|
|
241
233
|
private _facetColumnsStructure?: Array<number | FacetGroup>;
|
|
242
|
-
private _activeList?:
|
|
234
|
+
private _activeList?: ActiveList;
|
|
243
235
|
private _citation?: Citation | null;
|
|
244
236
|
private _googleDatasetMetadata?: GoogleDatasetMetadata | null;
|
|
245
237
|
|
|
@@ -328,6 +320,10 @@ export class Reference {
|
|
|
328
320
|
return this._pseudoColumn;
|
|
329
321
|
}
|
|
330
322
|
|
|
323
|
+
/**
|
|
324
|
+
* The column-directive that is used for creating this reference.
|
|
325
|
+
* Allows access to props defined on the column-directive (display props, condition, waitFor, etc)
|
|
326
|
+
*/
|
|
331
327
|
setPseudoColumn(value: VisibleColumn) {
|
|
332
328
|
this._pseudoColumn = value;
|
|
333
329
|
}
|
|
@@ -604,7 +600,7 @@ export class Reference {
|
|
|
604
600
|
/**
|
|
605
601
|
* Generate the list of columns for this reference based on context and annotations
|
|
606
602
|
*/
|
|
607
|
-
generateColumnsList(tuple?: Tuple, columnsList?:
|
|
603
|
+
generateColumnsList(tuple?: Tuple, columnsList?: Array<unknown>, dontChangeReference?: boolean, skipLog?: boolean): ReferenceColumn[] {
|
|
608
604
|
const resultColumns = generateColumnsList(this, tuple, columnsList, skipLog);
|
|
609
605
|
|
|
610
606
|
if (!dontChangeReference) {
|
|
@@ -849,7 +845,7 @@ export class Reference {
|
|
|
849
845
|
if (this._activeList === undefined) {
|
|
850
846
|
this.generateActiveList();
|
|
851
847
|
}
|
|
852
|
-
return this._activeList
|
|
848
|
+
return this._activeList!;
|
|
853
849
|
}
|
|
854
850
|
|
|
855
851
|
/**
|
|
@@ -878,182 +874,80 @@ export class Reference {
|
|
|
878
874
|
* @private
|
|
879
875
|
*/
|
|
880
876
|
generateActiveList(tuple?: Tuple): ActiveList {
|
|
881
|
-
// VARIABLES:
|
|
882
|
-
const allOutBounds: Array<PseudoColumn | ForeignKeyPseudoColumn> = [];
|
|
883
|
-
const requests: any[] = [];
|
|
884
|
-
const selfLinks: Array<KeyPseudoColumn> = [];
|
|
885
|
-
const consideredUniqueFiltered: { [key: string]: number } = {};
|
|
886
|
-
const consideredSets: { [key: string]: number } = {};
|
|
887
|
-
const consideredOutbounds: { [key: string]: boolean } = {};
|
|
888
|
-
const consideredAggregates: { [key: string]: number } = {};
|
|
889
|
-
const consideredSelfLinks: { [key: string]: boolean } = {};
|
|
890
|
-
const consideredEntryWaitFors: { [key: string]: number } = {};
|
|
891
|
-
|
|
892
|
-
const sds = this.table.sourceDefinitions;
|
|
893
|
-
|
|
894
|
-
// in detailed, we want related and citation
|
|
895
877
|
const isDetailed = this._context === _contexts.DETAILED;
|
|
896
|
-
|
|
897
|
-
// the waitfors will be used in chaise instead prior to submission.
|
|
898
|
-
const isEntry = _isEntryContext(this._context);
|
|
899
|
-
|
|
900
|
-
const COLUMN_TYPE = 'column';
|
|
901
|
-
const RELATED_TYPE = 'related';
|
|
902
|
-
const CITATION_TYPE = 'citation';
|
|
903
|
-
const INLINE_TYPE = 'inline';
|
|
904
|
-
|
|
905
|
-
// FUNCTIONS:
|
|
906
|
-
const hasAggregate = (col: VisibleColumn) => {
|
|
907
|
-
return col.hasWaitForAggregate || ((col as PseudoColumn).isPathColumn && (col as PseudoColumn).hasAggregate);
|
|
908
|
-
};
|
|
909
|
-
|
|
910
|
-
// col: the column that we need its data
|
|
911
|
-
// isWaitFor: whether it was part of waitFor or just visible
|
|
912
|
-
// type: where in the page it belongs to
|
|
913
|
-
// index: the container index (column index, or related index) (optional)
|
|
914
|
-
const addColToActiveList = (col: VisibleColumn, isWaitFor: boolean, type: string, index?: number) => {
|
|
915
|
-
const obj: any = { isWaitFor: isWaitFor };
|
|
916
|
-
|
|
917
|
-
// add the type
|
|
918
|
-
obj[type] = true;
|
|
919
|
-
|
|
920
|
-
// add index if available (not available in citation)
|
|
921
|
-
if (Number.isInteger(index)) {
|
|
922
|
-
obj.index = index;
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
if (isWaitFor && isEntry) {
|
|
926
|
-
if (col.name in consideredEntryWaitFors) {
|
|
927
|
-
requests[consideredEntryWaitFors[col.name]].objects.push(obj);
|
|
928
|
-
return;
|
|
929
|
-
}
|
|
930
|
-
consideredEntryWaitFors[col.name] = requests.length;
|
|
931
|
-
requests.push({ firstOutbound: true, column: col, objects: [obj] });
|
|
932
|
-
return;
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
// unique filtered
|
|
936
|
-
// TODO FILTER_IN_SOURCE chaise should use this type of column as well?
|
|
937
|
-
// TODO FILTER_IN_SOURCE should be added to documentation as well
|
|
938
|
-
if ((col as PseudoColumn).sourceObjectWrapper?.isUniqueFiltered) {
|
|
939
|
-
// duplicate
|
|
940
|
-
if (col.name in consideredUniqueFiltered) {
|
|
941
|
-
requests[consideredUniqueFiltered[col.name]].objects.push(obj);
|
|
942
|
-
return;
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
// new
|
|
946
|
-
consideredUniqueFiltered[col.name] = requests.length;
|
|
947
|
-
requests.push({ entity: true, column: col, objects: [obj] });
|
|
948
|
-
return;
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
// aggregates
|
|
952
|
-
if ((col as PseudoColumn).isPathColumn && (col as PseudoColumn).hasAggregate) {
|
|
953
|
-
// duplicate
|
|
954
|
-
if (col.name in consideredAggregates) {
|
|
955
|
-
requests[consideredAggregates[col.name]].objects.push(obj);
|
|
956
|
-
return;
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
// new
|
|
960
|
-
consideredAggregates[col.name] = requests.length;
|
|
961
|
-
requests.push({ aggregate: true, column: col, objects: [obj] });
|
|
962
|
-
return;
|
|
963
|
-
}
|
|
878
|
+
const builder = new ActiveListBuilder(this, tuple, isDetailed);
|
|
964
879
|
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
// all outbounds
|
|
981
|
-
if (isAllOutboundColumn(col)) {
|
|
982
|
-
if (col.name in consideredOutbounds) return;
|
|
983
|
-
consideredOutbounds[col.name] = true;
|
|
984
|
-
allOutBounds.push(col as PseudoColumn | ForeignKeyPseudoColumn);
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
// self-links
|
|
988
|
-
if ((col as KeyPseudoColumn).isKey) {
|
|
989
|
-
if (col.name in consideredSelfLinks) return;
|
|
990
|
-
consideredSelfLinks[col.name] = true;
|
|
991
|
-
selfLinks.push(col as KeyPseudoColumn);
|
|
992
|
-
}
|
|
993
|
-
};
|
|
994
|
-
|
|
995
|
-
const addInlineColumn = (col: VisibleColumn, i: number) => {
|
|
996
|
-
if (isRelatedColumn(col)) {
|
|
997
|
-
requests.push({ inline: true, index: i });
|
|
998
|
-
} else {
|
|
999
|
-
addColToActiveList(col, false, COLUMN_TYPE, i);
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
col.waitFor.forEach((wf: any) => {
|
|
1003
|
-
addColToActiveList(wf, true, isRelatedColumn(col) ? INLINE_TYPE : COLUMN_TYPE, i);
|
|
1004
|
-
});
|
|
880
|
+
/**
|
|
881
|
+
* Helper for the condition branch shared across the columns / related loops.
|
|
882
|
+
* Returns true if the column was routed through a conditional group (so the
|
|
883
|
+
* caller should NOT add the column directly), false if there was no
|
|
884
|
+
* applicable condition (caller adds normally).
|
|
885
|
+
*/
|
|
886
|
+
const tryCondition = (
|
|
887
|
+
condition: ActiveListCondition | null | undefined,
|
|
888
|
+
conditionedItem: { column?: boolean; inline?: boolean; related?: boolean; index: number },
|
|
889
|
+
addToDeps: (deps: Array<ActiveListRequest | ActiveListRelatedEntityRequest>) => void,
|
|
890
|
+
): boolean => {
|
|
891
|
+
if (!isDetailed || !condition) return false;
|
|
892
|
+
builder.processConditionedItem(condition, conditionedItem, addToDeps);
|
|
893
|
+
return true;
|
|
1005
894
|
};
|
|
1006
895
|
|
|
1007
|
-
// THE CODE STARTS HERE:
|
|
1008
896
|
const columns = this.generateColumnsList(tuple);
|
|
1009
897
|
|
|
1010
898
|
// citation
|
|
1011
899
|
if (isDetailed && this.citation) {
|
|
1012
900
|
this.citation.waitFor.forEach((col) => {
|
|
1013
|
-
|
|
901
|
+
builder.addCol(col, true, ActiveListRequestTypes.CITATION);
|
|
1014
902
|
});
|
|
1015
903
|
}
|
|
1016
904
|
|
|
1017
905
|
// columns without aggregate
|
|
1018
906
|
columns.forEach((col, i: number) => {
|
|
1019
|
-
if (
|
|
1020
|
-
|
|
1021
|
-
}
|
|
907
|
+
if (builder.hasAggregate(col)) return;
|
|
908
|
+
const rc = col.resolvedCondition;
|
|
909
|
+
const item = isRelatedColumn(col) ? { inline: true, index: i } : { column: true, index: i };
|
|
910
|
+
if (tryCondition(rc, item, (deps) => builder.addInlineToDependent(deps, col, i))) return;
|
|
911
|
+
builder.addInline(col, i);
|
|
1022
912
|
});
|
|
1023
913
|
|
|
1024
914
|
// columns with aggregate
|
|
1025
915
|
columns.forEach((col, i: number) => {
|
|
1026
|
-
if (hasAggregate(col))
|
|
1027
|
-
|
|
1028
|
-
}
|
|
916
|
+
if (!builder.hasAggregate(col)) return;
|
|
917
|
+
const rc = col.resolvedCondition;
|
|
918
|
+
const item = isRelatedColumn(col) ? { inline: true, index: i } : { column: true, index: i };
|
|
919
|
+
if (tryCondition(rc, item, (deps) => builder.addInlineToDependent(deps, col, i))) return;
|
|
920
|
+
builder.addInline(col, i);
|
|
1029
921
|
});
|
|
1030
922
|
|
|
1031
923
|
// related tables
|
|
1032
924
|
if (isDetailed) {
|
|
1033
925
|
this.generateRelatedList(tuple).forEach((rel, i) => {
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
926
|
+
const rc = rel.pseudoColumn?.resolvedCondition;
|
|
927
|
+
/**
|
|
928
|
+
* if there's a condition, we have to capture the requests generated for the related entity as its dependencies.
|
|
929
|
+
* So when the condition is evaludated, we can decide whether to run those requests or not.
|
|
930
|
+
*/
|
|
931
|
+
const addRelatedToDeps = (deps: Array<ActiveListRequest | ActiveListRelatedEntityRequest>) => {
|
|
932
|
+
deps.push({ related: true, index: i });
|
|
933
|
+
rel.pseudoColumn?.waitFor.forEach((wf) => {
|
|
934
|
+
builder.addColToDependent(deps, wf, true, ActiveListRequestTypes.RELATED, i);
|
|
1039
935
|
});
|
|
1040
|
-
}
|
|
936
|
+
};
|
|
937
|
+
if (tryCondition(rc, { related: true, index: i }, addRelatedToDeps)) return;
|
|
938
|
+
builder.requests.push({ related: true, index: i });
|
|
939
|
+
rel.pseudoColumn?.waitFor.forEach((wf) => {
|
|
940
|
+
builder.addCol(wf, true, ActiveListRequestTypes.RELATED, i);
|
|
941
|
+
});
|
|
1041
942
|
});
|
|
1042
943
|
}
|
|
1043
944
|
|
|
1044
|
-
//fkeys
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
consideredOutbounds[fk.name] = true;
|
|
1048
|
-
allOutBounds.push(new ForeignKeyPseudoColumn(this, fk));
|
|
945
|
+
// fkeys
|
|
946
|
+
this.table.sourceDefinitions.fkeys.forEach((fk) => {
|
|
947
|
+
builder.addAllOutBound(new ForeignKeyPseudoColumn(this, fk));
|
|
1049
948
|
});
|
|
1050
949
|
|
|
1051
|
-
this._activeList =
|
|
1052
|
-
requests: requests,
|
|
1053
|
-
allOutBounds: allOutBounds,
|
|
1054
|
-
selfLinks: selfLinks,
|
|
1055
|
-
};
|
|
1056
|
-
|
|
950
|
+
this._activeList = builder.build();
|
|
1057
951
|
return this._activeList;
|
|
1058
952
|
}
|
|
1059
953
|
|
|
@@ -1074,7 +968,7 @@ export class Reference {
|
|
|
1074
968
|
}
|
|
1075
969
|
|
|
1076
970
|
/**
|
|
1077
|
-
* Will return the
|
|
971
|
+
* Will return the export templates that are available for this reference.
|
|
1078
972
|
* It will validate the templates that are defined in annotations.
|
|
1079
973
|
* If its `detailed` context and annotation was missing,
|
|
1080
974
|
* it will return the default export template.
|
|
@@ -1088,6 +982,7 @@ export class Reference {
|
|
|
1088
982
|
|
|
1089
983
|
// annotation is missing
|
|
1090
984
|
if (!Array.isArray(templates)) {
|
|
985
|
+
// eslint-disable-next-line eqeqeq
|
|
1091
986
|
const canUseDefault = useDefault && this.context === _contexts.DETAILED && this.defaultExportTemplate != null;
|
|
1092
987
|
|
|
1093
988
|
return canUseDefault ? [this.defaultExportTemplate] : [];
|
|
@@ -1128,7 +1023,7 @@ export class Reference {
|
|
|
1128
1023
|
*/
|
|
1129
1024
|
getColumnByName(name: string): VisibleColumn {
|
|
1130
1025
|
// given an array of columns, find column by name
|
|
1131
|
-
const findCol = (list:
|
|
1026
|
+
const findCol = (list: Array<VisibleColumn | Column>): VisibleColumn | Column | false => {
|
|
1132
1027
|
for (let i = 0; i < list.length; i++) {
|
|
1133
1028
|
if (list[i].name === name) {
|
|
1134
1029
|
return list[i];
|
|
@@ -1140,13 +1035,13 @@ export class Reference {
|
|
|
1140
1035
|
// search in visible columns
|
|
1141
1036
|
let c = findCol(this.columns);
|
|
1142
1037
|
if (c) {
|
|
1143
|
-
return c;
|
|
1038
|
+
return c as VisibleColumn;
|
|
1144
1039
|
}
|
|
1145
1040
|
|
|
1146
1041
|
// search in table columns
|
|
1147
1042
|
c = findCol(this.table.columns.all());
|
|
1148
1043
|
if (c) {
|
|
1149
|
-
return new ReferenceColumn(this, [c]);
|
|
1044
|
+
return new ReferenceColumn(this, [c as Column]);
|
|
1150
1045
|
}
|
|
1151
1046
|
|
|
1152
1047
|
// backward compatibility, look at fks and keys using constraint name
|
|
@@ -232,7 +232,7 @@ export class AssetPseudoColumn extends ReferenceColumn {
|
|
|
232
232
|
return { isHTML: false, value: data[this._baseCol.name], unformatted: data[this._baseCol.name] };
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
-
if (this.hasWaitFor && !options.skipWaitFor) {
|
|
235
|
+
if ((this.hasWaitFor || this.hasCondition) && !options.skipWaitFor) {
|
|
236
236
|
return nullValue;
|
|
237
237
|
}
|
|
238
238
|
|
|
@@ -14,6 +14,7 @@ import { isObjectAndNotNull, isStringAndNotEmpty, isObjectAndKeyExists } from '@
|
|
|
14
14
|
import { fixedEncodeURIComponent } from '@isrd-isi-edu/ermrestjs/src/utils/value-utils';
|
|
15
15
|
import { renderMarkdown } from '@isrd-isi-edu/ermrestjs/src/utils/markdown-utils';
|
|
16
16
|
import { _annotations, _foreignKeyInputModes } from '@isrd-isi-edu/ermrestjs/src/utils/constants';
|
|
17
|
+
import { buildSelfTemplateVariables } from '@isrd-isi-edu/ermrestjs/src/utils/template-utils';
|
|
17
18
|
import { Reference } from '@isrd-isi-edu/ermrestjs/src/models/reference';
|
|
18
19
|
|
|
19
20
|
// legacy
|
|
@@ -323,8 +324,8 @@ export class ForeignKeyPseudoColumn extends ReferenceColumn {
|
|
|
323
324
|
unformatted: this._getNullValue(context!),
|
|
324
325
|
};
|
|
325
326
|
|
|
326
|
-
// if there's wait_for, this should return null.
|
|
327
|
-
if (this.hasWaitFor && !options.skipWaitFor) {
|
|
327
|
+
// if there's wait_for or async condition, this should return null.
|
|
328
|
+
if ((this.hasWaitFor || this.hasCondition) && !options.skipWaitFor) {
|
|
328
329
|
return nullValue;
|
|
329
330
|
}
|
|
330
331
|
|
|
@@ -347,12 +348,7 @@ export class ForeignKeyPseudoColumn extends ReferenceColumn {
|
|
|
347
348
|
const context = this._context;
|
|
348
349
|
|
|
349
350
|
if (this.display.sourceMarkdownPattern) {
|
|
350
|
-
|
|
351
|
-
if (mainTuple.linkedData[this.name]) {
|
|
352
|
-
selfTemplateVariables = {
|
|
353
|
-
$self: _getRowTemplateVariables(this.table, context, mainTuple.linkedData[this.name]),
|
|
354
|
-
};
|
|
355
|
-
}
|
|
351
|
+
const selfTemplateVariables = buildSelfTemplateVariables(this, mainTuple, columnValue);
|
|
356
352
|
|
|
357
353
|
const keyValues = {};
|
|
358
354
|
Object.assign(keyValues, templateVariables, selfTemplateVariables);
|
|
@@ -8,6 +8,7 @@ import { Reference, Tuple } from '@isrd-isi-edu/ermrestjs/src/models/reference';
|
|
|
8
8
|
// utils
|
|
9
9
|
import { renderMarkdown } from '@isrd-isi-edu/ermrestjs/src/utils/markdown-utils';
|
|
10
10
|
import { isStringAndNotEmpty } from '@isrd-isi-edu/ermrestjs/src/utils/type-utils';
|
|
11
|
+
import { buildSelfTemplateVariables } from '@isrd-isi-edu/ermrestjs/src/utils/template-utils';
|
|
11
12
|
|
|
12
13
|
// legacy
|
|
13
14
|
import { Key, Table } from '@isrd-isi-edu/ermrestjs/js/core';
|
|
@@ -95,7 +96,7 @@ export class KeyPseudoColumn extends ReferenceColumn {
|
|
|
95
96
|
unformatted: this._getNullValue(context!),
|
|
96
97
|
};
|
|
97
98
|
|
|
98
|
-
if (this.hasWaitFor && !options.skipWaitFor) {
|
|
99
|
+
if ((this.hasWaitFor || this.hasCondition) && !options.skipWaitFor) {
|
|
99
100
|
return nullValue;
|
|
100
101
|
}
|
|
101
102
|
|
|
@@ -118,9 +119,7 @@ export class KeyPseudoColumn extends ReferenceColumn {
|
|
|
118
119
|
const context = this._context;
|
|
119
120
|
|
|
120
121
|
if (this.display.sourceMarkdownPattern) {
|
|
121
|
-
const selfTemplateVariables =
|
|
122
|
-
$self: _getRowTemplateVariables(this.table, context, mainTuple.data),
|
|
123
|
-
};
|
|
122
|
+
const selfTemplateVariables = buildSelfTemplateVariables(this, mainTuple, columnValue);
|
|
124
123
|
|
|
125
124
|
const keyValues = {};
|
|
126
125
|
Object.assign(keyValues, templateVariables, selfTemplateVariables);
|
|
@@ -15,6 +15,7 @@ import { renderMarkdown } from '@isrd-isi-edu/ermrestjs/src/utils/markdown-utils
|
|
|
15
15
|
import { isDefinedAndNotNull, isObject, isObjectAndNotNull, isStringAndNotEmpty, verify } from '@isrd-isi-edu/ermrestjs/src/utils/type-utils';
|
|
16
16
|
import { fixedEncodeURIComponent } from '@isrd-isi-edu/ermrestjs/src/utils/value-utils';
|
|
17
17
|
import { processAggregateValue } from '@isrd-isi-edu/ermrestjs/src/utils/column-utils';
|
|
18
|
+
import { buildSelfTemplateVariables } from '@isrd-isi-edu/ermrestjs/src/utils/template-utils';
|
|
18
19
|
import {
|
|
19
20
|
_pseudoColAggregateFns,
|
|
20
21
|
_pseudoColEntityAggregateFns,
|
|
@@ -148,7 +149,7 @@ export class PseudoColumn extends ReferenceColumn {
|
|
|
148
149
|
return nullValue;
|
|
149
150
|
}
|
|
150
151
|
|
|
151
|
-
if (this.hasWaitFor && !options.skipWaitFor) {
|
|
152
|
+
if ((this.hasWaitFor || this.hasCondition) && !options.skipWaitFor) {
|
|
152
153
|
return nullValue;
|
|
153
154
|
}
|
|
154
155
|
|
|
@@ -193,42 +194,10 @@ export class PseudoColumn extends ReferenceColumn {
|
|
|
193
194
|
}
|
|
194
195
|
|
|
195
196
|
sourceFormatPresentation(templateVariables: any, columnValue: any, mainTuple: Tuple) {
|
|
196
|
-
const baseCol = this.baseColumn;
|
|
197
197
|
const context = this._context;
|
|
198
|
-
let selfTemplateVariables: any = {};
|
|
199
198
|
|
|
200
199
|
if (this.display.sourceMarkdownPattern) {
|
|
201
|
-
|
|
202
|
-
if (this.hasAggregate && columnValue) {
|
|
203
|
-
selfTemplateVariables = columnValue.templateVariables;
|
|
204
|
-
}
|
|
205
|
-
// all-outbound paths
|
|
206
|
-
else if (this.hasPath && this.isUnique) {
|
|
207
|
-
// use the linked data if exists
|
|
208
|
-
if (!mainTuple.linkedData[this.name]) {
|
|
209
|
-
selfTemplateVariables = {};
|
|
210
|
-
}
|
|
211
|
-
// scalar default
|
|
212
|
-
else if (!this.isEntityMode) {
|
|
213
|
-
selfTemplateVariables = {
|
|
214
|
-
$self: baseCol.formatvalue(mainTuple.linkedData[this.name][baseCol.name], context),
|
|
215
|
-
$_self: mainTuple.linkedData[this.name][baseCol.name],
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
// entity default
|
|
219
|
-
else {
|
|
220
|
-
selfTemplateVariables = {
|
|
221
|
-
$self: _getRowTemplateVariables(this.table, context, mainTuple.linkedData[this.name]),
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
// any other paths
|
|
226
|
-
else if (baseCol) {
|
|
227
|
-
selfTemplateVariables = {
|
|
228
|
-
$self: baseCol.formatvalue(mainTuple.data[baseCol.name], context),
|
|
229
|
-
$_self: mainTuple.data[baseCol.name],
|
|
230
|
-
};
|
|
231
|
-
}
|
|
200
|
+
const selfTemplateVariables = buildSelfTemplateVariables(this, mainTuple, columnValue);
|
|
232
201
|
|
|
233
202
|
const keyValues = {};
|
|
234
203
|
Object.assign(keyValues, templateVariables, selfTemplateVariables);
|
|
@@ -5,13 +5,17 @@ import { CommentType } from '@isrd-isi-edu/ermrestjs/src/models/comment';
|
|
|
5
5
|
import { DisplayName } from '@isrd-isi-edu/ermrestjs/src/models/display-name';
|
|
6
6
|
import { ColumnAggregateFn, ColumnGroupAggregateFn } from '@isrd-isi-edu/ermrestjs/src/models/reference-column';
|
|
7
7
|
import { Tuple, Reference } from '@isrd-isi-edu/ermrestjs/src/models/reference';
|
|
8
|
+
import type { ConditionDefinition } from '@isrd-isi-edu/ermrestjs/src/models/table-source-definitions';
|
|
8
9
|
|
|
9
10
|
// services
|
|
11
|
+
import $log from '@isrd-isi-edu/ermrestjs/src/services/logger';
|
|
10
12
|
|
|
11
13
|
// utils
|
|
12
14
|
import { renderMarkdown } from '@isrd-isi-edu/ermrestjs/src/utils/markdown-utils';
|
|
13
15
|
import { isObjectAndKeyExists, isObjectAndNotNull, isStringAndNotEmpty } from '@isrd-isi-edu/ermrestjs/src/utils/type-utils';
|
|
14
16
|
import { _annotations, _contexts } from '@isrd-isi-edu/ermrestjs/src/utils/constants';
|
|
17
|
+
import { buildSelfTemplateVariables } from '@isrd-isi-edu/ermrestjs/src/utils/template-utils';
|
|
18
|
+
import ActiveListCondition from '@isrd-isi-edu/ermrestjs/src/models/active-list-condition';
|
|
15
19
|
|
|
16
20
|
// legacy
|
|
17
21
|
import { Column, Table, Type } from '@isrd-isi-edu/ermrestjs/js/core';
|
|
@@ -122,6 +126,8 @@ export class ReferenceColumn {
|
|
|
122
126
|
protected _hasWaitFor?: boolean;
|
|
123
127
|
protected _hasWaitForAggregate?: boolean;
|
|
124
128
|
protected _waitFor?: ReferenceColumn[];
|
|
129
|
+
protected _hasCondition?: boolean;
|
|
130
|
+
protected _resolvedCondition?: ActiveListCondition | null;
|
|
125
131
|
/**
|
|
126
132
|
* the reference that this column is defined on
|
|
127
133
|
*/
|
|
@@ -441,6 +447,69 @@ export class ReferenceColumn {
|
|
|
441
447
|
return this._waitFor!;
|
|
442
448
|
}
|
|
443
449
|
|
|
450
|
+
/**
|
|
451
|
+
* Whether the column has an async condition that requires a secondary request.
|
|
452
|
+
* Only true in detailed context for conditions whose source has an inbound path or aggregate.
|
|
453
|
+
*/
|
|
454
|
+
get hasCondition(): boolean {
|
|
455
|
+
if (this._hasCondition === undefined) {
|
|
456
|
+
// triggers resolution
|
|
457
|
+
void this.resolvedCondition;
|
|
458
|
+
}
|
|
459
|
+
return this._hasCondition!;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* The resolved condition for this column, or null if no condition or not applicable.
|
|
464
|
+
*/
|
|
465
|
+
get resolvedCondition(): ActiveListCondition | null {
|
|
466
|
+
if (this._resolvedCondition === undefined) {
|
|
467
|
+
this._resolvedCondition = this._resolveCondition();
|
|
468
|
+
this._hasCondition = this._resolvedCondition?.isAsync ?? false;
|
|
469
|
+
}
|
|
470
|
+
return this._resolvedCondition;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Resolve the condition definition and source for this column.
|
|
475
|
+
*/
|
|
476
|
+
protected _resolveCondition(): ActiveListCondition | null {
|
|
477
|
+
// only applicable in detailed context
|
|
478
|
+
if (this._context !== _contexts.DETAILED) {
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (!this.sourceObjectWrapper) {
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// get condition def: inline condition or condition_key
|
|
487
|
+
let condDef: ConditionDefinition | undefined, condKey: string | undefined;
|
|
488
|
+
const sourceObject = this.sourceObjectWrapper.sourceObject;
|
|
489
|
+
if (isStringAndNotEmpty(sourceObject.condition_key)) {
|
|
490
|
+
condKey = sourceObject.condition_key as string;
|
|
491
|
+
condDef = this._currentTable.sourceDefinitions.getCondition(condKey);
|
|
492
|
+
if (!condDef) {
|
|
493
|
+
$log.info('condition_key `' + condKey + '` not found in source-definitions conditions.');
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
} else if (isObjectAndNotNull(sourceObject.condition)) {
|
|
497
|
+
condDef = sourceObject.condition as Record<string, unknown>;
|
|
498
|
+
} else if (!condDef) {
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
let condition: ActiveListCondition;
|
|
503
|
+
try {
|
|
504
|
+
condition = new ActiveListCondition(condDef, this._baseReference, this._mainTuple, condKey);
|
|
505
|
+
} catch (e: unknown) {
|
|
506
|
+
$log.warn(e instanceof Error ? e.message : String(e));
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return condition;
|
|
511
|
+
}
|
|
512
|
+
|
|
444
513
|
/**
|
|
445
514
|
* Formats a value corresponding to this reference-column definition.
|
|
446
515
|
*/
|
|
@@ -465,7 +534,13 @@ export class ReferenceColumn {
|
|
|
465
534
|
context = this._context;
|
|
466
535
|
}
|
|
467
536
|
|
|
468
|
-
//
|
|
537
|
+
// wait_for placeholder: the column depends on data that isn't ready, so
|
|
538
|
+
// return a null placeholder. Note: hasCondition is intentionally NOT in
|
|
539
|
+
// this check — the column's value is independent of the condition; the
|
|
540
|
+
// condition only controls *visibility* (chaise's `conditionHide` flag).
|
|
541
|
+
// Returning null here for an async-conditioned local column would
|
|
542
|
+
// permanently blank it, since there's no secondary fetch to flip the
|
|
543
|
+
// value back in chaise.
|
|
469
544
|
if (this.hasWaitFor && !options.skipWaitFor) {
|
|
470
545
|
return {
|
|
471
546
|
isHTML: false,
|
|
@@ -608,17 +683,13 @@ export class ReferenceColumn {
|
|
|
608
683
|
}
|
|
609
684
|
|
|
610
685
|
sourceFormatPresentation(templateVariables: any, columnValue: any, mainTuple: Tuple): any {
|
|
611
|
-
const baseCol = this.baseColumns[0];
|
|
612
686
|
const context = this._context;
|
|
613
687
|
|
|
614
688
|
if (this.display.sourceMarkdownPattern) {
|
|
615
689
|
let selfTemplateVariables = {};
|
|
616
|
-
// children pseudo-
|
|
617
|
-
if (!this.isPseudo
|
|
618
|
-
selfTemplateVariables =
|
|
619
|
-
$self: baseCol.formatvalue(mainTuple.data[baseCol.name], context),
|
|
620
|
-
$_self: mainTuple.data[baseCol.name],
|
|
621
|
-
};
|
|
690
|
+
// children pseudo-columns build $self in their own override via buildSelfTemplateVariables
|
|
691
|
+
if (!this.isPseudo) {
|
|
692
|
+
selfTemplateVariables = buildSelfTemplateVariables(this, mainTuple, columnValue);
|
|
622
693
|
}
|
|
623
694
|
|
|
624
695
|
const keyValues = {};
|