@isrd-isi-edu/ermrestjs 2.0.1 → 2.1.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/README.md +2 -2
- package/js/core.js +2 -2
- package/js/parser.js +6 -5
- package/js/utils/helpers.js +4 -0
- package/js/utils/pseudocolumn_helpers.js +49 -9
- package/package.json +12 -7
- package/src/models/reference/reference.ts +64 -16
- package/src/models/reference-column/facet-column.ts +25 -7
- package/src/models/reference-column/facet-group.ts +76 -0
- package/src/models/reference-column/index.ts +1 -0
- package/src/models/source-object-wrapper.ts +98 -48
- package/src/models/table-source-definitions.ts +3 -1
- package/src/utils/constants.ts +3 -0
- package/src/utils/reference-utils.ts +124 -79
- package/vite.config.mts +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ERMrestJS [](https://github.com/informatics-isi-edu/ermrestjs/actions?query=workflow%3A%22ERmrestJS+tests%22+branch%3Amaster)
|
|
2
2
|
|
|
3
|
-
ERMrestJS is a javascript client library for interacting with the [ERMrest](http://github.com/informatics-isi-edu/ermrest) service. It provides higher-level, simplified application programming interfaces (APIs) for working with the Entity-Relationship concepts native to ERMrest.
|
|
3
|
+
ERMrestJS is a javascript client library for interacting with the [ERMrest](http://github.com/informatics-isi-edu/ermrest) service. It provides higher-level, simplified application programming interfaces (APIs) for working with the Entity-Relationship concepts native to ERMrest.
|
|
4
4
|
|
|
5
5
|
The library has been extended to also support [Hatrac](https://github.com/informatics-isi-edu/hatrac) (an object store service), and [deriva-web export](https://github.com/informatics-isi-edu/deriva-web). ERMrestJS is a part of [Deriva Platform](http://isrd.isi.edu/deriva).
|
|
6
6
|
|
|
@@ -29,7 +29,7 @@ Documents are categorized based on their audience.
|
|
|
29
29
|
|
|
30
30
|
When developing new code for ERMrestJS, please make sure you're following these steps:
|
|
31
31
|
|
|
32
|
-
1. create a new branch and make your updates to the code in the branch (
|
|
32
|
+
1. create a new branch and make your updates to the code in the branch (follow DO NOT change `master` branch directly and ensure your commit messages follow [the convetions described here](docs/dev-docs/dev-guide.md#commit-message-conventions));
|
|
33
33
|
2. do your own quality assurance;
|
|
34
34
|
3. update all relevant documentation (Please refer to [this page](docs/dev-docs/update-docs.md) for more information);
|
|
35
35
|
4. update the unit tests (if applicable);
|
package/js/core.js
CHANGED
|
@@ -481,7 +481,7 @@ import {
|
|
|
481
481
|
// load the catalog (or use the one that is cached)
|
|
482
482
|
this._get().then((response) => {
|
|
483
483
|
this.snaptime = response.snaptime;
|
|
484
|
-
|
|
484
|
+
|
|
485
485
|
let versionCorrected = false;
|
|
486
486
|
if (isStringAndNotEmpty(this.version) && this.version !== this.snaptime) {
|
|
487
487
|
this.version = this.snaptime;
|
|
@@ -4351,7 +4351,7 @@ import {
|
|
|
4351
4351
|
var definitions = this._table.sourceDefinitions, wm = _warningMessages;
|
|
4352
4352
|
var logErr = function (bool, message, i) {
|
|
4353
4353
|
if (bool) {
|
|
4354
|
-
$log.info(
|
|
4354
|
+
$log.info(`vis-fk for table '${self._table.name}' in context '${context}' at index '${i}':`);
|
|
4355
4355
|
$log.info(message);
|
|
4356
4356
|
}
|
|
4357
4357
|
return bool;
|
package/js/parser.js
CHANGED
|
@@ -858,11 +858,12 @@ import HistoryService from '@isrd-isi-edu/ermrestjs/src/services/history';
|
|
|
858
858
|
|
|
859
859
|
/**
|
|
860
860
|
* The datetime iso string representation of the catalog version
|
|
861
|
-
* @returns {String} the iso string or null if version is not specified
|
|
861
|
+
* @returns {String|null} the iso string or null if version is not specified
|
|
862
862
|
*/
|
|
863
863
|
get versionAsISOString() {
|
|
864
864
|
if (this._versionAsISOString === undefined) {
|
|
865
|
-
this._versionAsISOString = HistoryService.snapshotToDatetimeISO(this._version,
|
|
865
|
+
this._versionAsISOString = HistoryService.snapshotToDatetimeISO(this._version, true);
|
|
866
|
+
if (this._versionAsISOString === '') this._versionAsISOString = null;
|
|
866
867
|
}
|
|
867
868
|
return this._versionAsISOString;
|
|
868
869
|
},
|
|
@@ -972,7 +973,7 @@ import HistoryService from '@isrd-isi-edu/ermrestjs/src/services/history';
|
|
|
972
973
|
|
|
973
974
|
/**
|
|
974
975
|
* filter is converted to the last join table (if uri has join)
|
|
975
|
-
* @returns {ParsedFilter} undefined if there is no filter
|
|
976
|
+
* @returns {ParsedFilter|undefined} undefined if there is no filter
|
|
976
977
|
*/
|
|
977
978
|
get filter() {
|
|
978
979
|
return this.lastPathPart ? this.lastPathPart.filter : undefined;
|
|
@@ -1059,7 +1060,7 @@ import HistoryService from '@isrd-isi-edu/ermrestjs/src/services/history';
|
|
|
1059
1060
|
|
|
1060
1061
|
/**
|
|
1061
1062
|
* facets object of the last path part
|
|
1062
|
-
* @type {ParsedFacets} facets object
|
|
1063
|
+
* @type {ParsedFacets|undefined} facets object
|
|
1063
1064
|
*/
|
|
1064
1065
|
get facets() {
|
|
1065
1066
|
return this.lastPathPart ? this.lastPathPart.facets : undefined;
|
|
@@ -1084,7 +1085,7 @@ import HistoryService from '@isrd-isi-edu/ermrestjs/src/services/history';
|
|
|
1084
1085
|
|
|
1085
1086
|
/**
|
|
1086
1087
|
* the custom facet of the last path part
|
|
1087
|
-
* @type {CustomFacets}
|
|
1088
|
+
* @type {CustomFacets|undefined}
|
|
1088
1089
|
*/
|
|
1089
1090
|
get customFacets() {
|
|
1090
1091
|
return this.lastPathPart ? this.lastPathPart.customFacets : undefined;
|
package/js/utils/helpers.js
CHANGED
|
@@ -642,6 +642,10 @@ import AuthnService from '@isrd-isi-edu/ermrestjs/src/services/authn';
|
|
|
642
642
|
|
|
643
643
|
/**
|
|
644
644
|
* Given the source object and default comment props, will return the comment that should be used.
|
|
645
|
+
* @param {any} sourceObject the object that might have comment props
|
|
646
|
+
* @param {string=} defaultComment the default comment that should be used if sourceObject doesn't have comment
|
|
647
|
+
* @param {boolean=} defaultCommentRenderMd the default comment_render_markdown that should be used if sourceObject doesn't have comment_render_markdown
|
|
648
|
+
* @param {string=} defaultDisplayMode the default comment_display that should be used if sourceObject doesn't have comment_display
|
|
645
649
|
* @returns {CommentType}
|
|
646
650
|
* @private
|
|
647
651
|
*/
|
|
@@ -894,24 +894,61 @@ import { parse, _convertSearchTermToFilter } from '@isrd-isi-edu/ermrestjs/js/pa
|
|
|
894
894
|
},
|
|
895
895
|
|
|
896
896
|
/**
|
|
897
|
-
* Given a source object
|
|
898
|
-
*
|
|
897
|
+
* Given a source definition object, it will return a SourceObjectWrapper that can be used as a facet object.
|
|
898
|
+
* It will return null if the source definition is not supported as a facet.
|
|
899
|
+
*
|
|
900
|
+
* NOTE:
|
|
901
|
+
* - this function will remove any filter defined in the source definition if hasFilterOrFacet is true.
|
|
902
|
+
* - might throw an error if the source definition is invalid.
|
|
903
|
+
*
|
|
904
|
+
*
|
|
905
|
+
* @param {any} obj the source definition object
|
|
906
|
+
* @param {Table} table the table that this source definition is based on
|
|
907
|
+
* @param {boolean} hasFilterOrFacet whether the url has any filter or facet defined. If this is true, we will remove any filter defined in the source definition.
|
|
908
|
+
*
|
|
909
|
+
* @throws {Error} if the source definition is invalid for facet
|
|
910
|
+
*
|
|
911
|
+
* @returns {SourceObjectWrapper} the source object wrapper that can be used as a facet object.
|
|
899
912
|
*/
|
|
900
|
-
|
|
901
|
-
|
|
913
|
+
sourceDefToFacetObjectWrapper: function (obj, table, hasFilterOrFacet) {
|
|
914
|
+
let wrapper;
|
|
915
|
+
// if both source and sourcekey are defined, ignore the source and use sourcekey
|
|
916
|
+
if (obj.sourcekey) {
|
|
917
|
+
const sd = table.sourceDefinitions.getSource(obj.sourcekey);
|
|
918
|
+
if (!sd) {
|
|
919
|
+
throw new Error(_facetingErrors.invalidSourcekey);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
wrapper = sd.clone(obj, table, true);
|
|
923
|
+
} else {
|
|
924
|
+
wrapper = new SourceObjectWrapper(obj, table, true);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
const col = wrapper.column;
|
|
902
928
|
|
|
903
929
|
// aggregate is not supported
|
|
904
|
-
if (
|
|
905
|
-
|
|
930
|
+
if (wrapper.hasAggregate) {
|
|
931
|
+
throw new Error(_facetingErrors.aggregateFnNowtAllowed);
|
|
906
932
|
}
|
|
907
933
|
|
|
908
934
|
// column type array is not supported
|
|
909
935
|
if (col.type.isArray) {
|
|
910
|
-
|
|
936
|
+
throw new Error(_facetingErrors.arrayColumnTypeNotSupported);
|
|
911
937
|
}
|
|
912
938
|
|
|
913
939
|
// check the column type
|
|
914
|
-
|
|
940
|
+
if (_facetUnsupportedTypes.indexOf(col.type.name) !== -1) {
|
|
941
|
+
throw new Error(`Facet of column type '${col.type.name}' is not supported.`);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// if we have filters in the url, we will get the filters only from url
|
|
945
|
+
if (hasFilterOrFacet) {
|
|
946
|
+
delete wrapper.sourceObject.not_null;
|
|
947
|
+
delete wrapper.sourceObject.choices;
|
|
948
|
+
delete wrapper.sourceObject.search;
|
|
949
|
+
delete wrapper.sourceObject.ranges;
|
|
950
|
+
}
|
|
951
|
+
return wrapper;
|
|
915
952
|
},
|
|
916
953
|
|
|
917
954
|
/**
|
|
@@ -1003,7 +1040,10 @@ import { parse, _convertSearchTermToFilter } from '@isrd-isi-edu/ermrestjs/js/pa
|
|
|
1003
1040
|
* NOTE: facetColumns MUST be only used in COMPACT_SELECT context
|
|
1004
1041
|
* It doesn't feel right that I am doing contextualization in here,
|
|
1005
1042
|
* it's something that should be in client.
|
|
1006
|
-
* @
|
|
1043
|
+
* @param {SourceObjectWrapper} facetObjectWrapper the facet object
|
|
1044
|
+
* @param {boolean} usedAnnotation the annotation that was used to create this facet (if any)
|
|
1045
|
+
* @param {Table} table the current table that we want to make sure the facetObject is valid for.
|
|
1046
|
+
* @returns {boolean} whether the facetObjectWrapper is valid for this table.
|
|
1007
1047
|
*/
|
|
1008
1048
|
checkForAlternative: function (facetObjectWrapper, usedAnnotation, table) {
|
|
1009
1049
|
var currTable = facetObjectWrapper.column.table;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@isrd-isi-edu/ermrestjs",
|
|
3
3
|
"description": "ERMrest client library in JavaScript",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.1.1",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">= 20.0.0",
|
|
@@ -19,12 +19,14 @@
|
|
|
19
19
|
],
|
|
20
20
|
"scripts": {
|
|
21
21
|
"build": "tsc && vite build",
|
|
22
|
+
"watch": "tsc --watch && vite build --watch",
|
|
22
23
|
"pretest": "make deps-test && make dist-w-deps",
|
|
23
24
|
"deploy": "make deploy",
|
|
24
25
|
"test": "make test",
|
|
25
26
|
"lint": "eslint src js --quiet",
|
|
26
27
|
"lint-w-warn": "eslint src js",
|
|
27
|
-
"format": "prettier --write src"
|
|
28
|
+
"format": "prettier --write src",
|
|
29
|
+
"prepare": "husky"
|
|
28
30
|
},
|
|
29
31
|
"repository": {
|
|
30
32
|
"type": "git",
|
|
@@ -43,7 +45,7 @@
|
|
|
43
45
|
"dependencies": {
|
|
44
46
|
"@types/markdown-it": "^14.1.2",
|
|
45
47
|
"@types/q": "^1.5.8",
|
|
46
|
-
"axios": "1.
|
|
48
|
+
"axios": "1.13.2",
|
|
47
49
|
"handlebars": "4.7.8",
|
|
48
50
|
"lz-string": "^1.5.0",
|
|
49
51
|
"markdown-it": "12.3.2",
|
|
@@ -52,25 +54,28 @@
|
|
|
52
54
|
"mustache": "x",
|
|
53
55
|
"q": "1.5.1",
|
|
54
56
|
"spark-md5": "^3.0.0",
|
|
55
|
-
"terser": "^5.
|
|
56
|
-
"typescript": "~5.
|
|
57
|
+
"terser": "^5.44.1",
|
|
58
|
+
"typescript": "~5.9.3",
|
|
57
59
|
"vite": "^6.4.1",
|
|
58
60
|
"vite-plugin-compression2": "^2.2.1"
|
|
59
61
|
},
|
|
60
62
|
"devDependencies": {
|
|
63
|
+
"@commitlint/cli": "^20.2.0",
|
|
64
|
+
"@commitlint/config-conventional": "^20.2.0",
|
|
61
65
|
"@eslint/js": "^9.36.0",
|
|
62
66
|
"@isrd-isi-edu/ermrest-data-utils": "0.0.5",
|
|
67
|
+
"@semantic-release/git": "^10.0.1",
|
|
63
68
|
"@types/node": "^24.6.1",
|
|
64
69
|
"eslint": "^9.36.0",
|
|
65
70
|
"eslint-config-prettier": "^10.1.8",
|
|
66
71
|
"eslint-plugin-prettier": "^5.5.4",
|
|
67
|
-
"file-api": "^0.10.4",
|
|
68
72
|
"globals": "^16.4.0",
|
|
73
|
+
"husky": "^9.1.7",
|
|
69
74
|
"jasmine": "2.5.3",
|
|
70
75
|
"jasmine-expect": "3.7.1",
|
|
71
76
|
"jasmine-spec-reporter": "2.5.0",
|
|
72
77
|
"nock": "13.5.4",
|
|
73
|
-
"prettier": "^3.
|
|
78
|
+
"prettier": "^3.7.4",
|
|
74
79
|
"require-reload": "^0.2.2",
|
|
75
80
|
"rollup-plugin-visualizer": "^6.0.3",
|
|
76
81
|
"typescript-eslint": "^8.45.0",
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
KeyPseudoColumn,
|
|
19
19
|
AssetPseudoColumn,
|
|
20
20
|
InboundForeignKeyPseudoColumn,
|
|
21
|
+
type FacetGroup,
|
|
21
22
|
type PseudoColumn,
|
|
22
23
|
type ColumnAggregateFn,
|
|
23
24
|
} from '@isrd-isi-edu/ermrestjs/src/models/reference-column';
|
|
@@ -124,8 +125,6 @@ export const resolve = async (uri: string, contextHeaderParams?: any): Promise<R
|
|
|
124
125
|
// It should have been taken care by outer try but did not work
|
|
125
126
|
const loc = parse(uri);
|
|
126
127
|
|
|
127
|
-
console.log(loc.catalog);
|
|
128
|
-
|
|
129
128
|
const server = ermrestFactory.getServer(loc.service, contextHeaderParams);
|
|
130
129
|
|
|
131
130
|
const catalog = await server.catalogs.get(loc.catalog);
|
|
@@ -239,6 +238,7 @@ export class Reference {
|
|
|
239
238
|
private _referenceColumns?: Array<VisibleColumn>;
|
|
240
239
|
private _related?: Array<RelatedReference>;
|
|
241
240
|
private _facetColumns?: Array<FacetColumn>;
|
|
241
|
+
private _facetColumnsStructure?: Array<number | FacetGroup>;
|
|
242
242
|
private _activeList?: any;
|
|
243
243
|
private _citation?: Citation | null;
|
|
244
244
|
private _googleDatasetMetadata?: GoogleDatasetMetadata | null;
|
|
@@ -614,19 +614,37 @@ export class Reference {
|
|
|
614
614
|
}
|
|
615
615
|
|
|
616
616
|
/**
|
|
617
|
-
*
|
|
618
|
-
*
|
|
617
|
+
* Returns the list of facets.
|
|
618
|
+
*
|
|
619
|
+
* NOTE this will not map the entity choice pickers, make sure to call "generateFacetColumns" first.
|
|
619
620
|
*/
|
|
620
621
|
get facetColumns(): FacetColumn[] {
|
|
621
622
|
if (this._facetColumns === undefined) {
|
|
622
623
|
const res = generateFacetColumns(this, true);
|
|
623
624
|
if (!(res instanceof Promise)) {
|
|
624
625
|
this._facetColumns = res.facetColumns;
|
|
626
|
+
this._facetColumnsStructure = res.facetColumnsStructure;
|
|
625
627
|
}
|
|
626
628
|
}
|
|
627
629
|
return this._facetColumns!;
|
|
628
630
|
}
|
|
629
631
|
|
|
632
|
+
/**
|
|
633
|
+
* An array of numbers and FacetGroup objects that represent the structure of facet columns.
|
|
634
|
+
* Each number indicates the number of facet columns in that group, while a FacetGroup object
|
|
635
|
+
* represents a nested group of facets.
|
|
636
|
+
*/
|
|
637
|
+
get facetColumnsStructure(): Array<number | FacetGroup> {
|
|
638
|
+
if (this._facetColumnsStructure === undefined) {
|
|
639
|
+
const res = generateFacetColumns(this, true);
|
|
640
|
+
if (!(res instanceof Promise)) {
|
|
641
|
+
this._facetColumns = res.facetColumns;
|
|
642
|
+
this._facetColumnsStructure = res.facetColumnsStructure;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
return this._facetColumnsStructure!;
|
|
646
|
+
}
|
|
647
|
+
|
|
630
648
|
/**
|
|
631
649
|
* Returns the facets that should be represented to the user.
|
|
632
650
|
* It will return a promise resolved with the following object:
|
|
@@ -663,14 +681,25 @@ export class Reference {
|
|
|
663
681
|
* });
|
|
664
682
|
* ```
|
|
665
683
|
*/
|
|
666
|
-
generateFacetColumns(): Promise<{
|
|
684
|
+
generateFacetColumns(): Promise<{
|
|
685
|
+
facetColumns: FacetColumn[];
|
|
686
|
+
facetColumnsStructure: Array<number | FacetGroup>;
|
|
687
|
+
issues: UnsupportedFilters | null;
|
|
688
|
+
}> {
|
|
667
689
|
return new Promise((resolve, reject) => {
|
|
668
|
-
const p = generateFacetColumns(this, false) as Promise<{
|
|
690
|
+
const p = generateFacetColumns(this, false) as Promise<{
|
|
691
|
+
facetColumns: FacetColumn[];
|
|
692
|
+
issues: UnsupportedFilters | null;
|
|
693
|
+
facetColumnsStructure: Array<number | FacetGroup>;
|
|
694
|
+
}>;
|
|
695
|
+
|
|
669
696
|
p.then((res) => {
|
|
670
697
|
this._facetColumns = res.facetColumns;
|
|
698
|
+
this._facetColumnsStructure = res.facetColumnsStructure;
|
|
671
699
|
resolve(res);
|
|
672
700
|
}).catch((err) => {
|
|
673
701
|
this._facetColumns = [];
|
|
702
|
+
this._facetColumnsStructure = [];
|
|
674
703
|
reject(err);
|
|
675
704
|
});
|
|
676
705
|
});
|
|
@@ -680,8 +709,9 @@ export class Reference {
|
|
|
680
709
|
* This is only added so _applyFilters in facet-column can use it.
|
|
681
710
|
* SHOULD NOT be used outside of this library.
|
|
682
711
|
*/
|
|
683
|
-
manuallySetFacetColumns(facetCols: FacetColumn[]) {
|
|
712
|
+
manuallySetFacetColumns(facetCols: FacetColumn[], facetColsStructure: Array<number | FacetGroup>) {
|
|
684
713
|
this._facetColumns = facetCols;
|
|
714
|
+
this._facetColumnsStructure = facetColsStructure;
|
|
685
715
|
}
|
|
686
716
|
|
|
687
717
|
/**
|
|
@@ -1195,11 +1225,11 @@ export class Reference {
|
|
|
1195
1225
|
|
|
1196
1226
|
/**
|
|
1197
1227
|
* Remove all the filters, facets, and custom-facets from the reference
|
|
1198
|
-
* @param
|
|
1199
|
-
* @param
|
|
1200
|
-
* @param
|
|
1228
|
+
* @param sameFilter By default we're removing filters, if this is true filters won't be changed.
|
|
1229
|
+
* @param sameCustomFacet By default we're removing custom-facets, if this is true custom-facets won't be changed.
|
|
1230
|
+
* @param sameFacet By default we're removing facets, if this is true facets won't be changed.
|
|
1201
1231
|
*
|
|
1202
|
-
* @return
|
|
1232
|
+
* @return A reference without facet filters
|
|
1203
1233
|
*/
|
|
1204
1234
|
removeAllFacetFilters(sameFilter?: boolean, sameCustomFacet?: boolean, sameFacet?: boolean) {
|
|
1205
1235
|
verify(!(sameFilter && sameCustomFacet && sameFacet), 'at least one of the options must be false.');
|
|
@@ -1214,10 +1244,16 @@ export class Reference {
|
|
|
1214
1244
|
// compute that logic again, those facets will disappear.
|
|
1215
1245
|
newReference._facetColumns = [];
|
|
1216
1246
|
this.facetColumns.forEach((fc) => {
|
|
1217
|
-
newReference._facetColumns!.push(
|
|
1247
|
+
newReference._facetColumns!.push(
|
|
1248
|
+
new FacetColumn(newReference, fc.index, fc.sourceObjectWrapper, fc.groupIndex, sameFacet ? fc.filters.slice() : []),
|
|
1249
|
+
);
|
|
1250
|
+
});
|
|
1251
|
+
newReference._facetColumnsStructure = [];
|
|
1252
|
+
this.facetColumnsStructure.forEach((structure) => {
|
|
1253
|
+
newReference._facetColumnsStructure!.push(typeof structure === 'number' ? structure : structure.copy(newReference));
|
|
1218
1254
|
});
|
|
1219
1255
|
|
|
1220
|
-
// update the location
|
|
1256
|
+
// update the location object
|
|
1221
1257
|
newReference._location = this._location._clone(newReference);
|
|
1222
1258
|
newReference._location.beforeObject = null;
|
|
1223
1259
|
newReference._location.afterObject = null;
|
|
@@ -1802,7 +1838,7 @@ export class Reference {
|
|
|
1802
1838
|
* b) A single term with space using ""
|
|
1803
1839
|
* c) use space for conjunction of terms
|
|
1804
1840
|
*/
|
|
1805
|
-
search(term
|
|
1841
|
+
search(term?: string | null) {
|
|
1806
1842
|
if (term) {
|
|
1807
1843
|
if (typeof term === 'string') term = term.trim();
|
|
1808
1844
|
else throw new InvalidInputError('Invalid input. Seach expects a string.');
|
|
@@ -1822,7 +1858,13 @@ export class Reference {
|
|
|
1822
1858
|
if (this._facetColumns !== undefined) {
|
|
1823
1859
|
newReference._facetColumns = [];
|
|
1824
1860
|
this.facetColumns.forEach((fc) => {
|
|
1825
|
-
newReference._facetColumns!.push(new FacetColumn(newReference, fc.index, fc.sourceObjectWrapper, fc.filters.slice()));
|
|
1861
|
+
newReference._facetColumns!.push(new FacetColumn(newReference, fc.index, fc.sourceObjectWrapper, fc.groupIndex, fc.filters.slice()));
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
if (this._facetColumnsStructure !== undefined) {
|
|
1865
|
+
newReference._facetColumnsStructure = [];
|
|
1866
|
+
this.facetColumnsStructure.forEach((structure) => {
|
|
1867
|
+
newReference._facetColumnsStructure!.push(typeof structure === 'number' ? structure : structure.copy(newReference));
|
|
1826
1868
|
});
|
|
1827
1869
|
}
|
|
1828
1870
|
|
|
@@ -1859,7 +1901,13 @@ export class Reference {
|
|
|
1859
1901
|
if (this._facetColumns !== undefined) {
|
|
1860
1902
|
newReference._facetColumns = [];
|
|
1861
1903
|
this.facetColumns.forEach((fc) => {
|
|
1862
|
-
newReference._facetColumns!.push(new FacetColumn(newReference, fc.index, fc.sourceObjectWrapper, fc.filters.slice()));
|
|
1904
|
+
newReference._facetColumns!.push(new FacetColumn(newReference, fc.index, fc.sourceObjectWrapper, fc.groupIndex, fc.filters.slice()));
|
|
1905
|
+
});
|
|
1906
|
+
}
|
|
1907
|
+
if (this._facetColumnsStructure !== undefined) {
|
|
1908
|
+
newReference._facetColumnsStructure = [];
|
|
1909
|
+
this.facetColumnsStructure.forEach((structure) => {
|
|
1910
|
+
newReference._facetColumnsStructure!.push(typeof structure === 'number' ? structure : structure.copy(newReference));
|
|
1863
1911
|
});
|
|
1864
1912
|
}
|
|
1865
1913
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// models
|
|
2
2
|
import SourceObjectWrapper from '@isrd-isi-edu/ermrestjs/src/models/source-object-wrapper';
|
|
3
3
|
import type SourceObjectNode from '@isrd-isi-edu/ermrestjs/src/models/source-object-node';
|
|
4
|
-
import { ReferenceColumn } from '@isrd-isi-edu/ermrestjs/src/models/reference-column';
|
|
4
|
+
import { FacetGroup, ReferenceColumn } from '@isrd-isi-edu/ermrestjs/src/models/reference-column';
|
|
5
5
|
import type { CommentType } from '@isrd-isi-edu/ermrestjs/src/models/comment';
|
|
6
6
|
import type { DisplayName } from '@isrd-isi-edu/ermrestjs/src/models/display-name';
|
|
7
7
|
import { type Tuple, Reference } from '@isrd-isi-edu/ermrestjs/src/models/reference';
|
|
@@ -163,7 +163,8 @@ class NotNullFacetFilter {
|
|
|
163
163
|
|
|
164
164
|
/**
|
|
165
165
|
* @param {Reference} reference the reference that this FacetColumn blongs to.
|
|
166
|
-
* @param {
|
|
166
|
+
* @param {number} index The index of this FacetColumn in the list of facetColumns
|
|
167
|
+
* @param {number} structureIndex The index of this FacetColumn in the structure array
|
|
167
168
|
* @param {SourceObjectWrapper} facetObjectWrapper The filter object that this FacetColumn will be created based on
|
|
168
169
|
* @param {?FacetFilter[]} filters Array of filters
|
|
169
170
|
*/
|
|
@@ -180,10 +181,14 @@ export class FacetColumn {
|
|
|
180
181
|
|
|
181
182
|
/**
|
|
182
183
|
* The index of facetColumn in the list of facetColumns
|
|
183
|
-
* NOTE: Might not be needed
|
|
184
184
|
*/
|
|
185
185
|
public index: number;
|
|
186
186
|
|
|
187
|
+
/**
|
|
188
|
+
* if part of the group, the index of that group in the facetColumnsStructure array
|
|
189
|
+
*/
|
|
190
|
+
public groupIndex?: number;
|
|
191
|
+
|
|
187
192
|
/**
|
|
188
193
|
* A valid data-source path
|
|
189
194
|
* NOTE: we're not validating this data-source, we assume that this is valid.
|
|
@@ -245,10 +250,17 @@ export class FacetColumn {
|
|
|
245
250
|
private _choiceFilters?: ChoiceFacetFilter[];
|
|
246
251
|
private _rangeFilters?: RangeFacetFilter[];
|
|
247
252
|
|
|
248
|
-
constructor(
|
|
253
|
+
constructor(
|
|
254
|
+
reference: Reference,
|
|
255
|
+
index: number,
|
|
256
|
+
facetObjectWrapper: SourceObjectWrapper,
|
|
257
|
+
groupIndex?: number,
|
|
258
|
+
filters?: Array<FacetFilter | NotNullFacetFilter>,
|
|
259
|
+
) {
|
|
249
260
|
this._column = facetObjectWrapper.column!;
|
|
250
261
|
this.reference = reference;
|
|
251
262
|
this.index = index;
|
|
263
|
+
this.groupIndex = groupIndex;
|
|
252
264
|
this.dataSource = facetObjectWrapper.sourceObject.source;
|
|
253
265
|
this.compressedDataSource = _compressSource(this.dataSource);
|
|
254
266
|
|
|
@@ -1326,6 +1338,7 @@ export class FacetColumn {
|
|
|
1326
1338
|
const loc = this.reference.location;
|
|
1327
1339
|
const newReference = this.reference.copy();
|
|
1328
1340
|
const facets: FacetColumn[] = [];
|
|
1341
|
+
const facetColsStructure: Array<FacetGroup | number> = [];
|
|
1329
1342
|
|
|
1330
1343
|
// create a new FacetColumn so it doesn't reference to the current FacetColum
|
|
1331
1344
|
// TODO can be refactored
|
|
@@ -1337,9 +1350,9 @@ export class FacetColumn {
|
|
|
1337
1350
|
let newFc: FacetColumn;
|
|
1338
1351
|
this.reference.facetColumns.forEach((fc: FacetColumn) => {
|
|
1339
1352
|
if (fc.index !== this.index) {
|
|
1340
|
-
newFc = new FacetColumn(newReference, fc.index, fc.sourceObjectWrapper, fc.filters.slice() as FacetFilter[]);
|
|
1353
|
+
newFc = new FacetColumn(newReference, fc.index, fc.sourceObjectWrapper, fc.groupIndex, fc.filters.slice() as FacetFilter[]);
|
|
1341
1354
|
} else {
|
|
1342
|
-
newFc = new FacetColumn(newReference, this.index, this.sourceObjectWrapper, filters as FacetFilter[]);
|
|
1355
|
+
newFc = new FacetColumn(newReference, this.index, this.sourceObjectWrapper, this.groupIndex, filters as FacetFilter[]);
|
|
1343
1356
|
}
|
|
1344
1357
|
|
|
1345
1358
|
facets.push(newFc);
|
|
@@ -1349,7 +1362,12 @@ export class FacetColumn {
|
|
|
1349
1362
|
}
|
|
1350
1363
|
});
|
|
1351
1364
|
|
|
1352
|
-
|
|
1365
|
+
// recreate the facetColumnsStructure so we don't have to recompute the whole thing.
|
|
1366
|
+
this.reference.facetColumnsStructure.forEach((structure) => {
|
|
1367
|
+
facetColsStructure.push(typeof structure === 'number' ? structure : structure.copy(newReference));
|
|
1368
|
+
});
|
|
1369
|
+
|
|
1370
|
+
newReference.manuallySetFacetColumns(facets, facetColsStructure);
|
|
1353
1371
|
newReference.setLocation(this.reference.location._clone(newReference));
|
|
1354
1372
|
newReference.location.beforeObject = null;
|
|
1355
1373
|
newReference.location.afterObject = null;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { DisplayName } from '@isrd-isi-edu/ermrestjs/src/models/display-name';
|
|
2
|
+
import type { CommentType } from '@isrd-isi-edu/ermrestjs/src/models/comment';
|
|
3
|
+
import type { FacetObjectGroupWrapper } from '@isrd-isi-edu/ermrestjs/src/models/source-object-wrapper';
|
|
4
|
+
import type { Reference } from '@isrd-isi-edu/ermrestjs/src/models/reference';
|
|
5
|
+
|
|
6
|
+
import { _processSourceObjectComment } from '@isrd-isi-edu/ermrestjs/js/utils/helpers';
|
|
7
|
+
|
|
8
|
+
export class FacetGroup {
|
|
9
|
+
/**
|
|
10
|
+
* The reference that this facet blongs to
|
|
11
|
+
*/
|
|
12
|
+
public reference: Reference;
|
|
13
|
+
/**
|
|
14
|
+
* the index of this group in the reference.facetColumnsStructure array
|
|
15
|
+
*/
|
|
16
|
+
public structureIndex: number;
|
|
17
|
+
/**
|
|
18
|
+
* the facet object group wrapper from the source object
|
|
19
|
+
*/
|
|
20
|
+
public facetObjectGroupWrapper: FacetObjectGroupWrapper;
|
|
21
|
+
/**
|
|
22
|
+
* the indices of the children facet columns in the facetColumns array
|
|
23
|
+
*/
|
|
24
|
+
public children: Array<number>;
|
|
25
|
+
|
|
26
|
+
public displayname: DisplayName;
|
|
27
|
+
public comment: CommentType;
|
|
28
|
+
|
|
29
|
+
private _isOpen?: boolean;
|
|
30
|
+
|
|
31
|
+
constructor(reference: Reference, index: number, facetObjectGroupWrapper: FacetObjectGroupWrapper, children: Array<number>) {
|
|
32
|
+
this.reference = reference;
|
|
33
|
+
this.structureIndex = index;
|
|
34
|
+
this.facetObjectGroupWrapper = facetObjectGroupWrapper;
|
|
35
|
+
this.children = children;
|
|
36
|
+
this.displayname = facetObjectGroupWrapper.displayname;
|
|
37
|
+
this.comment = _processSourceObjectComment(facetObjectGroupWrapper.sourceObject);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* whether the group should be opened by default or not
|
|
42
|
+
*/
|
|
43
|
+
get isOpen(): boolean {
|
|
44
|
+
if (this._isOpen === undefined) {
|
|
45
|
+
// by default all groups are opened
|
|
46
|
+
let isOpen = true;
|
|
47
|
+
|
|
48
|
+
// if the source object has open property, use it
|
|
49
|
+
const sourceOpen = this.facetObjectGroupWrapper.sourceObject.open;
|
|
50
|
+
if (typeof sourceOpen === 'boolean') {
|
|
51
|
+
isOpen = sourceOpen;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// if any of the childrens have filters, the group should be opened
|
|
55
|
+
for (const child of this.facetObjectGroupWrapper.children) {
|
|
56
|
+
const facet = this.reference.facetColumns.find((fc) => fc.sourceObjectWrapper.name === child.name);
|
|
57
|
+
if (facet && facet.filters.length > 0) {
|
|
58
|
+
isOpen = true;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
this._isOpen = isOpen;
|
|
64
|
+
}
|
|
65
|
+
return this._isOpen;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* creates a copy of this facet group for the new reference
|
|
70
|
+
* @param newReference the reference that the cloned facet group will belong to
|
|
71
|
+
* @returns a cloned facet group
|
|
72
|
+
*/
|
|
73
|
+
copy(newReference?: Reference): FacetGroup {
|
|
74
|
+
return new FacetGroup(newReference ? newReference : this.reference, this.structureIndex, this.facetObjectGroupWrapper, this.children.slice());
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* other files must import from here and not directly from each individual class
|
|
5
5
|
*/
|
|
6
6
|
export * from '@isrd-isi-edu/ermrestjs/src/models/reference-column/reference-column';
|
|
7
|
+
export * from '@isrd-isi-edu/ermrestjs/src/models/reference-column/facet-group';
|
|
7
8
|
export * from '@isrd-isi-edu/ermrestjs/src/models/reference-column/facet-column';
|
|
8
9
|
export * from '@isrd-isi-edu/ermrestjs/src/models/reference-column/pseudo-column';
|
|
9
10
|
export * from '@isrd-isi-edu/ermrestjs/src/models/reference-column/foreign-key-pseudo-column';
|
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
/* eslint-disable prettier/prettier */
|
|
2
2
|
|
|
3
3
|
// models
|
|
4
|
-
import SourceObjectNode from '@isrd-isi-edu/ermrestjs/src/models/source-object-node';
|
|
5
|
-
import { ReferenceColumn } from '@isrd-isi-edu/ermrestjs/src/models/reference-column';
|
|
6
|
-
import { Tuple, Reference } from '@isrd-isi-edu/ermrestjs/src/models/reference';
|
|
4
|
+
import type SourceObjectNode from '@isrd-isi-edu/ermrestjs/src/models/source-object-node';
|
|
5
|
+
import type { ReferenceColumn } from '@isrd-isi-edu/ermrestjs/src/models/reference-column';
|
|
6
|
+
import type { Tuple, Reference } from '@isrd-isi-edu/ermrestjs/src/models/reference';
|
|
7
|
+
import type { DisplayName } from '@isrd-isi-edu/ermrestjs/src/models/display-name';
|
|
7
8
|
|
|
8
9
|
// services
|
|
9
|
-
|
|
10
|
+
import $log from '@isrd-isi-edu/ermrestjs/src/services/logger';
|
|
10
11
|
|
|
11
12
|
// utils
|
|
12
13
|
import { isObjectAndNotNull, isStringAndNotEmpty } from '@isrd-isi-edu/ermrestjs/src/utils/type-utils';
|
|
13
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
_contexts,
|
|
16
|
+
_facetFilterTypes,
|
|
17
|
+
_pseudoColAggregateFns,
|
|
18
|
+
_sourceDefinitionAttributes,
|
|
19
|
+
_warningMessages,
|
|
20
|
+
} from '@isrd-isi-edu/ermrestjs/src/utils/constants';
|
|
14
21
|
import { renderMarkdown } from '@isrd-isi-edu/ermrestjs/src/utils/markdown-utils';
|
|
15
22
|
import { createPseudoColumn } from '@isrd-isi-edu/ermrestjs/src/utils/column-utils';
|
|
16
23
|
|
|
17
24
|
// legacy imports that need to be accessed
|
|
18
|
-
import { _sourceColumnHelpers } from '@isrd-isi-edu/ermrestjs/js/utils/pseudocolumn_helpers';
|
|
19
|
-
import { Column, Table } from '@isrd-isi-edu/ermrestjs/js/core';
|
|
25
|
+
import { _facetColumnHelpers, _sourceColumnHelpers } from '@isrd-isi-edu/ermrestjs/js/utils/pseudocolumn_helpers';
|
|
26
|
+
import type { Column, Table } from '@isrd-isi-edu/ermrestjs/js/core';
|
|
20
27
|
|
|
21
28
|
export type FilterPropsType = {
|
|
22
29
|
/**
|
|
@@ -68,7 +75,7 @@ export type InputIframePropsType = {
|
|
|
68
75
|
/**
|
|
69
76
|
* Represents a column-directive
|
|
70
77
|
*/
|
|
71
|
-
class SourceObjectWrapper {
|
|
78
|
+
export default class SourceObjectWrapper {
|
|
72
79
|
/**
|
|
73
80
|
* the source object
|
|
74
81
|
*/
|
|
@@ -107,7 +114,7 @@ class SourceObjectWrapper {
|
|
|
107
114
|
isFilterProcessed: true,
|
|
108
115
|
hasRootFilter: false,
|
|
109
116
|
hasFilterInBetween: false,
|
|
110
|
-
leafFilterString: ''
|
|
117
|
+
leafFilterString: '',
|
|
111
118
|
};
|
|
112
119
|
|
|
113
120
|
/**
|
|
@@ -138,7 +145,6 @@ class SourceObjectWrapper {
|
|
|
138
145
|
public isInputIframe = false;
|
|
139
146
|
public inputIframeProps?: InputIframePropsType;
|
|
140
147
|
|
|
141
|
-
|
|
142
148
|
/**
|
|
143
149
|
* used for facets
|
|
144
150
|
* TODO is there a better way to manage this?
|
|
@@ -152,6 +158,8 @@ class SourceObjectWrapper {
|
|
|
152
158
|
* @param sources already generated source (only useful for source-def generation)
|
|
153
159
|
* @param mainTuple the main tuple that is used for filters
|
|
154
160
|
* @param skipProcessingFilters whether we should skip processing filters or not
|
|
161
|
+
*
|
|
162
|
+
* @throws Will throw an error if the source object is invalid.
|
|
155
163
|
*/
|
|
156
164
|
constructor(
|
|
157
165
|
sourceObject: Record<string, unknown>,
|
|
@@ -226,7 +234,7 @@ class SourceObjectWrapper {
|
|
|
226
234
|
isFacet?: boolean,
|
|
227
235
|
sources?: any,
|
|
228
236
|
mainTuple?: Tuple,
|
|
229
|
-
skipProcessingFilters?: boolean
|
|
237
|
+
skipProcessingFilters?: boolean,
|
|
230
238
|
): true | { error: boolean; message: string } {
|
|
231
239
|
const sourceObject = this.sourceObject;
|
|
232
240
|
const wm = _warningMessages;
|
|
@@ -270,7 +278,7 @@ class SourceObjectWrapper {
|
|
|
270
278
|
table.schema.catalog.id,
|
|
271
279
|
sources,
|
|
272
280
|
mainTuple,
|
|
273
|
-
skipProcessingFilters
|
|
281
|
+
skipProcessingFilters,
|
|
274
282
|
) as any;
|
|
275
283
|
|
|
276
284
|
if (res.error) {
|
|
@@ -346,21 +354,13 @@ class SourceObjectWrapper {
|
|
|
346
354
|
|
|
347
355
|
// generate name:
|
|
348
356
|
// TODO maybe we shouldn't even allow aggregate in faceting (for now we're ignoring it)
|
|
349
|
-
if (
|
|
350
|
-
sourceObject.self_link === true ||
|
|
351
|
-
this.isFiltered ||
|
|
352
|
-
this.hasPath ||
|
|
353
|
-
this.isEntityMode ||
|
|
354
|
-
(isFacet !== true && this.hasAggregate)
|
|
355
|
-
) {
|
|
357
|
+
if (sourceObject.self_link === true || this.isFiltered || this.hasPath || this.isEntityMode || (isFacet !== true && this.hasAggregate)) {
|
|
356
358
|
this.name = _sourceColumnHelpers.generateSourceObjectHashName(sourceObject, !!isFacet, sourceObjectNodes) as string;
|
|
357
359
|
|
|
358
360
|
this.isHash = true;
|
|
359
361
|
|
|
360
362
|
if (table.columns.has(this.name)) {
|
|
361
|
-
return returnError(
|
|
362
|
-
'Generated Hash `' + this.name + '` for pseudo-column exists in table `' + table.name + '`.'
|
|
363
|
-
);
|
|
363
|
+
return returnError('Generated Hash `' + this.name + '` for pseudo-column exists in table `' + table.name + '`.');
|
|
364
364
|
}
|
|
365
365
|
} else {
|
|
366
366
|
this.name = col.name;
|
|
@@ -403,19 +403,12 @@ class SourceObjectWrapper {
|
|
|
403
403
|
if (reverse) {
|
|
404
404
|
return sn.toString(reverse, isLeft, outAlias, isReverseRightJoin);
|
|
405
405
|
} else {
|
|
406
|
-
return sn.toString(
|
|
407
|
-
reverse,
|
|
408
|
-
isLeft,
|
|
409
|
-
this.foreignKeyPathLength === sn.nodeObject.foreignKeyPathLength ? outAlias : null,
|
|
410
|
-
isReverseRightJoin
|
|
411
|
-
);
|
|
406
|
+
return sn.toString(reverse, isLeft, this.foreignKeyPathLength === sn.nodeObject.foreignKeyPathLength ? outAlias : null, isReverseRightJoin);
|
|
412
407
|
}
|
|
413
408
|
}
|
|
414
409
|
|
|
415
410
|
const fkStr = sn.toString(reverse, isLeft);
|
|
416
|
-
const addAlias =
|
|
417
|
-
outAlias &&
|
|
418
|
-
((reverse && sn === this.firstForeignKeyNode) || (!reverse && sn === this.lastForeignKeyNode));
|
|
411
|
+
const addAlias = outAlias && ((reverse && sn === this.firstForeignKeyNode) || (!reverse && sn === this.lastForeignKeyNode));
|
|
419
412
|
|
|
420
413
|
// NOTE alias on each node is ignored!
|
|
421
414
|
// currently we've added alias only for the association and
|
|
@@ -465,12 +458,7 @@ class SourceObjectWrapper {
|
|
|
465
458
|
const sn = this.sourceObjectNodes[i];
|
|
466
459
|
if (sn.isPathPrefix) {
|
|
467
460
|
// if this is the last element, we have to add the alias to this
|
|
468
|
-
path.push(
|
|
469
|
-
...sn.nodeObject.getRawSourcePath(
|
|
470
|
-
reverse,
|
|
471
|
-
this.foreignKeyPathLength == sn.nodeObject.foreignKeyPathLength ? outAlias : null
|
|
472
|
-
)
|
|
473
|
-
);
|
|
461
|
+
path.push(...sn.nodeObject.getRawSourcePath(reverse, this.foreignKeyPathLength == sn.nodeObject.foreignKeyPathLength ? outAlias : null));
|
|
474
462
|
} else if (sn.isFilter) {
|
|
475
463
|
path.push(sn.nodeObject);
|
|
476
464
|
} else {
|
|
@@ -549,7 +537,11 @@ class SourceObjectWrapper {
|
|
|
549
537
|
* @param usedIframeInputMappings an object capturing columns used in other mappings. used to avoid overlapping
|
|
550
538
|
* @param tuple the tuple object
|
|
551
539
|
*/
|
|
552
|
-
processInputIframe(
|
|
540
|
+
processInputIframe(
|
|
541
|
+
reference: Reference,
|
|
542
|
+
usedIframeInputMappings: any,
|
|
543
|
+
tuple?: Tuple,
|
|
544
|
+
): {
|
|
553
545
|
success?: boolean;
|
|
554
546
|
error?: boolean;
|
|
555
547
|
message?: string;
|
|
@@ -595,20 +587,14 @@ class SourceObjectWrapper {
|
|
|
595
587
|
const isSerial = c.type.name.indexOf('serial') === 0;
|
|
596
588
|
|
|
597
589
|
// we cannot use getInputDisabled since we just want to do this based on ACLs
|
|
598
|
-
if (
|
|
599
|
-
context === _contexts.CREATE &&
|
|
600
|
-
(c.isSystemColumn || c.isGeneratedPerACLs || isSerial)
|
|
601
|
-
) {
|
|
590
|
+
if (context === _contexts.CREATE && (c.isSystemColumn || c.isGeneratedPerACLs || isSerial)) {
|
|
602
591
|
if (colName in optionalFieldNames) continue;
|
|
603
592
|
return {
|
|
604
593
|
error: true,
|
|
605
594
|
message: 'column `' + colName + '` cannot be modified by this user.',
|
|
606
595
|
};
|
|
607
596
|
}
|
|
608
|
-
if (
|
|
609
|
-
(context === _contexts.EDIT || context === _contexts.ENTRY) &&
|
|
610
|
-
(c.isSystemColumn || c.isImmutablePerACLs || isSerial)
|
|
611
|
-
) {
|
|
597
|
+
if ((context === _contexts.EDIT || context === _contexts.ENTRY) && (c.isSystemColumn || c.isImmutablePerACLs || isSerial)) {
|
|
612
598
|
if (colName in optionalFieldNames) continue;
|
|
613
599
|
return {
|
|
614
600
|
error: true,
|
|
@@ -660,7 +646,10 @@ class SourceObjectWrapper {
|
|
|
660
646
|
* @param mainTuple
|
|
661
647
|
* @param dontThrowError if set to true, will not throw an error if the filters are not valid
|
|
662
648
|
*/
|
|
663
|
-
processFilterNodes(
|
|
649
|
+
processFilterNodes(
|
|
650
|
+
mainTuple?: Tuple,
|
|
651
|
+
dontThrowError?: boolean,
|
|
652
|
+
): {
|
|
664
653
|
success: boolean;
|
|
665
654
|
error?: boolean;
|
|
666
655
|
message?: string;
|
|
@@ -691,4 +680,65 @@ class SourceObjectWrapper {
|
|
|
691
680
|
}
|
|
692
681
|
}
|
|
693
682
|
|
|
694
|
-
|
|
683
|
+
/**
|
|
684
|
+
* Represents a facet object group
|
|
685
|
+
*/
|
|
686
|
+
export class FacetObjectGroupWrapper {
|
|
687
|
+
sourceObject: Record<string, unknown>;
|
|
688
|
+
children: SourceObjectWrapper[];
|
|
689
|
+
displayname: DisplayName;
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Creates a new instance of FacetObjectGroupWrapper.
|
|
693
|
+
* Will throw an error if there was any issues in processing the source object.
|
|
694
|
+
*
|
|
695
|
+
* @param sourceObject The source object representing the facet group.
|
|
696
|
+
* @param table The table to which the facet group belongs.
|
|
697
|
+
* @param hasFilterOrFacet Indicates if the group has any filters or facets.
|
|
698
|
+
*
|
|
699
|
+
* @throws Will throw an error if the source object is invalid or has no valid children.
|
|
700
|
+
*/
|
|
701
|
+
constructor(sourceObject: Record<string, unknown>, table: Table, hasFilterOrFacet: boolean) {
|
|
702
|
+
if (!Array.isArray(sourceObject.and) || sourceObject.and.length === 0) {
|
|
703
|
+
throw new Error('valid array of children is required');
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
this.sourceObject = sourceObject;
|
|
707
|
+
|
|
708
|
+
let displayname: DisplayName | null = null;
|
|
709
|
+
const rendered = renderMarkdown(
|
|
710
|
+
sourceObject.markdown_name && typeof sourceObject.markdown_name === 'string' ? sourceObject.markdown_name : '',
|
|
711
|
+
true,
|
|
712
|
+
);
|
|
713
|
+
if (rendered) {
|
|
714
|
+
displayname = {
|
|
715
|
+
value: rendered,
|
|
716
|
+
unformatted: sourceObject.markdown_name as string,
|
|
717
|
+
isHTML: true,
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
if (!displayname) {
|
|
722
|
+
throw new Error('valid markdown_name is required');
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
this.displayname = displayname;
|
|
726
|
+
|
|
727
|
+
const children: SourceObjectWrapper[] = [];
|
|
728
|
+
for (const child of sourceObject.and) {
|
|
729
|
+
try {
|
|
730
|
+
const wrapper = _facetColumnHelpers.sourceDefToFacetObjectWrapper(child, table, hasFilterOrFacet);
|
|
731
|
+
children.push(wrapper);
|
|
732
|
+
} catch (exp: unknown) {
|
|
733
|
+
$log.info(`child of facet group "${sourceObject.markdown_name}", index: ${children.length} is invalid:`);
|
|
734
|
+
$log.info((exp as Error).message);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
if (children.length === 0) {
|
|
739
|
+
throw new Error('the group must have at least one valid child');
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
this.children = children;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
@@ -81,8 +81,10 @@ class TableSourceDefinitions {
|
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
83
|
* Get a source definition by its key.
|
|
84
|
+
* This will also make sure the filter nodes are processed and valid.
|
|
84
85
|
* @param sourcekey The key of the source definition.
|
|
85
|
-
* @
|
|
86
|
+
* @param mainTuple The main tuple to use for processing the filter nodes.
|
|
87
|
+
* @returns The source object wrapper or undefined if not found.
|
|
86
88
|
*/
|
|
87
89
|
getSource(sourcekey: string, mainTuple?: Tuple): SourceObjectWrapper | undefined {
|
|
88
90
|
const sd = this.sources[sourcekey];
|
package/src/utils/constants.ts
CHANGED
|
@@ -353,6 +353,8 @@ export const _facetingErrors = Object.freeze({
|
|
|
353
353
|
onlyOneNullFilter: 'Only one null filter is allowed in the facets',
|
|
354
354
|
duplicateFacets: 'Cannot define two different sets of facets',
|
|
355
355
|
invalidSourcekey: 'Given sourcekey string is not valid',
|
|
356
|
+
aggregateFnNowtAllowed: 'Aggregate functions are not allowed in facet source definition.',
|
|
357
|
+
arrayColumnTypeNotSupported: 'Facet of array column types are not supported.',
|
|
356
358
|
});
|
|
357
359
|
|
|
358
360
|
export const _HTTPErrorCodes = Object.freeze({
|
|
@@ -370,6 +372,7 @@ export const _HTTPErrorCodes = Object.freeze({
|
|
|
370
372
|
|
|
371
373
|
export const _warningMessages = Object.freeze({
|
|
372
374
|
NO_PSEUDO_IN_ENTRY: 'pseudo-columns are not allowed in entry contexts.',
|
|
375
|
+
INVALID_FACET_ENTRY: 'given value must be an object with either `source` or `sourcekey` defined.',
|
|
373
376
|
INVALID_SOURCE: 'given object is invalid. `source` is required and it must be valid',
|
|
374
377
|
INVALID_SOURCEKEY: 'given object is invalid. The defined `sourcekey` is invalid.',
|
|
375
378
|
INVALID_VIRTUAL_NO_NAME: '`markdown_name` is required when `source` and `sourcekey` are undefiend.',
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// models
|
|
2
2
|
import { InvalidPageCriteria, InvalidSortCriteria, UnsupportedFilters } from '@isrd-isi-edu/ermrestjs/src/models/errors';
|
|
3
|
-
import SourceObjectWrapper from '@isrd-isi-edu/ermrestjs/src/models/source-object-wrapper';
|
|
3
|
+
import SourceObjectWrapper, { FacetObjectGroupWrapper } from '@isrd-isi-edu/ermrestjs/src/models/source-object-wrapper';
|
|
4
4
|
import {
|
|
5
5
|
AssetPseudoColumn,
|
|
6
6
|
FacetColumn,
|
|
7
|
+
FacetGroup,
|
|
7
8
|
ForeignKeyPseudoColumn,
|
|
8
9
|
InboundForeignKeyPseudoColumn,
|
|
9
10
|
KeyPseudoColumn,
|
|
@@ -61,7 +62,7 @@ export interface ReadPathResult {
|
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
export type ValidatedFacetFilters = {
|
|
64
|
-
facetObjectWrappers: Array<SourceObjectWrapper>;
|
|
65
|
+
facetObjectWrappers: Array<SourceObjectWrapper | FacetObjectGroupWrapper>;
|
|
65
66
|
newFilters: Array<any>;
|
|
66
67
|
issues: UnsupportedFilters | null;
|
|
67
68
|
};
|
|
@@ -522,6 +523,21 @@ export function generateColumnsList(reference: Reference | RelatedReference, tup
|
|
|
522
523
|
const isCompact = typeof context === 'string' && context.startsWith(_contexts.COMPACT);
|
|
523
524
|
const isCompactEntry = typeof context === 'string' && context.startsWith(_contexts.COMPACT_ENTRY);
|
|
524
525
|
|
|
526
|
+
// create a map of tableColumns to make it easier to find one
|
|
527
|
+
reference.table.columns.all().forEach((c: any) => {
|
|
528
|
+
tableColumns[c.name] = true;
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
// get columns from the input (used when we just want to process the given list of columns)
|
|
532
|
+
let columns: unknown = -1;
|
|
533
|
+
if (Array.isArray(columnsList)) {
|
|
534
|
+
columns = columnsList;
|
|
535
|
+
}
|
|
536
|
+
// get column orders from annotation
|
|
537
|
+
else if (reference.table.annotations.contains(_annotations.VISIBLE_COLUMNS)) {
|
|
538
|
+
columns = _getRecursiveAnnotationValue(reference.context, reference.table.annotations.get(_annotations.VISIBLE_COLUMNS).content);
|
|
539
|
+
}
|
|
540
|
+
|
|
525
541
|
// check if we should hide some columns or not.
|
|
526
542
|
// NOTE: if the reference is actually an inbound related reference, we should hide the foreign key that created reference link.
|
|
527
543
|
const hasOrigFKRToHide = typeof context === 'string' && context.startsWith(_contexts.COMPACT_BRIEF) && isObjectAndNotNull(hiddenFKR);
|
|
@@ -596,29 +612,15 @@ export function generateColumnsList(reference: Reference | RelatedReference, tup
|
|
|
596
612
|
};
|
|
597
613
|
|
|
598
614
|
const wm = _warningMessages;
|
|
599
|
-
const logCol = (bool: boolean, message: string, index
|
|
615
|
+
const logCol = (bool: boolean, message: string, index: number) => {
|
|
600
616
|
if (bool && !skipLog) {
|
|
601
|
-
$log.info(`
|
|
617
|
+
$log.info(`vis-col for table '${reference.table.name}' in context '${context}' at index '${index}':`);
|
|
602
618
|
$log.info(message);
|
|
619
|
+
// we could log the object too, but it might be too verbose
|
|
603
620
|
}
|
|
604
621
|
return bool;
|
|
605
622
|
};
|
|
606
623
|
|
|
607
|
-
// create a map of tableColumns to make it easier to find one
|
|
608
|
-
reference.table.columns.all().forEach((c: any) => {
|
|
609
|
-
tableColumns[c.name] = true;
|
|
610
|
-
});
|
|
611
|
-
|
|
612
|
-
// get columns from the input (used when we just want to process the given list of columns)
|
|
613
|
-
let columns: unknown = -1;
|
|
614
|
-
if (Array.isArray(columnsList)) {
|
|
615
|
-
columns = columnsList;
|
|
616
|
-
}
|
|
617
|
-
// get column orders from annotation
|
|
618
|
-
else if (reference.table.annotations.contains(_annotations.VISIBLE_COLUMNS)) {
|
|
619
|
-
columns = _getRecursiveAnnotationValue(reference.context, reference.table.annotations.get(_annotations.VISIBLE_COLUMNS).content);
|
|
620
|
-
}
|
|
621
|
-
|
|
622
624
|
// annotation
|
|
623
625
|
if (columns !== -1 && Array.isArray(columns)) {
|
|
624
626
|
for (let i = 0; i < columns.length; i++) {
|
|
@@ -724,7 +726,7 @@ export function generateColumnsList(reference: Reference | RelatedReference, tup
|
|
|
724
726
|
wrapper = new SourceObjectWrapper(col, reference.table, false, undefined, tuple);
|
|
725
727
|
}
|
|
726
728
|
} catch (exp: unknown) {
|
|
727
|
-
logCol(true,
|
|
729
|
+
logCol(true, (exp as Error).message, i);
|
|
728
730
|
continue;
|
|
729
731
|
}
|
|
730
732
|
|
|
@@ -754,7 +756,7 @@ export function generateColumnsList(reference: Reference | RelatedReference, tup
|
|
|
754
756
|
i,
|
|
755
757
|
) ||
|
|
756
758
|
logCol(wrapper.hasAggregate && isEntry, wm.NO_AGG_IN_ENTRY, i) ||
|
|
757
|
-
logCol(wrapper.isUniqueFiltered, wm.FILTER_NO_PATH_NOT_ALLOWED) ||
|
|
759
|
+
logCol(wrapper.isUniqueFiltered, wm.FILTER_NO_PATH_NOT_ALLOWED, i) ||
|
|
758
760
|
logCol(
|
|
759
761
|
isEntry && wrapper.hasPath && (wrapper.hasInbound || wrapper.isFiltered || wrapper.foreignKeyPathLength > 1),
|
|
760
762
|
wm.NO_PATH_IN_ENTRY,
|
|
@@ -1043,13 +1045,16 @@ export function generateColumnsList(reference: Reference | RelatedReference, tup
|
|
|
1043
1045
|
export function generateFacetColumns(
|
|
1044
1046
|
reference: Reference,
|
|
1045
1047
|
skipMappingEntityChoices: boolean,
|
|
1046
|
-
):
|
|
1048
|
+
):
|
|
1049
|
+
| Promise<{ facetColumns: FacetColumn[]; issues: UnsupportedFilters | null; facetColumnsStructure: Array<number | FacetGroup> }>
|
|
1050
|
+
| { facetColumns: FacetColumn[]; issues: UnsupportedFilters | null; facetColumnsStructure: Array<number | FacetGroup> } {
|
|
1047
1051
|
const andOperator = _FacetsLogicalOperators.AND;
|
|
1052
|
+
const addedGroups: Record<string, boolean> = {};
|
|
1048
1053
|
let searchTerm = reference.location.searchTerm;
|
|
1049
1054
|
const helpers = _facetColumnHelpers;
|
|
1050
1055
|
|
|
1051
1056
|
// if location has facet or filter, we should honor it and we should not add preselected facets in annotation
|
|
1052
|
-
const hasFilterOrFacet = reference.location.facets || reference.location.filter || reference.location.customFacets;
|
|
1057
|
+
const hasFilterOrFacet = !!reference.location.facets || !!reference.location.filter || !!reference.location.customFacets;
|
|
1053
1058
|
|
|
1054
1059
|
const andFilters = reference.location.facets ? reference.location.facets.andFilters : [];
|
|
1055
1060
|
// change filters to facet NOTE can be optimized to actually merge instead of just appending to list
|
|
@@ -1060,7 +1065,7 @@ export function generateFacetColumns(
|
|
|
1060
1065
|
|
|
1061
1066
|
let annotationCols: any = -1;
|
|
1062
1067
|
let usedAnnotation = false;
|
|
1063
|
-
const facetObjectWrappers:
|
|
1068
|
+
const facetObjectWrappers: Array<SourceObjectWrapper | FacetObjectGroupWrapper> = [];
|
|
1064
1069
|
|
|
1065
1070
|
// get column orders from annotation
|
|
1066
1071
|
if (reference.table.annotations.contains(_annotations.VISIBLE_COLUMNS)) {
|
|
@@ -1072,10 +1077,21 @@ export function generateFacetColumns(
|
|
|
1072
1077
|
}
|
|
1073
1078
|
}
|
|
1074
1079
|
|
|
1080
|
+
const wm = _warningMessages;
|
|
1081
|
+
const logError = (message: string, index: number) => {
|
|
1082
|
+
$log.info(`vis-col for table '${reference.table.name}' in context 'filter' at index '${index}':`);
|
|
1083
|
+
$log.info(message);
|
|
1084
|
+
};
|
|
1085
|
+
|
|
1075
1086
|
if (annotationCols !== -1) {
|
|
1076
1087
|
usedAnnotation = true;
|
|
1077
1088
|
// NOTE We're allowing duplicates in annotation.
|
|
1078
1089
|
annotationCols.forEach((obj: any, objIndex: number) => {
|
|
1090
|
+
if (!isObjectAndNotNull(obj)) {
|
|
1091
|
+
logError(wm.INVALID_FACET_ENTRY, objIndex);
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1079
1095
|
// if we have filters in the url, we will get the filters only from url
|
|
1080
1096
|
if (obj.sourcekey === _specialSourceDefinitions.SEARCH_BOX && andFilters.length === 0) {
|
|
1081
1097
|
if (!searchTerm) {
|
|
@@ -1087,36 +1103,23 @@ export function generateFacetColumns(
|
|
|
1087
1103
|
// make sure it's not referring to the annotation object.
|
|
1088
1104
|
obj = simpleDeepCopy(obj);
|
|
1089
1105
|
|
|
1090
|
-
let sd: any, wrapper: any;
|
|
1091
1106
|
try {
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
if (!
|
|
1096
|
-
|
|
1097
|
-
|
|
1107
|
+
if ('and' in obj) {
|
|
1108
|
+
const fow = new FacetObjectGroupWrapper(obj, reference.table, hasFilterOrFacet);
|
|
1109
|
+
// avoid duplicate groups
|
|
1110
|
+
if (fow.displayname.unformatted! in addedGroups) {
|
|
1111
|
+
throw new Error(`Duplicate facet group name: ${fow.displayname.unformatted}`);
|
|
1112
|
+
}
|
|
1113
|
+
addedGroups[fow.displayname.unformatted!] = true;
|
|
1114
|
+
facetObjectWrappers.push(fow);
|
|
1098
1115
|
} else {
|
|
1099
|
-
wrapper =
|
|
1116
|
+
const wrapper = helpers.sourceDefToFacetObjectWrapper(obj, reference.table, hasFilterOrFacet);
|
|
1117
|
+
facetObjectWrappers.push(wrapper);
|
|
1100
1118
|
}
|
|
1101
1119
|
} catch (exp) {
|
|
1102
|
-
|
|
1103
|
-
// invalid definition or not unsupported
|
|
1104
|
-
// TODO could show the error message
|
|
1120
|
+
logError((exp as Error).message, objIndex);
|
|
1105
1121
|
return;
|
|
1106
1122
|
}
|
|
1107
|
-
|
|
1108
|
-
const supported = helpers.checkFacetObjectWrapper(wrapper);
|
|
1109
|
-
if (!supported) return;
|
|
1110
|
-
|
|
1111
|
-
// if we have filters in the url, we will get the filters only from url
|
|
1112
|
-
if (hasFilterOrFacet) {
|
|
1113
|
-
delete wrapper.sourceObject.not_null;
|
|
1114
|
-
delete wrapper.sourceObject.choices;
|
|
1115
|
-
delete wrapper.sourceObject.search;
|
|
1116
|
-
delete wrapper.sourceObject.ranges;
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
facetObjectWrappers.push(wrapper);
|
|
1120
1123
|
});
|
|
1121
1124
|
}
|
|
1122
1125
|
// annotation didn't exist, so we are going to use the
|
|
@@ -1164,20 +1167,39 @@ export function generateFacetColumns(
|
|
|
1164
1167
|
}
|
|
1165
1168
|
|
|
1166
1169
|
// if we have filters in the url, we should just get the structure from annotation
|
|
1167
|
-
const finalize = (res:
|
|
1168
|
-
const facetColumns:
|
|
1170
|
+
const finalize = (res: ValidatedFacetFilters) => {
|
|
1171
|
+
const facetColumns: FacetColumn[] = [];
|
|
1172
|
+
const facetColumnsStructure: Array<number | FacetGroup> = [];
|
|
1169
1173
|
|
|
1170
1174
|
// turn facetObjectWrappers into facetColumn
|
|
1171
|
-
res.facetObjectWrappers.forEach((fo
|
|
1172
|
-
// if
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1175
|
+
res.facetObjectWrappers.forEach((fo) => {
|
|
1176
|
+
// if it's a group, we should create object for all children and add their index to the group
|
|
1177
|
+
if ('children' in fo) {
|
|
1178
|
+
// TODO should this be a proper check for FacetObjectGroupWrapper?
|
|
1179
|
+
const structureIndex = facetColumnsStructure.length;
|
|
1180
|
+
const childIndexes: number[] = [];
|
|
1181
|
+
fo.children.forEach((child) => {
|
|
1182
|
+
// if the function returns false, it couldn't handle that case, and therefore we are ignoring it.
|
|
1183
|
+
if (!helpers.checkForAlternative(child, usedAnnotation, reference.table)) return;
|
|
1184
|
+
const usedIndex = facetColumns.length;
|
|
1185
|
+
facetColumns.push(new FacetColumn(reference, usedIndex, child, structureIndex));
|
|
1186
|
+
childIndexes.push(usedIndex);
|
|
1187
|
+
});
|
|
1188
|
+
if (childIndexes.length === 0) return; // if there wasn't any valid child, ignore the group
|
|
1189
|
+
facetColumnsStructure.push(new FacetGroup(reference, structureIndex, fo, childIndexes));
|
|
1190
|
+
}
|
|
1191
|
+
// individual facet column
|
|
1192
|
+
else {
|
|
1193
|
+
// if the function returns false, it couldn't handle that case, and therefore we are ignoring it.
|
|
1194
|
+
if (!helpers.checkForAlternative(fo, usedAnnotation, reference.table)) return;
|
|
1195
|
+
const usedIndex = facetColumns.length;
|
|
1196
|
+
facetColumns.push(new FacetColumn(reference, usedIndex, fo));
|
|
1197
|
+
facetColumnsStructure.push(usedIndex);
|
|
1198
|
+
}
|
|
1177
1199
|
});
|
|
1178
1200
|
|
|
1179
1201
|
// get the existing facets on the columns (coming from annotation)
|
|
1180
|
-
facetColumns.forEach((fc
|
|
1202
|
+
facetColumns.forEach((fc) => {
|
|
1181
1203
|
if (fc.filters.length !== 0) {
|
|
1182
1204
|
res.newFilters.push(fc.toJSON());
|
|
1183
1205
|
}
|
|
@@ -1190,24 +1212,26 @@ export function generateFacetColumns(
|
|
|
1190
1212
|
reference.location.facets = null;
|
|
1191
1213
|
}
|
|
1192
1214
|
|
|
1193
|
-
return facetColumns;
|
|
1215
|
+
return { facetColumns, facetColumnsStructure };
|
|
1194
1216
|
};
|
|
1195
1217
|
|
|
1196
1218
|
// if we don't want to map entity choices, then function will work in sync mode
|
|
1197
1219
|
if (skipMappingEntityChoices) {
|
|
1198
1220
|
const res = validateFacetsFilters(reference, andFilters, facetObjectWrappers, searchTerm, skipMappingEntityChoices) as ValidatedFacetFilters;
|
|
1199
|
-
const
|
|
1221
|
+
const finalizedRes = finalize(res);
|
|
1200
1222
|
return {
|
|
1201
|
-
facetColumns,
|
|
1223
|
+
facetColumns: finalizedRes.facetColumns,
|
|
1224
|
+
facetColumnsStructure: finalizedRes.facetColumnsStructure,
|
|
1202
1225
|
issues: res.issues,
|
|
1203
1226
|
};
|
|
1204
1227
|
} else {
|
|
1205
1228
|
return new Promise((resolve, reject) => {
|
|
1206
1229
|
(validateFacetsFilters(reference, andFilters, facetObjectWrappers, searchTerm, skipMappingEntityChoices) as Promise<ValidatedFacetFilters>)
|
|
1207
1230
|
.then((res) => {
|
|
1208
|
-
const
|
|
1231
|
+
const finalizedRes = finalize(res);
|
|
1209
1232
|
resolve({
|
|
1210
|
-
facetColumns,
|
|
1233
|
+
facetColumns: finalizedRes.facetColumns,
|
|
1234
|
+
facetColumnsStructure: finalizedRes.facetColumnsStructure,
|
|
1211
1235
|
issues: res.issues,
|
|
1212
1236
|
});
|
|
1213
1237
|
})
|
|
@@ -1234,14 +1258,14 @@ export function generateFacetColumns(
|
|
|
1234
1258
|
export function validateFacetsFilters(
|
|
1235
1259
|
reference: Reference,
|
|
1236
1260
|
facetAndFilters: any,
|
|
1237
|
-
facetObjectWrappers: Array<SourceObjectWrapper>,
|
|
1261
|
+
facetObjectWrappers: Array<SourceObjectWrapper | FacetObjectGroupWrapper>,
|
|
1238
1262
|
searchTerm?: string,
|
|
1239
1263
|
skipMappingEntityChoices?: boolean,
|
|
1240
1264
|
changeLocation?: boolean,
|
|
1241
1265
|
): ValidatedFacetFilters | Promise<ValidatedFacetFilters> {
|
|
1242
1266
|
const helpers = _facetColumnHelpers;
|
|
1243
1267
|
const promises: any[] = [];
|
|
1244
|
-
const checkedObjects: { [key:
|
|
1268
|
+
const checkedObjects: { [key: string]: boolean } = {};
|
|
1245
1269
|
let j: number;
|
|
1246
1270
|
const facetLen = facetObjectWrappers.length;
|
|
1247
1271
|
let andFilterObject: SourceObjectWrapper;
|
|
@@ -1422,21 +1446,42 @@ export function validateFacetsFilters(
|
|
|
1422
1446
|
// find the facet corresponding to the filter
|
|
1423
1447
|
let found = false;
|
|
1424
1448
|
for (j = 0; j < facetLen; j++) {
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
if (
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1449
|
+
const curr = res.facetObjectWrappers[j];
|
|
1450
|
+
|
|
1451
|
+
if (curr instanceof FacetObjectGroupWrapper) {
|
|
1452
|
+
curr.children.forEach((child, childIndex) => {
|
|
1453
|
+
if (checkedObjects[`${j}_${childIndex}`]) return;
|
|
1454
|
+
if (child.name === resp.andFilterObject.name) {
|
|
1455
|
+
checkedObjects[`${j}_${childIndex}`] = true;
|
|
1456
|
+
found = true;
|
|
1457
|
+
// merge facet objects
|
|
1458
|
+
helpers.mergeFacetObjects(child.sourceObject, resp.andFilterObject.sourceObject);
|
|
1459
|
+
|
|
1460
|
+
// make sure the page object is stored
|
|
1461
|
+
if (resp.andFilterObject.entityChoiceFilterTuples) {
|
|
1462
|
+
child.entityChoiceFilterTuples = resp.andFilterObject.entityChoiceFilterTuples;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
});
|
|
1466
|
+
} else {
|
|
1467
|
+
// it can be merged only once, since in a facet the filter is
|
|
1468
|
+
// `or` and outside it's `and`.
|
|
1469
|
+
if (checkedObjects[j]) continue;
|
|
1470
|
+
|
|
1471
|
+
const facetObjectWrapper = curr as SourceObjectWrapper;
|
|
1472
|
+
|
|
1473
|
+
// we want to make sure these two sources are exactly the same
|
|
1474
|
+
// so we can compare their hashnames
|
|
1475
|
+
if (facetObjectWrapper.name === resp.andFilterObject.name) {
|
|
1476
|
+
checkedObjects[j] = true;
|
|
1477
|
+
found = true;
|
|
1478
|
+
// merge facet objects
|
|
1479
|
+
helpers.mergeFacetObjects(facetObjectWrapper.sourceObject, resp.andFilterObject.sourceObject);
|
|
1480
|
+
|
|
1481
|
+
// make sure the page object is stored
|
|
1482
|
+
if (resp.andFilterObject.entityChoiceFilterTuples) {
|
|
1483
|
+
facetObjectWrapper.entityChoiceFilterTuples = resp.andFilterObject.entityChoiceFilterTuples;
|
|
1484
|
+
}
|
|
1440
1485
|
}
|
|
1441
1486
|
}
|
|
1442
1487
|
}
|
package/vite.config.mts
CHANGED