From 4b5536b1bd26469d2e8a4b202267a2629014b635 Mon Sep 17 00:00:00 2001 From: Joshua Chudy Date: Thu, 27 Jun 2024 11:54:09 -0700 Subject: [PATCH 01/15] isAssocation API added --- js/core.js | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/js/core.js b/js/core.js index 6e1e44f8..cc3420f1 100644 --- a/js/core.js +++ b/js/core.js @@ -2092,11 +2092,19 @@ */ get isPureBinaryAssociation () { if(this._isPureBinaryAssociation === undefined) { - this._isPureBinaryAssociation = this._computePureBinaryAssociation(); + this._isPureBinaryAssociation = this._computePureBinaryAssociation(0); } return this._isPureBinaryAssociation; }, + get isAssociation () { + if(this._isAssociation === undefined) { + // will attach the value of _almostPureBinary + this._computePureBinaryAssociation(5); + } + return this._isAssociation; + }, + /** * if the table is pure and binary, will return the two foreignkeys that create it * @type {ERMrest.ForeignKeyRef[]} @@ -2104,12 +2112,12 @@ get pureBinaryForeignKeys () { if(this._pureBinaryForeignKeys_cached === undefined) { // will attach the value of _pureBinaryForeignKeys_cached - this._computePureBinaryAssociation(); + this._computePureBinaryAssociation(5); } return this._pureBinaryForeignKeys_cached; }, - _computePureBinaryAssociation: function () { + _computePureBinaryAssociation: function (numberNonKeyColsAllowed) { var isSystemCol = function (col) { return module._systemColumns.indexOf(col.name) !== -1; }; @@ -2160,13 +2168,22 @@ }); // check for purity - if (nonKeyCols.length === 0) { + if (nonKeyCols.length <= 0) { // attach the value of _pureBinaryForeignKeys this._pureBinaryForeignKeys_cached = nonSystemColumnFks; return true; } + if (nonKeyCols.length <= numberNonKeyColsAllowed) { + // attach the value of _pureBinaryForeignKeys + this._pureBinaryForeignKeys_cached = nonSystemColumnFks; + + // if there are between 1 and 5 nonKeyCols, treat it "almost" like a pure and binary table + this._isAssociation = nonKeyCols.length > 0; + return false; + } + this._pureBinaryForeignKeys_cached = null; return false; }, From c30f72200a997b2a3e78891ebb76cd829f568355 Mon Sep 17 00:00:00 2001 From: Joshua Chudy Date: Thu, 22 Aug 2024 15:43:15 -0700 Subject: [PATCH 02/15] remove old temprary changes and add a new object to reference.js called Prefill --- js/core.js | 25 ++-------- js/reference.js | 128 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+), 21 deletions(-) diff --git a/js/core.js b/js/core.js index cc3420f1..6e1e44f8 100644 --- a/js/core.js +++ b/js/core.js @@ -2092,19 +2092,11 @@ */ get isPureBinaryAssociation () { if(this._isPureBinaryAssociation === undefined) { - this._isPureBinaryAssociation = this._computePureBinaryAssociation(0); + this._isPureBinaryAssociation = this._computePureBinaryAssociation(); } return this._isPureBinaryAssociation; }, - get isAssociation () { - if(this._isAssociation === undefined) { - // will attach the value of _almostPureBinary - this._computePureBinaryAssociation(5); - } - return this._isAssociation; - }, - /** * if the table is pure and binary, will return the two foreignkeys that create it * @type {ERMrest.ForeignKeyRef[]} @@ -2112,12 +2104,12 @@ get pureBinaryForeignKeys () { if(this._pureBinaryForeignKeys_cached === undefined) { // will attach the value of _pureBinaryForeignKeys_cached - this._computePureBinaryAssociation(5); + this._computePureBinaryAssociation(); } return this._pureBinaryForeignKeys_cached; }, - _computePureBinaryAssociation: function (numberNonKeyColsAllowed) { + _computePureBinaryAssociation: function () { var isSystemCol = function (col) { return module._systemColumns.indexOf(col.name) !== -1; }; @@ -2168,22 +2160,13 @@ }); // check for purity - if (nonKeyCols.length <= 0) { + if (nonKeyCols.length === 0) { // attach the value of _pureBinaryForeignKeys this._pureBinaryForeignKeys_cached = nonSystemColumnFks; return true; } - if (nonKeyCols.length <= numberNonKeyColsAllowed) { - // attach the value of _pureBinaryForeignKeys - this._pureBinaryForeignKeys_cached = nonSystemColumnFks; - - // if there are between 1 and 5 nonKeyCols, treat it "almost" like a pure and binary table - this._isAssociation = nonKeyCols.length > 0; - return false; - } - this._pureBinaryForeignKeys_cached = null; return false; }, diff --git a/js/reference.js b/js/reference.js index 1c48fba7..6c93cc1d 100644 --- a/js/reference.js +++ b/js/reference.js @@ -4014,6 +4014,29 @@ return this._cascadingDeletedItems; }, + /** + * If prefill object is defined and has the required attributes, will return + * a Prefill object with the necessary objects used for a association modal picker + * + * @type {ERMrest.Prefill} + */ + get prefill() { + if (this._prefill === undefined) { + // TODO: error with message "Call computePrefill first" + } + return this._prefill; + }, + + computePrefill: function (prefillObject) { + if (this._prefill === undefined) { + var annotation = null; + // var prefillAnno = table.annotations.get(module._annotations.TABLE_DISPLAY).content; + if (prefillAnno) annotation = prefillAnno; + this._prefill = new Prefill(this, prefillObject, annotation); + } + return this._prefill; + }, + /** * Generate a related reference given a foreign key and tuple. * TODO SHOULD BE REFACTORED AS A TYPE OF REFERENCE @@ -6595,3 +6618,108 @@ } }); } + + /** + * + * @param {ERMrest.Reference} reference reference for the assocation table + * @param {Object} prefillObject computed prefill object after extracting the query param and fetching the data from cookie storage + * @param {Object} prefillAnnotation + */ + function Prefill (reference, prefillObject, prefillAnnotation) { + var self = this; + this._reference = reference; + this._prefillObject = prefillObject; + this._prefillAnnotation = prefillAnnotation; + + // ignore the fks that are simple and their constituent column is system col + var nonSystemColumnFks = this._reference.foreignKeys.all().filter((fk) => { + return !(fk.simple && isSystemCol(fk.colset.columns[0])); + }); + + // set of foreignkey columns (they might be overlapping so we're not using array) + var fkCols = {}; + nonSystemColumnFks.forEach((fk) => { + fk.colset.columns.forEach((col) => { + fkCols[col] = true; + }); + }); + + // NOTE: I think this can be done differently. Leaving this alone for now for the initial implementation + reference.columns.forEach((column) => { + // column should be a foreignkey pseudo column + if (!column.isForeignKey) return; + + nonSystemColumnFks.forEach((fk) => { + // column and foreign key `.name` property is a hash value + if (column.name === fk.name) { + if (prefillObject.fkColumnNames.indexOf(column.name) !== -1) { + self.mainColumn = column; + } else { + // TODO: what if there are multiple "other" fk columns? + if (self.leafColumn) { + // throw error saying "more than 1 foreign key column available" + // OR inspect key data and if there is a key with 2 foreign key columns (and 1 is the main column), those are our columns for the association + } + + self.leafColumn = column; + } + } + }); + }); + + this.isAssociation = (this.mainColumn && this.leafColumn); + + this.isUnique = false; + + // to calculate isUnique + // - One of the keys should contain the main and leaf foreign key columns + var tempKeys = this.keys.all().filter((key) => { + var keyCols = key.colset.columns; + return !(keyCols.length == 1 && (module._serialTypes.indexOf(keyCols[0].type.name) != -1 || module._systemColumns.indexOf(keyCols[0].name) != -1) && !(keyCols[0] in fkCols)); + }); + + // if we determine there is a key with 2 foreign key columns in it, should we assume that's the main/leaf columns for the association + // even if there are more than 2 foreign key columns? + console.log('temp keys: ', tempKeys); + } + + Prefill.prototype = { + /** + * @returns {Object[]} filters array for getting the rows that should be disabled + */ + disabledRowsFilter: () => { + var self = this; + + var disabledRowsFilters = []; + Object.keys(this.prefillObject.keys).forEach((key) => { + disabledRowsFilters.push({ + source: [ + { 'inbound': self.leafColumn.foreignKey.constraint_names[0] }, + { 'outbound': self.mainColumn.foreignKey.constraint_names[0] }, + self.mainColumn.foreignKey.mapping._to[0].name + ], + choices: [self.prefillObject.keys[key]] + }); + }); + + return disabledRowsFilters; + }, + + /** + * @returns {Object[]} filters array for ensuring rows from the leaf table are only able to be added if their key information is not null + */ + andFilters: () => { + var andFilters = []; + // loop through all of key columns of the leaf foreign key pseudo column that make up the key information for the leaf table of the association relationship and create non-null filters + // NOTE: this is similar to the function `openAddPureBinaryModal` in `related-table-actions` in chaise + this.leafColumn.foreignKey.key.colset.columns.forEach((col) => { + andFilters.push({ + source: col.name, + hidden: true, + not_null: true + }); + }); + + return andFilters; + } + } From 6f596fd65910d3cd4b3066438ed2e85c9747b659 Mon Sep 17 00:00:00 2001 From: Joshua Chudy Date: Mon, 26 Aug 2024 14:00:37 -0700 Subject: [PATCH 03/15] remove arrow synatx and other bug fixes --- js/reference.js | 52 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/js/reference.js b/js/reference.js index 6c93cc1d..a864d075 100644 --- a/js/reference.js +++ b/js/reference.js @@ -4031,7 +4031,7 @@ if (this._prefill === undefined) { var annotation = null; // var prefillAnno = table.annotations.get(module._annotations.TABLE_DISPLAY).content; - if (prefillAnno) annotation = prefillAnno; + // if (prefillAnno) annotation = prefillAnno; this._prefill = new Prefill(this, prefillObject, annotation); } return this._prefill; @@ -6621,35 +6621,41 @@ /** * - * @param {ERMrest.Reference} reference reference for the assocation table + * @param {ERMrest.Reference} reference reference for the association table * @param {Object} prefillObject computed prefill object after extracting the query param and fetching the data from cookie storage * @param {Object} prefillAnnotation */ function Prefill (reference, prefillObject, prefillAnnotation) { + verify(prefillObject, "'prefillObject' must be defined"); + var self = this; this._reference = reference; this._prefillObject = prefillObject; this._prefillAnnotation = prefillAnnotation; // ignore the fks that are simple and their constituent column is system col - var nonSystemColumnFks = this._reference.foreignKeys.all().filter((fk) => { - return !(fk.simple && isSystemCol(fk.colset.columns[0])); + var nonSystemColumnFks = this._reference.table.foreignKeys.all().filter(function(fk) { + return !(fk.simple && module._systemColumns.indexOf(fk.colset.columns[0]) !== -1); }); // set of foreignkey columns (they might be overlapping so we're not using array) var fkCols = {}; - nonSystemColumnFks.forEach((fk) => { - fk.colset.columns.forEach((col) => { + nonSystemColumnFks.forEach(function(fk) { + fk.colset.columns.forEach(function(col) { fkCols[col] = true; }); }); + // the following are ERMrest.ForeignKeyPseudoColumn + this.mainColumn; + this.leafColumn; + // NOTE: I think this can be done differently. Leaving this alone for now for the initial implementation - reference.columns.forEach((column) => { + reference.columns.forEach(function(column) { // column should be a foreignkey pseudo column if (!column.isForeignKey) return; - nonSystemColumnFks.forEach((fk) => { + nonSystemColumnFks.forEach(function(fk) { // column and foreign key `.name` property is a hash value if (column.name === fk.name) { if (prefillObject.fkColumnNames.indexOf(column.name) !== -1) { @@ -6667,38 +6673,50 @@ }); }); - this.isAssociation = (this.mainColumn && this.leafColumn); + this.isAssociation = (nonSystemColumnFks.length === 2 && this.mainColumn && this.leafColumn); this.isUnique = false; // to calculate isUnique // - One of the keys should contain the main and leaf foreign key columns - var tempKeys = this.keys.all().filter((key) => { + var tempKeys = reference.table.keys.all().filter(function(key) { var keyCols = key.colset.columns; return !(keyCols.length == 1 && (module._serialTypes.indexOf(keyCols[0].type.name) != -1 || module._systemColumns.indexOf(keyCols[0].name) != -1) && !(keyCols[0] in fkCols)); }); // if we determine there is a key with 2 foreign key columns in it, should we assume that's the main/leaf columns for the association // even if there are more than 2 foreign key columns? - console.log('temp keys: ', tempKeys); + tempKeys.forEach(function(key) { + var mainMatch = false, + leafMatch = false; + key.colset.columns.forEach(function(col) { + if (col.name === self.leafColumn._baseCols[0].name) { + leafMatch = true; + } else if (col.name === self.mainColumn._baseCols[0].name) { + mainMatch = true; + } + }); + + if (leafMatch && mainMatch) self.isUnique = true; + }); } Prefill.prototype = { /** * @returns {Object[]} filters array for getting the rows that should be disabled */ - disabledRowsFilter: () => { + disabledRowsFilter: function() { var self = this; var disabledRowsFilters = []; - Object.keys(this.prefillObject.keys).forEach((key) => { + Object.keys(this._prefillObject.keys).forEach(function(key) { disabledRowsFilters.push({ source: [ { 'inbound': self.leafColumn.foreignKey.constraint_names[0] }, { 'outbound': self.mainColumn.foreignKey.constraint_names[0] }, self.mainColumn.foreignKey.mapping._to[0].name ], - choices: [self.prefillObject.keys[key]] + choices: [self._prefillObject.keys[key]] }); }); @@ -6706,13 +6724,13 @@ }, /** - * @returns {Object[]} filters array for ensuring rows from the leaf table are only able to be added if their key information is not null + * @returns filters array to use on leafColumn.reference for ensuring rows from the leaf table are only able to be added if their key information is not null */ - andFilters: () => { + andFiltersForLeaf: function() { var andFilters = []; // loop through all of key columns of the leaf foreign key pseudo column that make up the key information for the leaf table of the association relationship and create non-null filters // NOTE: this is similar to the function `openAddPureBinaryModal` in `related-table-actions` in chaise - this.leafColumn.foreignKey.key.colset.columns.forEach((col) => { + this.leafColumn.foreignKey.key.colset.columns.forEach(function(col) { andFilters.push({ source: col.name, hidden: true, From f479a18ec81746b7757e26732d0669ca9df7cda8 Mon Sep 17 00:00:00 2001 From: Joshua Chudy Date: Tue, 3 Sep 2024 12:16:17 -0700 Subject: [PATCH 04/15] Update changes based on comments. Tests pending --- js/reference.js | 161 +++++++++++++++++++++++++++++------------------- 1 file changed, 98 insertions(+), 63 deletions(-) diff --git a/js/reference.js b/js/reference.js index a864d075..2d3749bf 100644 --- a/js/reference.js +++ b/js/reference.js @@ -4016,25 +4016,22 @@ /** * If prefill object is defined and has the required attributes, will return - * a Prefill object with the necessary objects used for a association modal picker + * a PrefillForCreateAssociation object with the necessary objects used for a association modal picker * - * @type {ERMrest.Prefill} + * @type {ERMrest.PrefillForCreateAssociation} */ - get prefill() { - if (this._prefill === undefined) { - // TODO: error with message "Call computePrefill first" + get prefillForCreateAssociation() { + if (this._prefillForCreateAssociation === undefined) { + verify(false, 'Call "computePrefill" with the prefill object first'); } - return this._prefill; + return this._prefillForCreateAssociation; }, - computePrefill: function (prefillObject) { - if (this._prefill === undefined) { - var annotation = null; - // var prefillAnno = table.annotations.get(module._annotations.TABLE_DISPLAY).content; - // if (prefillAnno) annotation = prefillAnno; - this._prefill = new Prefill(this, prefillObject, annotation); + computePrefillForCreateAssociation: function (prefillObject) { + if (this._prefillForCreateAssociation === undefined) { + this._prefillForCreateAssociation = new PrefillForCreateAssociation(this, prefillObject); } - return this._prefill; + return this._prefillForCreateAssociation; }, /** @@ -6620,20 +6617,25 @@ } /** + * NOTE: Potential improvement to the heuristics when there is no annotation defined + * if we have: + * - >2 FK columns + * - there is a key with 2 foreign key columns in it + * - that key includes the _mainColumn mentioned in prefillObject + * should we assume that's the main/leaf columns for the association? * * @param {ERMrest.Reference} reference reference for the association table - * @param {Object} prefillObject computed prefill object after extracting the query param and fetching the data from cookie storage - * @param {Object} prefillAnnotation + * @param {Object} prefillObject generated prefill object from chaise after extracting the query param and fetching the data from cookie storage */ - function Prefill (reference, prefillObject, prefillAnnotation) { - verify(prefillObject, "'prefillObject' must be defined"); + function PrefillForCreateAssociation (reference, prefillObject) { + if (!prefillObject) return null; var self = this; this._reference = reference; this._prefillObject = prefillObject; - this._prefillAnnotation = prefillAnnotation; // ignore the fks that are simple and their constituent column is system col + // TODO: composite FKs var nonSystemColumnFks = this._reference.table.foreignKeys.all().filter(function(fk) { return !(fk.simple && module._systemColumns.indexOf(fk.colset.columns[0]) !== -1); }); @@ -6646,11 +6648,14 @@ }); }); - // the following are ERMrest.ForeignKeyPseudoColumn - this.mainColumn; - this.leafColumn; + // There have to be 2 foreign key columns + if (nonSystemColumnFks.length !== 2) return null; - // NOTE: I think this can be done differently. Leaving this alone for now for the initial implementation + this._mainColumn = null; + this._leafColumn = null; + + // leafColumn will be set no matter what since the check above ensures there are 2 FK columns + // This makes sure one of the 2 FK columns is the same as the one that initiated the prefill logic in record app reference.columns.forEach(function(column) { // column should be a foreignkey pseudo column if (!column.isForeignKey) return; @@ -6659,85 +6664,115 @@ // column and foreign key `.name` property is a hash value if (column.name === fk.name) { if (prefillObject.fkColumnNames.indexOf(column.name) !== -1) { - self.mainColumn = column; + self._mainColumn = column; } else { - // TODO: what if there are multiple "other" fk columns? - if (self.leafColumn) { - // throw error saying "more than 1 foreign key column available" - // OR inspect key data and if there is a key with 2 foreign key columns (and 1 is the main column), those are our columns for the association - } - - self.leafColumn = column; + self._leafColumn = column; } } }); }); - this.isAssociation = (nonSystemColumnFks.length === 2 && this.mainColumn && this.leafColumn); + if (!this._mainColumn || !this._leafColumn) return null; - this.isUnique = false; + this._isAssociation = true; + this._isUnique = false; - // to calculate isUnique - // - One of the keys should contain the main and leaf foreign key columns var tempKeys = reference.table.keys.all().filter(function(key) { var keyCols = key.colset.columns; return !(keyCols.length == 1 && (module._serialTypes.indexOf(keyCols[0].type.name) != -1 || module._systemColumns.indexOf(keyCols[0].name) != -1) && !(keyCols[0] in fkCols)); }); - // if we determine there is a key with 2 foreign key columns in it, should we assume that's the main/leaf columns for the association - // even if there are more than 2 foreign key columns? + // to calculate isUnique + // - One of the keys should contain the main and leaf foreign key columns tempKeys.forEach(function(key) { var mainMatch = false, leafMatch = false; + key.colset.columns.forEach(function(col) { - if (col.name === self.leafColumn._baseCols[0].name) { + if (col.name === self._leafColumn._baseCols[0].name) { leafMatch = true; - } else if (col.name === self.mainColumn._baseCols[0].name) { + } else if (col.name === self._mainColumn._baseCols[0].name) { mainMatch = true; } }); - if (leafMatch && mainMatch) self.isUnique = true; + if (leafMatch && mainMatch) self._isUnique = true; }); } - Prefill.prototype = { + PrefillForCreateAssociation.prototype = { + // TODO: are the 4 following functions needed in chaise? + /** + * @returns ERMrest.ForeignKeyPseudoColumn + */ + get mainColumn (){ + return this._mainColumn; + }, + + /** + * @returns ERMrest.ForeignKeyPseudoColumn + */ + get leafColumn () { + return this._leafColumn; + }, + + /** + * @returns boolean + */ + get isAssociation () { + return this._isAssociation; + }, + + /** + * @returns boolean + */ + get isUnique () { + return this._isUnique; + }, + /** * @returns {Object[]} filters array for getting the rows that should be disabled */ disabledRowsFilter: function() { - var self = this; + if (this._disabledRowsFilters === undefined) { + var self = this; - var disabledRowsFilters = []; - Object.keys(this._prefillObject.keys).forEach(function(key) { - disabledRowsFilters.push({ - source: [ - { 'inbound': self.leafColumn.foreignKey.constraint_names[0] }, - { 'outbound': self.mainColumn.foreignKey.constraint_names[0] }, - self.mainColumn.foreignKey.mapping._to[0].name - ], - choices: [self._prefillObject.keys[key]] + var filters = []; + Object.keys(this._prefillObject.keys).forEach(function(key) { + filters.push({ + source: [ + { 'inbound': self._leafColumn.foreignKey.constraint_names[0] }, + { 'outbound': self._mainColumn.foreignKey.constraint_names[0] }, + self._mainColumn.foreignKey.mapping._to[0].name + ], + choices: [self._prefillObject.keys[key]] + }); }); - }); - return disabledRowsFilters; + this._disabledRowsFilters = filters; + } + + return this._disabledRowsFilters; }, /** - * @returns filters array to use on leafColumn.reference for ensuring rows from the leaf table are only able to be added if their key information is not null + * @returns {Object[]} filters array to use on leafColumn.reference for ensuring rows from the leaf table are only able to be added if their key information is not null */ andFiltersForLeaf: function() { - var andFilters = []; - // loop through all of key columns of the leaf foreign key pseudo column that make up the key information for the leaf table of the association relationship and create non-null filters - // NOTE: this is similar to the function `openAddPureBinaryModal` in `related-table-actions` in chaise - this.leafColumn.foreignKey.key.colset.columns.forEach(function(col) { - andFilters.push({ - source: col.name, - hidden: true, - not_null: true + if (this._andFilters === undefined) { + var filters = []; + // loop through all of key columns of the leaf foreign key pseudo column that make up the key information for the leaf table of the association relationship and create non-null filters + this._leafColumn.foreignKey.key.colset.columns.forEach(function(col) { + filters.push({ + source: col.name, + hidden: true, + not_null: true + }); }); - }); - return andFilters; + this._andFilters = filters; + } + + return this._andFilters; } } From 81a9cdc6641192b6c4c1665d27838dd4ef41c5dd Mon Sep 17 00:00:00 2001 From: Joshua Chudy Date: Tue, 3 Sep 2024 15:00:58 -0700 Subject: [PATCH 05/15] fix bugs in ermrestJS after refactoring --- js/reference.js | 118 +++++++++++++++++++++++------------------------- 1 file changed, 57 insertions(+), 61 deletions(-) diff --git a/js/reference.js b/js/reference.js index 2d3749bf..ccd4ec4e 100644 --- a/js/reference.js +++ b/js/reference.js @@ -4022,14 +4022,62 @@ */ get prefillForCreateAssociation() { if (this._prefillForCreateAssociation === undefined) { - verify(false, 'Call "computePrefill" with the prefill object first'); + verify(false, 'Call "computePrefillForCreateAssociation" with the prefill object first'); } return this._prefillForCreateAssociation; }, computePrefillForCreateAssociation: function (prefillObject) { if (this._prefillForCreateAssociation === undefined) { - this._prefillForCreateAssociation = new PrefillForCreateAssociation(this, prefillObject); + if (!prefillObject) { + this._prefillForCreateAssociation = null; + } else { + // ignore the fks that are simple and their constituent column is system col + // TODO: composite FKs + var nonSystemColumnFks = this.table.foreignKeys.all().filter(function(fk) { + return !(fk.simple && module._systemColumns.indexOf(fk.colset.columns[0]) !== -1); + }); + + // set of foreignkey columns (they might be overlapping so we're not using array) + var fkCols = {}; + nonSystemColumnFks.forEach(function(fk) { + fk.colset.columns.forEach(function(col) { + fkCols[col] = true; + }); + }); + + // There have to be 2 foreign key columns + if (nonSystemColumnFks.length !== 2) { + this._prefillForCreateAssociation = null; + } else { + var mainColumn = null; + var leafColumn = null; + + // leafColumn will be set no matter what since the check above ensures there are 2 FK columns + // This makes sure one of the 2 FK columns is the same as the one that initiated the prefill logic in record app + this.columns.forEach(function(column) { + // column should be a foreignkey pseudo column + if (!column.isForeignKey) return; + + nonSystemColumnFks.forEach(function(fk) { + // column and foreign key `.name` property is a hash value + if (column.name === fk.name) { + if (prefillObject.fkColumnNames.indexOf(column.name) !== -1) { + mainColumn = column; + } else { + leafColumn = column; + } + } + }); + }); + + if (!mainColumn || !leafColumn) { + this._prefillForCreateAssociation = null; + } else { + this._prefillForCreateAssociation = new PrefillForCreateAssociation(this, prefillObject, fkCols, mainColumn, leafColumn); + } + } + } } return this._prefillForCreateAssociation; }, @@ -6617,6 +6665,9 @@ } /** + * Constructor to create a PrefillForCreateAssociation object. Returns null if the table for the reference + * can not be determined to be an "association" + * * NOTE: Potential improvement to the heuristics when there is no annotation defined * if we have: * - >2 FK columns @@ -6626,55 +6677,15 @@ * * @param {ERMrest.Reference} reference reference for the association table * @param {Object} prefillObject generated prefill object from chaise after extracting the query param and fetching the data from cookie storage + * @param {Object} fkCols set of foreignkey columns that are not system columns (they might be overlapping so we're not using array) */ - function PrefillForCreateAssociation (reference, prefillObject) { - if (!prefillObject) return null; - + function PrefillForCreateAssociation (reference, prefillObject, fkCols, mainColumn, leafColumn) { var self = this; this._reference = reference; this._prefillObject = prefillObject; + this._mainColumn = mainColumn; + this._leafColumn = leafColumn; - // ignore the fks that are simple and their constituent column is system col - // TODO: composite FKs - var nonSystemColumnFks = this._reference.table.foreignKeys.all().filter(function(fk) { - return !(fk.simple && module._systemColumns.indexOf(fk.colset.columns[0]) !== -1); - }); - - // set of foreignkey columns (they might be overlapping so we're not using array) - var fkCols = {}; - nonSystemColumnFks.forEach(function(fk) { - fk.colset.columns.forEach(function(col) { - fkCols[col] = true; - }); - }); - - // There have to be 2 foreign key columns - if (nonSystemColumnFks.length !== 2) return null; - - this._mainColumn = null; - this._leafColumn = null; - - // leafColumn will be set no matter what since the check above ensures there are 2 FK columns - // This makes sure one of the 2 FK columns is the same as the one that initiated the prefill logic in record app - reference.columns.forEach(function(column) { - // column should be a foreignkey pseudo column - if (!column.isForeignKey) return; - - nonSystemColumnFks.forEach(function(fk) { - // column and foreign key `.name` property is a hash value - if (column.name === fk.name) { - if (prefillObject.fkColumnNames.indexOf(column.name) !== -1) { - self._mainColumn = column; - } else { - self._leafColumn = column; - } - } - }); - }); - - if (!this._mainColumn || !this._leafColumn) return null; - - this._isAssociation = true; this._isUnique = false; var tempKeys = reference.table.keys.all().filter(function(key) { @@ -6701,14 +6712,6 @@ } PrefillForCreateAssociation.prototype = { - // TODO: are the 4 following functions needed in chaise? - /** - * @returns ERMrest.ForeignKeyPseudoColumn - */ - get mainColumn (){ - return this._mainColumn; - }, - /** * @returns ERMrest.ForeignKeyPseudoColumn */ @@ -6716,13 +6719,6 @@ return this._leafColumn; }, - /** - * @returns boolean - */ - get isAssociation () { - return this._isAssociation; - }, - /** * @returns boolean */ From f03b2f2fe3052968a448f6ab03ca7b2455c1dabe Mon Sep 17 00:00:00 2001 From: Joshua Chudy Date: Tue, 10 Sep 2024 13:25:24 -0700 Subject: [PATCH 06/15] change constructor name, add test cases --- js/reference.js | 34 +- .../conf/bulk_create_foreign_keys.conf.json | 12 + .../data/association_table_non_unique_fk.json | 1 + .../association_table_w_static_columns.json | 1 + .../data/association_table_w_third_fk.json | 1 + .../data/leaf_table2_for_three_fk.json | 1 + .../data/leaf_table_for_non_unique.json | 1 + .../data/leaf_table_for_static_columns.json | 1 + .../data/leaf_table_for_three_fk.json | 1 + .../data/main_table.json | 1 + .../data/pb_leaf_table.json | 1 + .../pure_and_binary_association_table.json | 1 + .../conf/bulk_create_foreign_keys/schema.json | 593 ++++++++++++++++++ .../tests/20.bulk_create_foreign_keys.js | 245 ++++++++ 14 files changed, 878 insertions(+), 16 deletions(-) create mode 100644 test/specs/reference/conf/bulk_create_foreign_keys.conf.json create mode 100644 test/specs/reference/conf/bulk_create_foreign_keys/data/association_table_non_unique_fk.json create mode 100644 test/specs/reference/conf/bulk_create_foreign_keys/data/association_table_w_static_columns.json create mode 100644 test/specs/reference/conf/bulk_create_foreign_keys/data/association_table_w_third_fk.json create mode 100644 test/specs/reference/conf/bulk_create_foreign_keys/data/leaf_table2_for_three_fk.json create mode 100644 test/specs/reference/conf/bulk_create_foreign_keys/data/leaf_table_for_non_unique.json create mode 100644 test/specs/reference/conf/bulk_create_foreign_keys/data/leaf_table_for_static_columns.json create mode 100644 test/specs/reference/conf/bulk_create_foreign_keys/data/leaf_table_for_three_fk.json create mode 100644 test/specs/reference/conf/bulk_create_foreign_keys/data/main_table.json create mode 100644 test/specs/reference/conf/bulk_create_foreign_keys/data/pb_leaf_table.json create mode 100644 test/specs/reference/conf/bulk_create_foreign_keys/data/pure_and_binary_association_table.json create mode 100644 test/specs/reference/conf/bulk_create_foreign_keys/schema.json create mode 100644 test/specs/reference/tests/20.bulk_create_foreign_keys.js diff --git a/js/reference.js b/js/reference.js index ccd4ec4e..ea1f1339 100644 --- a/js/reference.js +++ b/js/reference.js @@ -4016,21 +4016,21 @@ /** * If prefill object is defined and has the required attributes, will return - * a PrefillForCreateAssociation object with the necessary objects used for a association modal picker + * a BulkCreateForeignKeyObject object with the necessary objects used for a association modal picker * - * @type {ERMrest.PrefillForCreateAssociation} + * @type {ERMrest.BulkCreateForeignKeyObject} */ - get prefillForCreateAssociation() { - if (this._prefillForCreateAssociation === undefined) { - verify(false, 'Call "computePrefillForCreateAssociation" with the prefill object first'); + get bulkCreateForeignKeyObject() { + if (this._bulkCreateForeignKeyObject === undefined) { + verify(false, 'Call "computeBulkCreateForeignKeyObject" with the prefill object first'); } - return this._prefillForCreateAssociation; + return this._bulkCreateForeignKeyObject; }, - computePrefillForCreateAssociation: function (prefillObject) { - if (this._prefillForCreateAssociation === undefined) { + computeBulkCreateForeignKeyObject: function (prefillObject) { + if (this._bulkCreateForeignKeyObject === undefined) { if (!prefillObject) { - this._prefillForCreateAssociation = null; + this._bulkCreateForeignKeyObject = null; } else { // ignore the fks that are simple and their constituent column is system col // TODO: composite FKs @@ -4048,7 +4048,7 @@ // There have to be 2 foreign key columns if (nonSystemColumnFks.length !== 2) { - this._prefillForCreateAssociation = null; + this._bulkCreateForeignKeyObject = null; } else { var mainColumn = null; var leafColumn = null; @@ -4072,14 +4072,14 @@ }); if (!mainColumn || !leafColumn) { - this._prefillForCreateAssociation = null; + this._bulkCreateForeignKeyObject = null; } else { - this._prefillForCreateAssociation = new PrefillForCreateAssociation(this, prefillObject, fkCols, mainColumn, leafColumn); + this._bulkCreateForeignKeyObject = new BulkCreateForeignKeyObject(this, prefillObject, fkCols, mainColumn, leafColumn); } } } } - return this._prefillForCreateAssociation; + return this._bulkCreateForeignKeyObject; }, /** @@ -6665,7 +6665,7 @@ } /** - * Constructor to create a PrefillForCreateAssociation object. Returns null if the table for the reference + * Constructor to create a BulkCreateForeignKeyObject object. Returns null if the table for the reference * can not be determined to be an "association" * * NOTE: Potential improvement to the heuristics when there is no annotation defined @@ -6678,8 +6678,10 @@ * @param {ERMrest.Reference} reference reference for the association table * @param {Object} prefillObject generated prefill object from chaise after extracting the query param and fetching the data from cookie storage * @param {Object} fkCols set of foreignkey columns that are not system columns (they might be overlapping so we're not using array) + * @param {ForeignKeyPseudoColumn} mainColumn the column from the assocation table that points to the main table in the association + * @param {ForeignKeyPseudoColumn} leafColumn the column from the assocation table that points to the leaf table in the association we are selecting rows from to associate to main */ - function PrefillForCreateAssociation (reference, prefillObject, fkCols, mainColumn, leafColumn) { + function BulkCreateForeignKeyObject (reference, prefillObject, fkCols, mainColumn, leafColumn) { var self = this; this._reference = reference; this._prefillObject = prefillObject; @@ -6711,7 +6713,7 @@ }); } - PrefillForCreateAssociation.prototype = { + BulkCreateForeignKeyObject.prototype = { /** * @returns ERMrest.ForeignKeyPseudoColumn */ diff --git a/test/specs/reference/conf/bulk_create_foreign_keys.conf.json b/test/specs/reference/conf/bulk_create_foreign_keys.conf.json new file mode 100644 index 00000000..8384de1b --- /dev/null +++ b/test/specs/reference/conf/bulk_create_foreign_keys.conf.json @@ -0,0 +1,12 @@ +{ + "catalog": {}, + "schema": { + "name": "bulk_create_foreign_keys", + "createNew": true, + "path": "test/specs/reference/conf/bulk_create_foreign_keys/schema.json" + }, + "tables": { + "createNew": true + }, + "entities": {} +} diff --git a/test/specs/reference/conf/bulk_create_foreign_keys/data/association_table_non_unique_fk.json b/test/specs/reference/conf/bulk_create_foreign_keys/data/association_table_non_unique_fk.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/test/specs/reference/conf/bulk_create_foreign_keys/data/association_table_non_unique_fk.json @@ -0,0 +1 @@ +[] diff --git a/test/specs/reference/conf/bulk_create_foreign_keys/data/association_table_w_static_columns.json b/test/specs/reference/conf/bulk_create_foreign_keys/data/association_table_w_static_columns.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/test/specs/reference/conf/bulk_create_foreign_keys/data/association_table_w_static_columns.json @@ -0,0 +1 @@ +[] diff --git a/test/specs/reference/conf/bulk_create_foreign_keys/data/association_table_w_third_fk.json b/test/specs/reference/conf/bulk_create_foreign_keys/data/association_table_w_third_fk.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/test/specs/reference/conf/bulk_create_foreign_keys/data/association_table_w_third_fk.json @@ -0,0 +1 @@ +[] diff --git a/test/specs/reference/conf/bulk_create_foreign_keys/data/leaf_table2_for_three_fk.json b/test/specs/reference/conf/bulk_create_foreign_keys/data/leaf_table2_for_three_fk.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/test/specs/reference/conf/bulk_create_foreign_keys/data/leaf_table2_for_three_fk.json @@ -0,0 +1 @@ +[] diff --git a/test/specs/reference/conf/bulk_create_foreign_keys/data/leaf_table_for_non_unique.json b/test/specs/reference/conf/bulk_create_foreign_keys/data/leaf_table_for_non_unique.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/test/specs/reference/conf/bulk_create_foreign_keys/data/leaf_table_for_non_unique.json @@ -0,0 +1 @@ +[] diff --git a/test/specs/reference/conf/bulk_create_foreign_keys/data/leaf_table_for_static_columns.json b/test/specs/reference/conf/bulk_create_foreign_keys/data/leaf_table_for_static_columns.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/test/specs/reference/conf/bulk_create_foreign_keys/data/leaf_table_for_static_columns.json @@ -0,0 +1 @@ +[] diff --git a/test/specs/reference/conf/bulk_create_foreign_keys/data/leaf_table_for_three_fk.json b/test/specs/reference/conf/bulk_create_foreign_keys/data/leaf_table_for_three_fk.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/test/specs/reference/conf/bulk_create_foreign_keys/data/leaf_table_for_three_fk.json @@ -0,0 +1 @@ +[] diff --git a/test/specs/reference/conf/bulk_create_foreign_keys/data/main_table.json b/test/specs/reference/conf/bulk_create_foreign_keys/data/main_table.json new file mode 100644 index 00000000..9b69094a --- /dev/null +++ b/test/specs/reference/conf/bulk_create_foreign_keys/data/main_table.json @@ -0,0 +1 @@ +[{"id": 1, "details": "record used for association tests with a prefill object"}] diff --git a/test/specs/reference/conf/bulk_create_foreign_keys/data/pb_leaf_table.json b/test/specs/reference/conf/bulk_create_foreign_keys/data/pb_leaf_table.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/test/specs/reference/conf/bulk_create_foreign_keys/data/pb_leaf_table.json @@ -0,0 +1 @@ +[] diff --git a/test/specs/reference/conf/bulk_create_foreign_keys/data/pure_and_binary_association_table.json b/test/specs/reference/conf/bulk_create_foreign_keys/data/pure_and_binary_association_table.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/test/specs/reference/conf/bulk_create_foreign_keys/data/pure_and_binary_association_table.json @@ -0,0 +1 @@ +[] diff --git a/test/specs/reference/conf/bulk_create_foreign_keys/schema.json b/test/specs/reference/conf/bulk_create_foreign_keys/schema.json new file mode 100644 index 00000000..e19d4ca6 --- /dev/null +++ b/test/specs/reference/conf/bulk_create_foreign_keys/schema.json @@ -0,0 +1,593 @@ +{ + "schema_name": "bulk_create_foreign_keys", + "tables": { + "main_table": { + "comment": "table for testing bulk foreign key selection APIs", + "kind": "table", + "table_name": "main_table", + "schema_name": "bulk_create_foreign_keys", + "keys": [ + { + "unique_columns": [ + "id" + ] + } + ], + "foreign_keys": [], + "column_definitions": [ + { + "name": "id", + "nullok": false, + "type": { + "typename": "text" + } + }, + { + "name": "details", + "type": { + "typename": "text" + } + } + ], + "annotations": {} + }, + "association_table_w_static_column": { + "kind": "table", + "schema_name": "bulk_create_foreign_keys", + "table_name": "association_table_w_static_column", + "column_definitions": [ + { + "name": "static_col1", + "type": { + "typename": "int4" + } + }, + { + "name": "static_col2", + "type": { + "typename": "text" + } + }, + { + "name": "static_col3", + "type": { + "typename": "timestamp" + } + }, + { + "name": "main_fk_col", + "nullok": false, + "type": { + "typename": "text" + } + }, + { + "name": "leaf_fk_col", + "nullok": false, + "type": { + "typename": "text" + } + } + ], + "keys": [ + { + "unique_columns": [ + "RID" + ] + }, + { + "unique_columns": [ + "main_fk_col", + "leaf_fk_col" + ] + } + ], + "foreign_keys": [ + { + "foreign_key_columns": [ + { + "schema_name": "bulk_create_foreign_keys", + "table_name": "association_table_w_static_column", + "column_name": "leaf_fk_col" + } + ], + "referenced_columns": [ + { + "schema_name": "bulk_create_foreign_keys", + "table_name": "leaf_table_for_static_columns", + "column_name": "RID" + } + ], + "names": [ + [ + "bulk_create_foreign_keys", + "static_to_leaf_fkey" + ] + ] + }, + { + "foreign_key_columns": [ + { + "schema_name": "bulk_create_foreign_keys", + "table_name": "association_table_w_static_column", + "column_name": "main_fk_col" + } + ], + "referenced_columns": [ + { + "schema_name": "bulk_create_foreign_keys", + "table_name": "main_table", + "column_name": "id" + } + ], + "names": [ + [ + "bulk_create_foreign_keys", + "static_to_main_fkey" + ] + ] + } + ], + "annotations": { + "tag:isrd.isi.edu,2016:visible-columns": { + "entry": [ + "static_col1", + "static_col2", + "static_col3", + [ + "bulk_create_foreign_keys", + "static_to_main_fkey" + ], + [ + "bulk_create_foreign_keys", + "static_to_leaf_fkey" + ] + ], + "compact": "entry", + "detailed": "entry" + } + } + }, + "association_table_non_unique_fk": { + "kind": "table", + "schema_name": "bulk_create_foreign_keys", + "table_name": "association_table_non_unique_fk", + "column_definitions": [ + { + "name": "main_fk_col", + "nullok": false, + "type": { + "typename": "text" + } + }, + { + "name": "leaf_fk_col", + "nullok": false, + "type": { + "typename": "text" + } + } + ], + "keys": [ + { + "unique_columns": [ + "RID" + ] + } + ], + "foreign_keys": [ + { + "foreign_key_columns": [ + { + "schema_name": "bulk_create_foreign_keys", + "table_name": "association_table_non_unique_fk", + "column_name": "leaf_fk_col" + } + ], + "referenced_columns": [ + { + "schema_name": "bulk_create_foreign_keys", + "table_name": "leaf_table_for_non_unique", + "column_name": "RID" + } + ], + "names": [ + [ + "bulk_create_foreign_keys", + "non_unique_to_leaf_fkey" + ] + ] + }, + { + "foreign_key_columns": [ + { + "schema_name": "bulk_create_foreign_keys", + "table_name": "association_table_non_unique_fk", + "column_name": "main_fk_col" + } + ], + "referenced_columns": [ + { + "schema_name": "bulk_create_foreign_keys", + "table_name": "main_table", + "column_name": "id" + } + ], + "names": [ + [ + "bulk_create_foreign_keys", + "non_unique_to_main_fkey" + ] + ] + } + ], + "annotations": { + "tag:isrd.isi.edu,2016:visible-columns": { + "entry": [ + [ + "bulk_create_foreign_keys", + "non_unique_to_main_fkey" + ], + [ + "bulk_create_foreign_keys", + "non_unique_to_leaf_fkey" + ] + ], + "compact": "entry", + "detailed": "entry" + } + } + }, + "association_table_w_third_fk": { + "kind": "table", + "schema_name": "bulk_create_foreign_keys", + "table_name": "association_table_w_third_fk", + "column_definitions": [ + { + "name": "main_fk_col", + "nullok": false, + "type": { + "typename": "text" + } + }, + { + "name": "leaf_fk_col", + "nullok": false, + "type": { + "typename": "text" + } + }, + { + "name": "leaf_fk2_col", + "type": { + "typename": "text" + } + } + ], + "keys": [ + { + "unique_columns": [ + "RID" + ] + }, + { + "unique_columns": [ + "main_fk_col", + "leaf_fk_col" + ] + } + ], + "foreign_keys": [ + { + "foreign_key_columns": [ + { + "schema_name": "bulk_create_foreign_keys", + "table_name": "association_table_w_third_fk", + "column_name": "leaf_fk_col" + } + ], + "referenced_columns": [ + { + "schema_name": "bulk_create_foreign_keys", + "table_name": "leaf_table_for_three_fk", + "column_name": "RID" + } + ], + "names": [ + [ + "bulk_create_foreign_keys", + "three_fks_to_leaf_fkey" + ] + ] + }, + { + "foreign_key_columns": [ + { + "schema_name": "bulk_create_foreign_keys", + "table_name": "association_table_w_third_fk", + "column_name": "main_fk_col" + } + ], + "referenced_columns": [ + { + "schema_name": "bulk_create_foreign_keys", + "table_name": "main_table", + "column_name": "id" + } + ], + "names": [ + [ + "bulk_create_foreign_keys", + "three_fks_to_main_fkey" + ] + ] + }, + { + "foreign_key_columns": [ + { + "schema_name": "bulk_create_foreign_keys", + "table_name": "association_table_w_third_fk", + "column_name": "leaf_fk2_col" + } + ], + "referenced_columns": [ + { + "schema_name": "bulk_create_foreign_keys", + "table_name": "leaf_table2_for_three_fk", + "column_name": "RID" + } + ], + "names": [ + [ + "bulk_create_foreign_keys", + "three_fks_to_leaf2_fkey" + ] + ] + } + ], + "annotations": { + "tag:isrd.isi.edu,2016:visible-columns": { + "entry": [ + [ + "bulk_create_foreign_keys", + "three_fks_to_main_fkey" + ], + [ + "bulk_create_foreign_keys", + "three_fks_to_leaf_fkey" + ], + [ + "bulk_create_foreign_keys", + "three_fks_to_leaf2_fkey" + ] + ], + "compact": "entry", + "detailed": "entry" + } + } + }, + "pure_and_binary_association_table": { + "kind": "table", + "schema_name": "bulk_create_foreign_keys", + "table_name": "pure_and_binary_association_table", + "column_definitions": [ + { + "name": "main_fk_col", + "nullok": false, + "type": { + "typename": "text" + } + }, + { + "name": "leaf_fk_col", + "nullok": false, + "type": { + "typename": "text" + } + } + ], + "keys": [ + { + "unique_columns": [ + "RID" + ] + }, + { + "unique_columns": [ + "main_fk_col", + "leaf_fk_col" + ] + } + ], + "foreign_keys": [ + { + "foreign_key_columns": [ + { + "schema_name": "bulk_create_foreign_keys", + "table_name": "pure_and_binary_association_table", + "column_name": "leaf_fk_col" + } + ], + "referenced_columns": [ + { + "schema_name": "bulk_create_foreign_keys", + "table_name": "pb_leaf_table", + "column_name": "RID" + } + ], + "names": [ + [ + "bulk_create_foreign_keys", + "pb_to_leaf_fkey" + ] + ] + }, + { + "foreign_key_columns": [ + { + "schema_name": "bulk_create_foreign_keys", + "table_name": "pure_and_binary_association_table", + "column_name": "main_fk_col" + } + ], + "referenced_columns": [ + { + "schema_name": "bulk_create_foreign_keys", + "table_name": "main_table", + "column_name": "id" + } + ], + "names": [ + [ + "bulk_create_foreign_keys", + "pb_to_main_fkey" + ] + ] + } + ], + "annotations": { + "tag:isrd.isi.edu,2016:visible-columns": { + "entry": [ + [ + "bulk_create_foreign_keys", + "pb_to_main_fkey" + ], + [ + "bulk_create_foreign_keys", + "pb_to_leaf_fkey" + ] + ], + "compact": "entry", + "detailed": "entry" + } + } + }, + "leaf_table_for_static_columns": { + "comment": "leaf table for testing an association with static columns", + "kind": "table", + "table_name": "leaf_table_for_static_columns", + "schema_name": "bulk_create_foreign_keys", + "keys": [ + { + "unique_columns": [ + "RID" + ] + } + ], + "foreign_keys": [], + "column_definitions": [ + { + "name": "details", + "type": { + "typename": "text" + } + } + ], + "annotations": {} + }, + "leaf_table_for_non_unique": { + "comment": "leaf table for testing an association with non unqiue fk columns", + "kind": "table", + "table_name": "leaf_table_for_non_unique", + "schema_name": "bulk_create_foreign_keys", + "keys": [ + { + "unique_columns": [ + "RID" + ] + } + ], + "foreign_keys": [], + "column_definitions": [ + { + "name": "details", + "type": { + "typename": "text" + } + } + ], + "annotations": {} + }, + "leaf_table_for_three_fk": { + "comment": "leaf table for testing an association with 3 fk columns", + "kind": "table", + "table_name": "leaf_table_for_three_fk", + "schema_name": "bulk_create_foreign_keys", + "keys": [ + { + "unique_columns": [ + "RID" + ] + } + ], + "foreign_keys": [], + "column_definitions": [ + { + "name": "details", + "type": { + "typename": "text" + } + } + ], + "annotations": {} + }, + "leaf_table2_for_three_fk": { + "comment": "2nd leaf table for testing an association with 3 fk columns", + "kind": "table", + "table_name": "leaf_table2_for_three_fk", + "schema_name": "bulk_create_foreign_keys", + "keys": [ + { + "unique_columns": [ + "RID" + ] + } + ], + "foreign_keys": [], + "column_definitions": [ + { + "name": "details", + "type": { + "typename": "text" + } + } + ], + "annotations": {} + }, + "pb_leaf_table": { + "comment": "leaf table for testing a pure and binary association", + "kind": "table", + "table_name": "pb_leaf_table", + "schema_name": "bulk_create_foreign_keys", + "keys": [ + { + "unique_columns": [ + "RID" + ] + } + ], + "foreign_keys": [], + "column_definitions": [ + { + "name": "details", + "type": { + "typename": "text" + } + } + ], + "annotations": {} + } + }, + "table_names": [ + "main_table", + "association_table_w_static_column", + "association_table_non_unique_fk", + "association_table_w_third_fk", + "pure_and_binary_association_table", + "leaf_table_for_static_columns", + "leaf_table_for_non_unique", + "leaf_table_for_three_fk", + "leaf_table2_for_three_fk", + "pb_leaf_table" + ] +} diff --git a/test/specs/reference/tests/20.bulk_create_foreign_keys.js b/test/specs/reference/tests/20.bulk_create_foreign_keys.js new file mode 100644 index 00000000..054abcca --- /dev/null +++ b/test/specs/reference/tests/20.bulk_create_foreign_keys.js @@ -0,0 +1,245 @@ +const utils = require("../../../utils/utilities.js"); + +exports.execute = (options) =>{ + + describe("bulk foreign key APIs for recordedit with prefill", () => { + + const catalog_id = process.env.DEFAULT_CATALOG, schema_name = "bulk_create_foreign_keys"; + + // main_table <- association_table_w_static_column -> leaf_table_for_static_columns + describe("for association table with static columns", () => { + + var table_name = "association_table_w_static_column"; + + var uri = `${options.url}/catalog/${catalog_id}/entity/${schema_name}:${table_name}`; + + var reference; + + beforeAll((done) => { + options.ermRest.resolve(uri, { cid: "test" }).then((response) => { + reference = response.contextualize.entryCreate; + + done(); + }, (err) => { + console.dir(err); + done.fail(); + }).catch((err) => { + console.dir(err); + done.fail(); + }); + }); + + it("should have the expected columns for entry create context", () => { + expect(reference.columns.length).toBe(5); + }); + + it("should throw an error trying to access bulkCreateForeignKeyObject before computing it'", () => { + try { + // try to access bulk create object + var prefillAPIs = reference.bulkCreateForeignKeyObject; + } catch (e) { + expect(e.message).toBe("Call \"computeBulkCreateForeignKeyObject\" with the prefill object first"); + } + }); + + it("should return null when there is no prefill object when calling 'computeBulkCreateForeignKeyObject'", () => { + reference.computeBulkCreateForeignKeyObject(null); + expect(reference.bulkCreateForeignKeyObject).toBeNull(); + }); + + it("should have proper values for bulk create object when prefill object is defined", () => { + // there are other keys for this object but only these 2 keys are used by ermrestJS + var prefillObject = { + fkColumnNames: ['iLtVyK4V4RngvckI34y2yQ'], + keys: {main_fkey_col: '1'} + } + + reference._bulkCreateForeignKeyObject = undefined; + reference.computeBulkCreateForeignKeyObject(prefillObject); + + expect(reference.bulkCreateForeignKeyObject).not.toBeNull(); + expect(reference.bulkCreateForeignKeyObject.leafColumn.name).toBe('nxJv7zX-Kq4S2B-Avpr4tA'); + expect(reference.bulkCreateForeignKeyObject.isUnique).toBeTruthy(); + + var andFilters = [{ + source: 'RID', + hidden: true, + not_null: true + }]; + expect(reference.bulkCreateForeignKeyObject.andFiltersForLeaf()).toEqual(andFilters) + + var disabledFilter = [{ + source: [ + { 'inbound': ['bulk_create_foreign_keys', 'static_to_leaf_fkey' ]}, + { 'outbound': ['bulk_create_foreign_keys', 'static_to_main_fkey' ]}, + 'id' + ], + choices: ['1'] + }] + expect(reference.bulkCreateForeignKeyObject.disabledRowsFilter()).toEqual(disabledFilter); + }); + }); + + // main_table <- association_table_non_unique_fk -> leaf_table_for_non_unique + describe("for association table with non unique fk columns", () => { + + var table_name = "association_table_non_unique_fk"; + + var uri = `${options.url}/catalog/${catalog_id}/entity/${schema_name}:${table_name}`; + + var reference; + + beforeAll((done) => { + options.ermRest.resolve(uri, { cid: "test" }).then((response) => { + reference = response.contextualize.entryCreate; + + done(); + }, (err) => { + console.dir(err); + done.fail(); + }).catch((err) => { + console.dir(err); + done.fail(); + }); + }); + + it("should have the expected columns for entry create context", () => { + expect(reference.columns.length).toBe(2); + }); + + it("should have proper values for bulk create object when prefill object is defined", () => { + // there are other keys for this object but only these 2 keys are used by ermrestJS + var prefillObject = { + fkColumnNames: ['UKzE0bKxIrwe4bfW_9v1TA'], + keys: {main_fkey_col: '1'} + } + + reference.computeBulkCreateForeignKeyObject(prefillObject); + + expect(reference.bulkCreateForeignKeyObject).not.toBeNull(); + expect(reference.bulkCreateForeignKeyObject.leafColumn.name).toBe('2CMHw2Q8LcUEWID9Lo2pbw'); + expect(reference.bulkCreateForeignKeyObject.isUnique).toBeFalsy(); + + var andFilters = [{ + source: 'RID', + hidden: true, + not_null: true + }]; + expect(reference.bulkCreateForeignKeyObject.andFiltersForLeaf()).toEqual(andFilters) + + var disabledFilter = [{ + source: [ + { 'inbound': ['bulk_create_foreign_keys', 'non_unique_to_leaf_fkey' ]}, + { 'outbound': ['bulk_create_foreign_keys', 'non_unique_to_main_fkey' ]}, + 'id' + ], + choices: ['1'] + }] + expect(reference.bulkCreateForeignKeyObject.disabledRowsFilter()).toEqual(disabledFilter); + }); + }); + + // main_table <- association_table_w_third_fk -> leaf_table_for_three_fk + describe("for association table with three fk columns", () => { + + var table_name = "association_table_w_third_fk"; + + var uri = `${options.url}/catalog/${catalog_id}/entity/${schema_name}:${table_name}`; + + var reference; + + beforeAll((done) => { + options.ermRest.resolve(uri, { cid: "test" }).then((response) => { + reference = response.contextualize.entryCreate; + + done(); + }, (err) => { + console.dir(err); + done.fail(); + }).catch((err) => { + console.dir(err); + done.fail(); + }); + }); + + it("should have the expected columns for entry create context", () => { + expect(reference.columns.length).toBe(3); + }); + + it("should return null even if prefill object is defined since there are more than 3 foreign keys", () => { + // there are other keys for this object but only these 2 keys are used by ermrestJS + var prefillObject = { + fkColumnNames: ['Ph5gK9Kje1ukxLFYC9sJ3A'], + keys: {main_fkey_col: '1'} + } + + reference.computeBulkCreateForeignKeyObject(prefillObject); + + expect(reference.bulkCreateForeignKeyObject).toBeNull(); + }); + }); + + /** + * NOTE: pure and binary association tables would use this mode when there is a prefill object defined + * but that should never be the case since p&b tables use a different UI workflow for adding records + */ + // main_table <- pure_and_binary_association_table -> pb_leaf_table + describe("for pure and binary association table", () => { + + var table_name = "pure_and_binary_association_table"; + + var uri = `${options.url}/catalog/${catalog_id}/entity/${schema_name}:${table_name}`; + + var reference; + + beforeAll((done) => { + options.ermRest.resolve(uri, { cid: "test" }).then((response) => { + reference = response.contextualize.entryCreate; + + done(); + }, (err) => { + console.dir(err); + done.fail(); + }).catch((err) => { + console.dir(err); + done.fail(); + }); + }); + + it("should have the expected columns for entry create context", () => { + expect(reference.columns.length).toBe(2); + }); + + it("should have proper values for bulk create object when prefill object is defined", () => { + // there are other keys for this object but only these 2 keys are used by ermrestJS + var prefillObject = { + fkColumnNames: ['bInlkY4Cbz2Ez4usvGekSA'], + keys: {main_fkey_col: '1'} + } + + reference.computeBulkCreateForeignKeyObject(prefillObject); + + expect(reference.bulkCreateForeignKeyObject).not.toBeNull(); + expect(reference.bulkCreateForeignKeyObject.leafColumn.name).toBe('yrf6sBjcm5XOF_VlzP89TQ'); + expect(reference.bulkCreateForeignKeyObject.isUnique).toBeTruthy(); + + var andFilters = [{ + source: 'RID', + hidden: true, + not_null: true + }]; + expect(reference.bulkCreateForeignKeyObject.andFiltersForLeaf()).toEqual(andFilters) + + var disabledFilter = [{ + source: [ + { 'inbound': ['bulk_create_foreign_keys', 'pb_to_leaf_fkey' ]}, + { 'outbound': ['bulk_create_foreign_keys', 'pb_to_main_fkey' ]}, + 'id' + ], + choices: ['1'] + }] + expect(reference.bulkCreateForeignKeyObject.disabledRowsFilter()).toEqual(disabledFilter); + }); + }); + }); +}; From 50f593c152535da4792474d4ae4c3ff756b374a9 Mon Sep 17 00:00:00 2001 From: Joshua Chudy Date: Fri, 13 Sep 2024 15:59:13 -0700 Subject: [PATCH 07/15] update comment --- js/reference.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/js/reference.js b/js/reference.js index ea1f1339..fbfb0eb5 100644 --- a/js/reference.js +++ b/js/reference.js @@ -4027,6 +4027,15 @@ return this._bulkCreateForeignKeyObject; }, + /** + * Will compute and return a BulkCreateForeignKeyObject if: + * - the prefillObject is defined + * - there are only 2 foreign key columns for this table that are not system columns + * - using the prefill object, we can determine the main column for prefilling and leaf column for bulk selection + * + * @param {Object} prefillObject computed prefill object from chaise + * @returns {BulkCreateForeignKeyObject} + */ computeBulkCreateForeignKeyObject: function (prefillObject) { if (this._bulkCreateForeignKeyObject === undefined) { if (!prefillObject) { @@ -6665,8 +6674,7 @@ } /** - * Constructor to create a BulkCreateForeignKeyObject object. Returns null if the table for the reference - * can not be determined to be an "association" + * Constructor to create a BulkCreateForeignKeyObject object * * NOTE: Potential improvement to the heuristics when there is no annotation defined * if we have: From 97d59670c6ee539556860993cdc43fa03a77852b Mon Sep 17 00:00:00 2001 From: Joshua Chudy Date: Mon, 23 Sep 2024 17:56:05 -0700 Subject: [PATCH 08/15] make sure 2 foreign keys are not composite and add a test case for that --- js/reference.js | 50 +++--- .../association_table_w_composite_fk.json | 1 + .../data/leaf_table_for_composite_fk.json | 1 + .../data/main_table.json | 2 +- .../conf/bulk_create_foreign_keys/schema.json | 152 ++++++++++++++++++ .../tests/20.bulk_create_foreign_keys.js | 48 +++++- 6 files changed, 226 insertions(+), 28 deletions(-) create mode 100644 test/specs/reference/conf/bulk_create_foreign_keys/data/association_table_w_composite_fk.json create mode 100644 test/specs/reference/conf/bulk_create_foreign_keys/data/leaf_table_for_composite_fk.json diff --git a/js/reference.js b/js/reference.js index fbfb0eb5..6f6aee03 100644 --- a/js/reference.js +++ b/js/reference.js @@ -4042,7 +4042,6 @@ this._bulkCreateForeignKeyObject = null; } else { // ignore the fks that are simple and their constituent column is system col - // TODO: composite FKs var nonSystemColumnFks = this.table.foreignKeys.all().filter(function(fk) { return !(fk.simple && module._systemColumns.indexOf(fk.colset.columns[0]) !== -1); }); @@ -4059,31 +4058,36 @@ if (nonSystemColumnFks.length !== 2) { this._bulkCreateForeignKeyObject = null; } else { - var mainColumn = null; - var leafColumn = null; - - // leafColumn will be set no matter what since the check above ensures there are 2 FK columns - // This makes sure one of the 2 FK columns is the same as the one that initiated the prefill logic in record app - this.columns.forEach(function(column) { - // column should be a foreignkey pseudo column - if (!column.isForeignKey) return; - - nonSystemColumnFks.forEach(function(fk) { - // column and foreign key `.name` property is a hash value - if (column.name === fk.name) { - if (prefillObject.fkColumnNames.indexOf(column.name) !== -1) { - mainColumn = column; - } else { - leafColumn = column; + // both foreign keys have to be simple + if (!nonSystemColumnFks[0].simple || !nonSystemColumnFks[1].simple) { + this._bulkCreateForeignKeyObject = null; + } else { + var mainColumn = null; + var leafColumn = null; + + // leafColumn will be set no matter what since the check above ensures there are 2 FK columns + // This makes sure one of the 2 FK columns is the same as the one that initiated the prefill logic in record app + this.columns.forEach(function(column) { + // column should be a foreignkey pseudo column + if (!column.isForeignKey) return; + + nonSystemColumnFks.forEach(function(fk) { + // column and foreign key `.name` property is a hash value + if (column.name === fk.name) { + if (prefillObject.fkColumnNames.indexOf(column.name) !== -1) { + mainColumn = column; + } else { + leafColumn = column; + } } - } + }); }); - }); - if (!mainColumn || !leafColumn) { - this._bulkCreateForeignKeyObject = null; - } else { - this._bulkCreateForeignKeyObject = new BulkCreateForeignKeyObject(this, prefillObject, fkCols, mainColumn, leafColumn); + if (!mainColumn || !leafColumn) { + this._bulkCreateForeignKeyObject = null; + } else { + this._bulkCreateForeignKeyObject = new BulkCreateForeignKeyObject(this, prefillObject, fkCols, mainColumn, leafColumn); + } } } } diff --git a/test/specs/reference/conf/bulk_create_foreign_keys/data/association_table_w_composite_fk.json b/test/specs/reference/conf/bulk_create_foreign_keys/data/association_table_w_composite_fk.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/test/specs/reference/conf/bulk_create_foreign_keys/data/association_table_w_composite_fk.json @@ -0,0 +1 @@ +[] diff --git a/test/specs/reference/conf/bulk_create_foreign_keys/data/leaf_table_for_composite_fk.json b/test/specs/reference/conf/bulk_create_foreign_keys/data/leaf_table_for_composite_fk.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/test/specs/reference/conf/bulk_create_foreign_keys/data/leaf_table_for_composite_fk.json @@ -0,0 +1 @@ +[] diff --git a/test/specs/reference/conf/bulk_create_foreign_keys/data/main_table.json b/test/specs/reference/conf/bulk_create_foreign_keys/data/main_table.json index 9b69094a..423057db 100644 --- a/test/specs/reference/conf/bulk_create_foreign_keys/data/main_table.json +++ b/test/specs/reference/conf/bulk_create_foreign_keys/data/main_table.json @@ -1 +1 @@ -[{"id": 1, "details": "record used for association tests with a prefill object"}] +[{"id": 1, "id2": 1, "details": "record used for association tests with a prefill object"}] diff --git a/test/specs/reference/conf/bulk_create_foreign_keys/schema.json b/test/specs/reference/conf/bulk_create_foreign_keys/schema.json index e19d4ca6..9c58bb77 100644 --- a/test/specs/reference/conf/bulk_create_foreign_keys/schema.json +++ b/test/specs/reference/conf/bulk_create_foreign_keys/schema.json @@ -11,6 +11,12 @@ "unique_columns": [ "id" ] + }, + { + "unique_columns": [ + "id", + "id2" + ] } ], "foreign_keys": [], @@ -22,6 +28,13 @@ "typename": "text" } }, + { + "name": "id2", + "nullok": false, + "type": { + "typename": "text" + } + }, { "name": "details", "type": { @@ -366,6 +379,120 @@ } } }, + "association_table_w_composite_fk": { + "kind": "table", + "schema_name": "bulk_create_foreign_keys", + "table_name": "association_table_w_composite_fk", + "column_definitions": [ + { + "name": "main_fk_col1", + "nullok": false, + "type": { + "typename": "text" + } + }, + { + "name": "main_fk_col2", + "nullok": false, + "type": { + "typename": "text" + } + }, + { + "name": "leaf_fk_col", + "nullok": false, + "type": { + "typename": "text" + } + } + ], + "keys": [ + { + "unique_columns": [ + "RID" + ] + }, + { + "unique_columns": [ + "main_fk_col1", + "main_fk_col2", + "leaf_fk_col" + ] + } + ], + "foreign_keys": [ + { + "foreign_key_columns": [ + { + "schema_name": "bulk_create_foreign_keys", + "table_name": "association_table_w_composite_fk", + "column_name": "leaf_fk_col" + } + ], + "referenced_columns": [ + { + "schema_name": "bulk_create_foreign_keys", + "table_name": "leaf_table_for_composite_fk", + "column_name": "RID" + } + ], + "names": [ + [ + "bulk_create_foreign_keys", + "composite_to_leaf_fkey" + ] + ] + }, + { + "foreign_key_columns": [ + { + "schema_name": "bulk_create_foreign_keys", + "table_name": "association_table_w_composite_fk", + "column_name": "main_fk_col1" + }, + { + "schema_name": "bulk_create_foreign_keys", + "table_name": "association_table_w_composite_fk", + "column_name": "main_fk_col2" + } + ], + "referenced_columns": [ + { + "schema_name": "bulk_create_foreign_keys", + "table_name": "main_table", + "column_name": "id" + }, + { + "schema_name": "bulk_create_foreign_keys", + "table_name": "main_table", + "column_name": "id2" + } + ], + "names": [ + [ + "bulk_create_foreign_keys", + "composite_to_main_fkey" + ] + ] + } + ], + "annotations": { + "tag:isrd.isi.edu,2016:visible-columns": { + "entry": [ + [ + "bulk_create_foreign_keys", + "composite_to_main_fkey" + ], + [ + "bulk_create_foreign_keys", + "composite_to_leaf_fkey" + ] + ], + "compact": "entry", + "detailed": "entry" + } + } + }, "pure_and_binary_association_table": { "kind": "table", "schema_name": "bulk_create_foreign_keys", @@ -554,6 +681,29 @@ ], "annotations": {} }, + "leaf_table_for_composite_fk": { + "comment": "leaf table for testing an association with 3 fk columns", + "kind": "table", + "table_name": "leaf_table_for_composite_fk", + "schema_name": "bulk_create_foreign_keys", + "keys": [ + { + "unique_columns": [ + "RID" + ] + } + ], + "foreign_keys": [], + "column_definitions": [ + { + "name": "details", + "type": { + "typename": "text" + } + } + ], + "annotations": {} + }, "pb_leaf_table": { "comment": "leaf table for testing a pure and binary association", "kind": "table", @@ -583,10 +733,12 @@ "association_table_w_static_column", "association_table_non_unique_fk", "association_table_w_third_fk", + "association_table_w_composite_fk", "pure_and_binary_association_table", "leaf_table_for_static_columns", "leaf_table_for_non_unique", "leaf_table_for_three_fk", + "leaf_table_for_composite_fk", "leaf_table2_for_three_fk", "pb_leaf_table" ] diff --git a/test/specs/reference/tests/20.bulk_create_foreign_keys.js b/test/specs/reference/tests/20.bulk_create_foreign_keys.js index 054abcca..1cdae188 100644 --- a/test/specs/reference/tests/20.bulk_create_foreign_keys.js +++ b/test/specs/reference/tests/20.bulk_create_foreign_keys.js @@ -51,7 +51,7 @@ exports.execute = (options) =>{ // there are other keys for this object but only these 2 keys are used by ermrestJS var prefillObject = { fkColumnNames: ['iLtVyK4V4RngvckI34y2yQ'], - keys: {main_fkey_col: '1'} + keys: {main_fk_col: '1'} } reference._bulkCreateForeignKeyObject = undefined; @@ -111,7 +111,7 @@ exports.execute = (options) =>{ // there are other keys for this object but only these 2 keys are used by ermrestJS var prefillObject = { fkColumnNames: ['UKzE0bKxIrwe4bfW_9v1TA'], - keys: {main_fkey_col: '1'} + keys: {main_fk_col: '1'} } reference.computeBulkCreateForeignKeyObject(prefillObject); @@ -170,7 +170,47 @@ exports.execute = (options) =>{ // there are other keys for this object but only these 2 keys are used by ermrestJS var prefillObject = { fkColumnNames: ['Ph5gK9Kje1ukxLFYC9sJ3A'], - keys: {main_fkey_col: '1'} + keys: {main_fk_col: '1'} + } + + reference.computeBulkCreateForeignKeyObject(prefillObject); + + expect(reference.bulkCreateForeignKeyObject).toBeNull(); + }); + }); + + // main_table <- association_table_w_composite_fk -> leaf_table_for_composite_fk + describe("for association table with a composite foreign key", () => { + + var table_name = "association_table_w_composite_fk"; + + var uri = `${options.url}/catalog/${catalog_id}/entity/${schema_name}:${table_name}`; + + var reference; + + beforeAll((done) => { + options.ermRest.resolve(uri, { cid: "test" }).then((response) => { + reference = response.contextualize.entryCreate; + + done(); + }, (err) => { + console.dir(err); + done.fail(); + }).catch((err) => { + console.dir(err); + done.fail(); + }); + }); + + it("should have the expected columns for entry create context", () => { + expect(reference.columns.length).toBe(2); + }); + + it("should return null even if prefill object is defined since there is a composite foreign key", () => { + // there are other keys for this object but only these 2 keys are used by ermrestJS + var prefillObject = { + fkColumnNames: ['o1jpzMQ3kaAxJpcXXUWbsA'], + keys: { main_fk_col1: '1', main_fk_col2: '1' } } reference.computeBulkCreateForeignKeyObject(prefillObject); @@ -214,7 +254,7 @@ exports.execute = (options) =>{ // there are other keys for this object but only these 2 keys are used by ermrestJS var prefillObject = { fkColumnNames: ['bInlkY4Cbz2Ez4usvGekSA'], - keys: {main_fkey_col: '1'} + keys: {main_fk_col: '1'} } reference.computeBulkCreateForeignKeyObject(prefillObject); From 6176ef7139ecfcb8afe6dd669ffe7b2bb49dab7c Mon Sep 17 00:00:00 2001 From: Joshua Chudy Date: Wed, 25 Sep 2024 15:20:52 -0700 Subject: [PATCH 09/15] return null instead of an error, add comments --- js/reference.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/js/reference.js b/js/reference.js index 6f6aee03..1fce1426 100644 --- a/js/reference.js +++ b/js/reference.js @@ -4022,7 +4022,7 @@ */ get bulkCreateForeignKeyObject() { if (this._bulkCreateForeignKeyObject === undefined) { - verify(false, 'Call "computeBulkCreateForeignKeyObject" with the prefill object first'); + this._bulkCreateForeignKeyObject = null; } return this._bulkCreateForeignKeyObject; }, @@ -6727,6 +6727,7 @@ BulkCreateForeignKeyObject.prototype = { /** + * the column that points to the table that rows are being selected from * @returns ERMrest.ForeignKeyPseudoColumn */ get leafColumn () { @@ -6734,6 +6735,7 @@ }, /** + * if the 2 foreign key columns are part of a unqiue key * @returns boolean */ get isUnique () { @@ -6766,12 +6768,12 @@ }, /** - * @returns {Object[]} filters array to use on leafColumn.reference for ensuring rows from the leaf table are only able to be added if their key information is not null + * @returns {Object[]} filters array to use on leafColumn.reference for ensuring rows from the table are only able to be added if their key information is not null */ andFiltersForLeaf: function() { if (this._andFilters === undefined) { var filters = []; - // loop through all of key columns of the leaf foreign key pseudo column that make up the key information for the leaf table of the association relationship and create non-null filters + // loop through all of key columns of the leaf foreign key pseudo column that make up the key information for the tablerows are selected from and create non-null filters this._leafColumn.foreignKey.key.colset.columns.forEach(function(col) { filters.push({ source: col.name, From 78bd7b2976fd44a11e2d1ba3dcdb65ceec335ae7 Mon Sep 17 00:00:00 2001 From: Joshua Chudy Date: Thu, 26 Sep 2024 10:15:47 -0700 Subject: [PATCH 10/15] add a new 'context' for reference for selecting foreign key values in bulk in recordedit --- js/reference.js | 8 ++++++++ js/utils/constants.js | 1 + 2 files changed, 9 insertions(+) diff --git a/js/reference.js b/js/reference.js index 1fce1426..a1b1adad 100644 --- a/js/reference.js +++ b/js/reference.js @@ -4823,6 +4823,14 @@ return this._contextualize(module._contexts.COMPACT_SELECT_FOREIGN_KEY); }, + /** + * The _compact/select/foreign_key/bulk_ context of this reference. + * @type {ERMrest.Reference} + */ + get compactSelectBulkForeignKey() { + return this._contextualize(module._contexts.COMPACT_SELECT_BULK_FOREIGN_KEY); + }, + /** * The _compact/select/saved_queries_ context of this reference. * @type {ERMrest.Reference} diff --git a/js/utils/constants.js b/js/utils/constants.js index 89c32f76..0e300350 100644 --- a/js/utils/constants.js +++ b/js/utils/constants.js @@ -90,6 +90,7 @@ COMPACT_SELECT_ASSOCIATION_LINK: 'compact/select/association/link', COMPACT_SELECT_ASSOCIATION_UNLINK: 'compact/select/association/unlink', COMPACT_SELECT_FOREIGN_KEY: 'compact/select/foreign_key', + COMPACT_SELECT_BULK_FOREIGN_KEY: 'compact/select/foreign_key/bulk', COMPACT_SELECT_SAVED_QUERIES: 'compact/select/saved_queries', COMPACT_SELECT_SHOW_MORE: 'compact/select/show_more', CREATE: 'entry/create', From 945014d557e36d717b8d9052705ff04112eecd18 Mon Sep 17 00:00:00 2001 From: Joshua Chudy Date: Mon, 30 Sep 2024 12:41:07 -0700 Subject: [PATCH 11/15] function for generating the unique id for a row when we have the fk pseudo column and the row data --- js/column.js | 9 +++++++++ js/reference.js | 16 +--------------- js/utils/helpers.js | 20 ++++++++++++++++++++ 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/js/column.js b/js/column.js index 1d2c5803..fb8ce67f 100644 --- a/js/column.js +++ b/js/column.js @@ -1663,6 +1663,15 @@ function ForeignKeyPseudoColumn (reference, fk, sourceObjectWrapper, name) { // extend the prototype module._extends(ForeignKeyPseudoColumn, ReferenceColumn); +/** + * Given the available tuple data, generate the uniqueId for the selected row from the table this pseudo column points to + * + * @param {*} linkedData + */ +ForeignKeyPseudoColumn.prototype.generateUniqueId = function (linkedData) { + return module._generateTupleUniqueId(this.reference.table.shortestKey, linkedData); +} + // properties to be overriden: /** * This function takes in a tuple and generates a reference that is diff --git a/js/reference.js b/js/reference.js index a1b1adad..7b8d22f6 100644 --- a/js/reference.js +++ b/js/reference.js @@ -6305,21 +6305,7 @@ */ get uniqueId() { if (this._uniqueId === undefined) { - var key, hasNull = false; - this._uniqueId = ""; - for (var i = 0; i < this._pageRef.table.shortestKey.length; i++) { - keyName = this._pageRef.table.shortestKey[i].name; - if (this.data[keyName] == null) { - hasNull = true; - break; - } - if (i !== 0) this._uniqueId += "_"; - this._uniqueId += this.data[keyName]; - } - - if (hasNull) { - this._uniqueId = null; - } + this._uniqueId = module._generateTupleUniqueId(this._pageRef.table.shortestKey, this.data); } return this._uniqueId; }, diff --git a/js/utils/helpers.js b/js/utils/helpers.js index 604ce876..6571ddf4 100644 --- a/js/utils/helpers.js +++ b/js/utils/helpers.js @@ -1026,6 +1026,26 @@ }; }; + module._generateTupleUniqueId = function (tableShortestKey, linkedData) { + var keyName, hasNull = false, _uniqueId = ""; + + for (var i = 0; i < tableShortestKey.length; i++) { + keyName = tableShortestKey[i].name; + if (linkedData[keyName] == null) { + hasNull = true; + break; + } + if (i !== 0) _uniqueId += "_"; + _uniqueId += linkedData[keyName]; + } + + if (hasNull) { + _uniqueId = null; + } + + return _uniqueId; + }; + /** * @function * @param {ERMrest.Table} table The table that we want the row name for. From 6e9f2bd9c613405f1f02cd2b84b7ba18a2788dcb Mon Sep 17 00:00:00 2001 From: Joshua Chudy Date: Mon, 30 Sep 2024 14:28:41 -0700 Subject: [PATCH 12/15] add test for pseudocolumn generateUniqueId function --- test/specs/column/tests/02.reference_column.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/specs/column/tests/02.reference_column.js b/test/specs/column/tests/02.reference_column.js index 0682e7b2..b193149d 100644 --- a/test/specs/column/tests/02.reference_column.js +++ b/test/specs/column/tests/02.reference_column.js @@ -663,6 +663,16 @@ exports.execute = function (options) { // mainEntityColumns }); + it ('should return the unqiue ID for a row of the table the pseudocolumn represents based on provided data', function () { + var data = { + "int_value": 4321, + "id": 1, + "RID": "1YJ" + } + + expect(mainEntityColumns[5].generateUniqueId(data)).toBe(data.RID); + }); + describe('should return a filtered reference based on provided data.', function () { it('Reference for mainEntityColumns[3], should have proper domain filter.', function() { mainEntityData = {} From c15b13b5bf7421d81e847f2bd21e4fd6ab2aa97f Mon Sep 17 00:00:00 2001 From: Joshua Chudy Date: Tue, 1 Oct 2024 10:29:41 -0700 Subject: [PATCH 13/15] added more info to comments --- js/column.js | 2 +- js/utils/helpers.js | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/js/column.js b/js/column.js index fb8ce67f..8c615276 100644 --- a/js/column.js +++ b/js/column.js @@ -1666,7 +1666,7 @@ module._extends(ForeignKeyPseudoColumn, ReferenceColumn); /** * Given the available tuple data, generate the uniqueId for the selected row from the table this pseudo column points to * - * @param {*} linkedData + * @param {Object} linkedData key-value pairs of column values of the table this pseudocolumn points to */ ForeignKeyPseudoColumn.prototype.generateUniqueId = function (linkedData) { return module._generateTupleUniqueId(this.reference.table.shortestKey, linkedData); diff --git a/js/utils/helpers.js b/js/utils/helpers.js index 6571ddf4..6ed0bf08 100644 --- a/js/utils/helpers.js +++ b/js/utils/helpers.js @@ -1026,6 +1026,13 @@ }; }; + /** + * Given the available linked data, generate the uniqueId for the row this data represents given the shortest key of the table + * + * @param {ERMrest.Key[]} tableShortestKey shortest key from the table the linkedData is for + * @param {Object} linkedData data to use to generate the unique id + * @returns string | null - unique id for the row the linkedData represents + */ module._generateTupleUniqueId = function (tableShortestKey, linkedData) { var keyName, hasNull = false, _uniqueId = ""; From 3333839435d94f91e9c604d564db328db7742e10 Mon Sep 17 00:00:00 2001 From: Joshua Chudy Date: Tue, 1 Oct 2024 10:31:26 -0700 Subject: [PATCH 14/15] semicolons and api.md updates --- docs/dev-docs/api.md | 73 ++++++++++++++++++++++++++++++++++++++++++-- js/column.js | 2 +- js/reference.js | 4 +-- 3 files changed, 74 insertions(+), 5 deletions(-) diff --git a/docs/dev-docs/api.md b/docs/dev-docs/api.md index 22e3fc3c..1e1870d2 100644 --- a/docs/dev-docs/api.md +++ b/docs/dev-docs/api.md @@ -367,6 +367,7 @@ to use for ERMrest JavaScript agents. * [.citation](#ERMrest.Reference+citation) : ERMrest.Citation * [.googleDatasetMetadata](#ERMrest.Reference+googleDatasetMetadata) : ERMrest.GoogleDatasetMetadata * [.cascadingDeletedItems](#ERMrest.Reference+cascadingDeletedItems) : Array.<Object> + * [.bulkCreateForeignKeyObject](#ERMrest.Reference+bulkCreateForeignKeyObject) : ERMrest.BulkCreateForeignKeyObject * [.generateFacetColumns()](#ERMrest.Reference+generateFacetColumns) * [.validateFacetsFilters(facetAndFilters, facetObjectWrappers, searchTerm, skipMappingEntityChoices, changeLocation)](#ERMrest.Reference+validateFacetsFilters) * [.removeAllFacetFilters(sameFilter, sameCustomFacet, sameFacet)](#ERMrest.Reference+removeAllFacetFilters) ⇒ ERMrest.reference @@ -386,6 +387,7 @@ to use for ERMrest JavaScript agents. * [.getColumnByName(name)](#ERMrest.Reference+getColumnByName) ⇒ [ReferenceColumn](#ERMrest.ReferenceColumn) * [.generateColumnsList(tuple, columnsList, dontChangeReference, skipLog)](#ERMrest.Reference+generateColumnsList) ⇒ [Array.<ReferenceColumn>](#ERMrest.ReferenceColumn) * [.generateActiveList([tuple])](#ERMrest.Reference+generateActiveList) ⇒ Object + * [.computeBulkCreateForeignKeyObject(prefillObject)](#ERMrest.Reference+computeBulkCreateForeignKeyObject) ⇒ BulkCreateForeignKeyObject * [._getReadPath(useEntity, getTRS, getTCRS, getUnlinkTRS)](#ERMrest.Reference+_getReadPath) : Object * [~processSortObject()](#ERMrest.Reference+_getReadPath..processSortObject) * [.Page](#ERMrest.Page) @@ -483,6 +485,7 @@ to use for ERMrest JavaScript agents. * [.defaultReference](#ERMrest.ForeignKeyPseudoColumn+defaultReference) : ERMrest.Refernece * [.RID](#ERMrest.ForeignKeyPseudoColumn+RID) : string * [.displayname](#ERMrest.ForeignKeyPseudoColumn+displayname) : Object + * [.generateUniqueId(linkedData)](#ERMrest.ForeignKeyPseudoColumn+generateUniqueId) * [.filteredRef(column, data)](#ERMrest.ForeignKeyPseudoColumn+filteredRef) ⇒ [Reference](#ERMrest.Reference) * [.KeyPseudoColumn](#ERMrest.KeyPseudoColumn) * [new KeyPseudoColumn(reference, key)](#new_ERMrest.KeyPseudoColumn_new) @@ -705,6 +708,7 @@ to use for ERMrest JavaScript agents. * [.citation](#ERMrest.Reference+citation) : ERMrest.Citation * [.googleDatasetMetadata](#ERMrest.Reference+googleDatasetMetadata) : ERMrest.GoogleDatasetMetadata * [.cascadingDeletedItems](#ERMrest.Reference+cascadingDeletedItems) : Array.<Object> + * [.bulkCreateForeignKeyObject](#ERMrest.Reference+bulkCreateForeignKeyObject) : ERMrest.BulkCreateForeignKeyObject * [.generateFacetColumns()](#ERMrest.Reference+generateFacetColumns) * [.validateFacetsFilters(facetAndFilters, facetObjectWrappers, searchTerm, skipMappingEntityChoices, changeLocation)](#ERMrest.Reference+validateFacetsFilters) * [.removeAllFacetFilters(sameFilter, sameCustomFacet, sameFacet)](#ERMrest.Reference+removeAllFacetFilters) ⇒ ERMrest.reference @@ -724,6 +728,7 @@ to use for ERMrest JavaScript agents. * [.getColumnByName(name)](#ERMrest.Reference+getColumnByName) ⇒ [ReferenceColumn](#ERMrest.ReferenceColumn) * [.generateColumnsList(tuple, columnsList, dontChangeReference, skipLog)](#ERMrest.Reference+generateColumnsList) ⇒ [Array.<ReferenceColumn>](#ERMrest.ReferenceColumn) * [.generateActiveList([tuple])](#ERMrest.Reference+generateActiveList) ⇒ Object + * [.computeBulkCreateForeignKeyObject(prefillObject)](#ERMrest.Reference+computeBulkCreateForeignKeyObject) ⇒ BulkCreateForeignKeyObject * [._getReadPath(useEntity, getTRS, getTCRS, getUnlinkTRS)](#ERMrest.Reference+_getReadPath) : Object * [~processSortObject()](#ERMrest.Reference+_getReadPath..processSortObject) * [.AttributeGroupReference](#ERMrest.AttributeGroupReference) : object @@ -3162,6 +3167,7 @@ Constructor for a ParsedFilter. * [.citation](#ERMrest.Reference+citation) : ERMrest.Citation * [.googleDatasetMetadata](#ERMrest.Reference+googleDatasetMetadata) : ERMrest.GoogleDatasetMetadata * [.cascadingDeletedItems](#ERMrest.Reference+cascadingDeletedItems) : Array.<Object> + * [.bulkCreateForeignKeyObject](#ERMrest.Reference+bulkCreateForeignKeyObject) : ERMrest.BulkCreateForeignKeyObject * [.generateFacetColumns()](#ERMrest.Reference+generateFacetColumns) * [.validateFacetsFilters(facetAndFilters, facetObjectWrappers, searchTerm, skipMappingEntityChoices, changeLocation)](#ERMrest.Reference+validateFacetsFilters) * [.removeAllFacetFilters(sameFilter, sameCustomFacet, sameFacet)](#ERMrest.Reference+removeAllFacetFilters) ⇒ ERMrest.reference @@ -3181,6 +3187,7 @@ Constructor for a ParsedFilter. * [.getColumnByName(name)](#ERMrest.Reference+getColumnByName) ⇒ [ReferenceColumn](#ERMrest.ReferenceColumn) * [.generateColumnsList(tuple, columnsList, dontChangeReference, skipLog)](#ERMrest.Reference+generateColumnsList) ⇒ [Array.<ReferenceColumn>](#ERMrest.ReferenceColumn) * [.generateActiveList([tuple])](#ERMrest.Reference+generateActiveList) ⇒ Object + * [.computeBulkCreateForeignKeyObject(prefillObject)](#ERMrest.Reference+computeBulkCreateForeignKeyObject) ⇒ BulkCreateForeignKeyObject * [._getReadPath(useEntity, getTRS, getTCRS, getUnlinkTRS)](#ERMrest.Reference+_getReadPath) : Object * [~processSortObject()](#ERMrest.Reference+_getReadPath..processSortObject) @@ -3405,11 +3412,14 @@ Returns true if #### reference.display : Object An object which contains row display properties for this reference. -It is determined based on the `table-display` annotation. It has the +It is determined based on the `table-display`, `display`, and 'chaise-config' annotations. It has the following properties: - `rowOrder`: `[{ column: '`_column object_`', descending:` {`true` | `false` } `}`...`]` or `undefined`, - `type`: {`'table'` | `'markdown'` | `'module'`} (default: `'table'`) + - `showFaceting`: A boolean indicating whether we should show the faceting feature or not. + - `maxFacetDepth`: A number indicating the facet depth. + - `facetPanelOpen`: Whether the facet panel should be opened by default or not. If type is `'markdown'`, the object will also these additional properties: @@ -3552,6 +3562,13 @@ a Metadata object #### reference.cascadingDeletedItems : Array.<Object> The related reference or tables that might be deleted as a result of deleting the current table. +**Kind**: instance property of [Reference](#ERMrest.Reference) + + +#### reference.bulkCreateForeignKeyObject : ERMrest.BulkCreateForeignKeyObject +If prefill object is defined and has the required attributes, will return +a BulkCreateForeignKeyObject object with the necessary objects used for a association modal picker + **Kind**: instance property of [Reference](#ERMrest.Reference) @@ -3984,6 +4001,20 @@ so if the fk definition is based on fkcolum and and not the RID, it would handle | --- | --- | | [tuple] | [Tuple](#ERMrest.Tuple) | + + +#### reference.computeBulkCreateForeignKeyObject(prefillObject) ⇒ BulkCreateForeignKeyObject +Will compute and return a BulkCreateForeignKeyObject if: + - the prefillObject is defined + - there are only 2 foreign key columns for this table that are not system columns + - using the prefill object, we can determine the main column for prefilling and leaf column for bulk selection + +**Kind**: instance method of [Reference](#ERMrest.Reference) + +| Param | Type | Description | +| --- | --- | --- | +| prefillObject | Object | computed prefill object from chaise | + #### reference.\_getReadPath(useEntity, getTRS, getTCRS, getUnlinkTRS) : Object @@ -4951,6 +4982,7 @@ In other cases, the returned data will only include the scalar value. * [.defaultReference](#ERMrest.ForeignKeyPseudoColumn+defaultReference) : ERMrest.Refernece * [.RID](#ERMrest.ForeignKeyPseudoColumn+RID) : string * [.displayname](#ERMrest.ForeignKeyPseudoColumn+displayname) : Object + * [.generateUniqueId(linkedData)](#ERMrest.ForeignKeyPseudoColumn+generateUniqueId) * [.filteredRef(column, data)](#ERMrest.ForeignKeyPseudoColumn+filteredRef) ⇒ [Reference](#ERMrest.Reference) @@ -5038,6 +5070,17 @@ returns the ermrest generated RID for the foreign key relationship this pseudo c use table's displayname disambiguated with columns' displayname, i.e. `table_1 (col_1, col_2)`. **Kind**: instance property of [ForeignKeyPseudoColumn](#ERMrest.ForeignKeyPseudoColumn) + + +#### foreignKeyPseudoColumn.generateUniqueId(linkedData) +Given the available tuple data, generate the uniqueId for the selected row from the table this pseudo column points to + +**Kind**: instance method of [ForeignKeyPseudoColumn](#ERMrest.ForeignKeyPseudoColumn) + +| Param | Type | Description | +| --- | --- | --- | +| linkedData | Object | key-value pairs of column values of the table this pseudocolumn points to | + #### foreignKeyPseudoColumn.filteredRef(column, data) ⇒ [Reference](#ERMrest.Reference) @@ -7053,6 +7096,7 @@ get PathColumn object by column name * [.citation](#ERMrest.Reference+citation) : ERMrest.Citation * [.googleDatasetMetadata](#ERMrest.Reference+googleDatasetMetadata) : ERMrest.GoogleDatasetMetadata * [.cascadingDeletedItems](#ERMrest.Reference+cascadingDeletedItems) : Array.<Object> + * [.bulkCreateForeignKeyObject](#ERMrest.Reference+bulkCreateForeignKeyObject) : ERMrest.BulkCreateForeignKeyObject * [.generateFacetColumns()](#ERMrest.Reference+generateFacetColumns) * [.validateFacetsFilters(facetAndFilters, facetObjectWrappers, searchTerm, skipMappingEntityChoices, changeLocation)](#ERMrest.Reference+validateFacetsFilters) * [.removeAllFacetFilters(sameFilter, sameCustomFacet, sameFacet)](#ERMrest.Reference+removeAllFacetFilters) ⇒ ERMrest.reference @@ -7072,6 +7116,7 @@ get PathColumn object by column name * [.getColumnByName(name)](#ERMrest.Reference+getColumnByName) ⇒ [ReferenceColumn](#ERMrest.ReferenceColumn) * [.generateColumnsList(tuple, columnsList, dontChangeReference, skipLog)](#ERMrest.Reference+generateColumnsList) ⇒ [Array.<ReferenceColumn>](#ERMrest.ReferenceColumn) * [.generateActiveList([tuple])](#ERMrest.Reference+generateActiveList) ⇒ Object + * [.computeBulkCreateForeignKeyObject(prefillObject)](#ERMrest.Reference+computeBulkCreateForeignKeyObject) ⇒ BulkCreateForeignKeyObject * [._getReadPath(useEntity, getTRS, getTCRS, getUnlinkTRS)](#ERMrest.Reference+_getReadPath) : Object * [~processSortObject()](#ERMrest.Reference+_getReadPath..processSortObject) @@ -7296,11 +7341,14 @@ Returns true if #### reference.display : Object An object which contains row display properties for this reference. -It is determined based on the `table-display` annotation. It has the +It is determined based on the `table-display`, `display`, and 'chaise-config' annotations. It has the following properties: - `rowOrder`: `[{ column: '`_column object_`', descending:` {`true` | `false` } `}`...`]` or `undefined`, - `type`: {`'table'` | `'markdown'` | `'module'`} (default: `'table'`) + - `showFaceting`: A boolean indicating whether we should show the faceting feature or not. + - `maxFacetDepth`: A number indicating the facet depth. + - `facetPanelOpen`: Whether the facet panel should be opened by default or not. If type is `'markdown'`, the object will also these additional properties: @@ -7443,6 +7491,13 @@ a Metadata object #### reference.cascadingDeletedItems : Array.<Object> The related reference or tables that might be deleted as a result of deleting the current table. +**Kind**: instance property of [Reference](#ERMrest.Reference) + + +#### reference.bulkCreateForeignKeyObject : ERMrest.BulkCreateForeignKeyObject +If prefill object is defined and has the required attributes, will return +a BulkCreateForeignKeyObject object with the necessary objects used for a association modal picker + **Kind**: instance property of [Reference](#ERMrest.Reference) @@ -7875,6 +7930,20 @@ so if the fk definition is based on fkcolum and and not the RID, it would handle | --- | --- | | [tuple] | [Tuple](#ERMrest.Tuple) | + + +#### reference.computeBulkCreateForeignKeyObject(prefillObject) ⇒ BulkCreateForeignKeyObject +Will compute and return a BulkCreateForeignKeyObject if: + - the prefillObject is defined + - there are only 2 foreign key columns for this table that are not system columns + - using the prefill object, we can determine the main column for prefilling and leaf column for bulk selection + +**Kind**: instance method of [Reference](#ERMrest.Reference) + +| Param | Type | Description | +| --- | --- | --- | +| prefillObject | Object | computed prefill object from chaise | + #### reference.\_getReadPath(useEntity, getTRS, getTCRS, getUnlinkTRS) : Object diff --git a/js/column.js b/js/column.js index bb5d0a4e..00c0c76d 100644 --- a/js/column.js +++ b/js/column.js @@ -1670,7 +1670,7 @@ module._extends(ForeignKeyPseudoColumn, ReferenceColumn); */ ForeignKeyPseudoColumn.prototype.generateUniqueId = function (linkedData) { return module._generateTupleUniqueId(this.reference.table.shortestKey, linkedData); -} +}; // properties to be overriden: /** diff --git a/js/reference.js b/js/reference.js index 7f5ff65f..ac565f17 100644 --- a/js/reference.js +++ b/js/reference.js @@ -2466,7 +2466,7 @@ } // validate the value if (!isInteger(maxFacetDepth) || maxFacetDepth < 0) { - maxFacetDepth = 1 + maxFacetDepth = 1; } else if (maxFacetDepth > 2) { maxFacetDepth = 2; } @@ -6808,4 +6808,4 @@ return this._andFilters; } - } + }; From 0ba99f21618295f7243f00e20edc2f6a229d2d01 Mon Sep 17 00:00:00 2001 From: Joshua Chudy Date: Tue, 1 Oct 2024 13:54:44 -0700 Subject: [PATCH 15/15] update annotation.md --- docs/user-docs/annotation.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/user-docs/annotation.md b/docs/user-docs/annotation.md index 3c8842af..33d3cff3 100644 --- a/docs/user-docs/annotation.md +++ b/docs/user-docs/annotation.md @@ -1159,6 +1159,7 @@ List of _context_ names that are used in ERMrest: - `"compact/select/association/link"`: A sub-context of `compact/select/association` used for selecting entities to link to the main record. - `"compact/select/association/unlink"`: A sub-context of `compact/select/association` used for selecting entities to unlink from the main record. - `"compact/select/foreign_key"`: A sub-context of `compact/select` used for selecting entities for a foreign key value in an `entry` context. + - `"compactSelectBulkForeignKey"`: a sub-context of `compact/select` used for selecting multiple entities to fill in foreign key values in multiple forms in `entry/create` context. - `"compact/select/saved_queries"`: A sub-context of `compact/select` used for selecting a saved query to apply in `compact` context. - `"compact/select/show_more"`: A sub-context of `compact/select` used for selecting entities as a facet value. - `"detailed"`: Any detailed read-only, entity-level presentation context.