Last active
July 14, 2022 04:53
-
-
Save theomichel/855ba66396c4a0101f0f4d3e667158b3 to your computer and use it in GitHub Desktop.
This script attempts to automatically create back-links for an Airtable linked record that references the same table.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* This script attempts to automatically create back-links for an Airtable linked record | |
* that references the same table. It supports an arbitrary number of links per record. | |
* It is susceptible to race conditions if multiple changes are made in quick succession. | |
* | |
* To use this, one would add it as an automation on the table, trigged by record updates | |
* to the "parents" or "children" columns. | |
* | |
* NOTE: some efficiency could be gained by having two scripts, one for parents and | |
* one for children, and triggering each one when appropriate. | |
*/ | |
// maximally inefficient set difference, but our sets are quite small, and this is resilient | |
// to ordering changes | |
function setDifference(setOne, setTwo) | |
{ | |
setOne = setOne != null ? setOne : []; | |
setTwo = setTwo != null ? setTwo : []; | |
let difference = setOne.filter(function(currentOne) { | |
return !setTwo.some(currentTwo => currentTwo.id === currentOne.id) | |
}); | |
return difference; | |
} | |
// the table to check | |
let table = base.getTable("Cards"); | |
// depends on a 'recordId' input variable connected to a record trigger | |
let config = input.config(); | |
let recordId = config.recordId; | |
// get the modified record. This means querying the table to get all the records, | |
// as Airtable doesn't support filters on select. But they're probably cached anyway? | |
let recordQueryResult = await table.selectRecordsAsync() | |
let modifiedRecord = recordQueryResult.getRecord(recordId); | |
// get the relevant fields of the modified record | |
// the "previous_" fields are dupes of the parents/children fields, but are only updated by the script | |
let modifiedRecordChildren = modifiedRecord.getCellValue("Children"); | |
let modifiedRecordPreviousChildren = modifiedRecord.getCellValue("Previous_Children"); | |
let modifiedRecordParents = modifiedRecord.getCellValue("Parents"); | |
let modifiedRecordPreviousParents = modifiedRecord.getCellValue("Previous_Parents"); | |
// compare children to previous children and parents to previous parents | |
// the differences represent changes just made (we assume... although race conditions exist!) | |
// NOTE: these set differences are very inefficient, but the sets are so small... | |
let addedKids = setDifference(modifiedRecordChildren, modifiedRecordPreviousChildren); | |
let removedKids = setDifference(modifiedRecordPreviousChildren, modifiedRecordChildren); | |
let addedParents = setDifference(modifiedRecordParents, modifiedRecordPreviousParents); | |
let removedParents = setDifference(modifiedRecordPreviousParents, modifiedRecordParents); | |
// what follows is repetitive code, to add/remove the reciprocal links in both directions (parent/child) | |
// this code is repetitive because airtable scripting was unable to correctly infer data types | |
// once I put the code into a function, and it turned into a pain. | |
// There's probably a solution to the data type inference issue, but I don't have time at the moment. | |
// go to each new kid and tell them we're they're parent | |
console.log("addedkids length:" + addedKids.length); | |
for (let i = 0; addedKids != null && i < addedKids.length; i++) { | |
let currentAddedKidParents = recordQueryResult.getRecord(addedKids[i]["id"]).getCellValue("Parents"); | |
currentAddedKidParents = currentAddedKidParents != null ? currentAddedKidParents : []; | |
if(!currentAddedKidParents.includes( { id: modifiedRecord.id, name: modifiedRecord.name })) { | |
currentAddedKidParents.push( { id: modifiedRecord.id, name: modifiedRecord.name } ); | |
} | |
console.log("currentAddedKidParents"); | |
console.log(currentAddedKidParents); | |
await table.updateRecordAsync(addedKids[i]["id"], {["Previous_Parents"]: currentAddedKidParents}); | |
await table.updateRecordAsync(addedKids[i]["id"], {["Parents"]: currentAddedKidParents}); | |
} | |
// go to each new parent and tell them we're their kid | |
console.log("addedparents length:" + addedParents.length); | |
for (let i = 0; addedParents != null && i < addedParents.length; i++) { | |
let currentAddedParentKids = recordQueryResult.getRecord(addedParents[i]["id"]).getCellValue("Children"); | |
currentAddedParentKids = currentAddedParentKids != null ? currentAddedParentKids : []; | |
if(!currentAddedParentKids.includes( { id: modifiedRecord.id, name: modifiedRecord.name })) { | |
currentAddedParentKids.push( { id: modifiedRecord.id, name: modifiedRecord.name } ); | |
} | |
console.log("currentAddedParentKids"); | |
console.log(currentAddedParentKids); | |
await table.updateRecordAsync(addedParents[i]["id"], {["Previous_Children"]: currentAddedParentKids}); | |
await table.updateRecordAsync(addedParents[i]["id"], {["Children"]: currentAddedParentKids}); | |
} | |
// go to each removed kid and tell them we're no longer their parent | |
console.log("removedKids length:" + removedKids.length); | |
for (let i = 0; removedKids != null && i < removedKids.length; i++) { | |
let currentRemovedKidParents = recordQueryResult.getRecord(removedKids[i]["id"]).getCellValue("Parents"); | |
currentRemovedKidParents = currentRemovedKidParents != null ? currentRemovedKidParents : []; | |
let foundItem = currentRemovedKidParents.findIndex( (element) => element.id == modifiedRecord.id ); // { id: modifiedRecord.id, name: modifiedRecord.name } | |
if(foundItem >= 0) { | |
currentRemovedKidParents.splice(foundItem, 1); | |
} | |
console.log("currentRemovedKidParents"); | |
console.log(currentRemovedKidParents); | |
await table.updateRecordAsync(removedKids[i]["id"], {["Previous_Parents"]: currentRemovedKidParents}); | |
await table.updateRecordAsync(removedKids[i]["id"], {["Parents"]: currentRemovedKidParents}); | |
} | |
// go to each removed parent and tell them we're no longer their kid | |
console.log("removedParents length:" + removedParents.length); | |
for (let i = 0; removedParents != null && i < removedParents.length; i++) { | |
let currentRemovedParentKids = recordQueryResult.getRecord(removedParents[i]["id"]).getCellValue("Children"); | |
currentRemovedParentKids = currentRemovedParentKids != null ? currentRemovedParentKids : []; | |
let foundItem = currentRemovedParentKids.findIndex( (element) => element.id == modifiedRecord.id ); // { id: modifiedRecord.id, name: modifiedRecord.name } | |
if(foundItem >= 0) { | |
currentRemovedParentKids.splice(foundItem, 1); | |
} | |
console.log("currentRemovedParentKids"); | |
console.log(currentRemovedParentKids); | |
await table.updateRecordAsync(removedParents[i]["id"], {["Previous_Children"]: currentRemovedParentKids}); | |
await table.updateRecordAsync(removedParents[i]["id"], {["Children"]: currentRemovedParentKids}); | |
} | |
// finally, set the "previous" fields of the current record to match the current values | |
await table.updateRecordAsync(recordId, {["Previous_Children"]: modifiedRecordChildren}); | |
await table.updateRecordAsync(recordId, {["Previous_Parents"]: modifiedRecordParents}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment