Last active
September 16, 2021 17:56
-
-
Save on2air/ae7b26f7254c02f8ea33af11290f0744 to your computer and use it in GitHub Desktop.
Script for setting default values in Airtable
This file contains hidden or 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
/***** | |
* Title: Manage & Run Default Values | |
* License: MIT | |
* Author: Openside (Team behind On2Air products and BuiltOnAir community) | |
* Sites: | |
* https://openside.com - Openside Consulting Services | |
* https://openside.com/#products - On2Air Products | |
* https://builtonair.com - All things Airtable Community | |
* | |
* Reach out for all your Airtable needs | |
* | |
* Description: This script allows you to set default values for any or all | |
* tables in your base. You can configure once, then run whenever you want | |
* fields to be populated with default values. It will process any blank fields | |
* for the table (and can be filtered down to a view) and fill them with | |
* the default value you configured for that table. | |
* | |
* This requires a user with Editor/Creator/Owner role, and requires | |
* creating a new table in the base with 5 fields. The names for the table | |
* and fields are configurable below. | |
* | |
* Full Instructions Here: | |
* | |
* | |
* | |
*/ | |
/** | |
* The table name used to store default values | |
*/ | |
const dfTable = 'Defaults' | |
/** | |
* The field names within the Defaults table. | |
* These can all be SingleLineText fields (the Type field could be a SingleSelect field or SingleLineText) | |
*/ | |
const ddField = 'Name' | |
const ddTable = 'Table' | |
const ddType = 'Type' | |
const ddValue = 'Value' | |
const ddCode = 'Code' | |
//-------------------------------------------------- | |
//NO CHANGES BELOW | |
//-------------------------------------------------- | |
//----------------------------------- | |
//Function: getCollablId | |
//Description: looks up the collaborator id based on email | |
//----------------------------------- | |
const getCollabId = (email) => { | |
email = email.trim().toLowerCase() | |
let collabs = base.activeCollaborators | |
for(let collab of collabs){ | |
if(collab.email.toLowerCase() === email){ | |
return collab.id | |
} | |
} | |
return null | |
} | |
//----------------------------------- | |
//Function: cast | |
//Params: | |
//table - The table model that is being updated (needed to get the field) | |
//field - The field name or id that will be updated | |
//value - the string value to be converted based on the field type | |
//Description: Converts a String value to the | |
//corresponding value needed to perform an update | |
//----------------------------------- | |
const cast = ( table, field, value ) => { | |
let fieldMeta = table.getField(field) | |
let type = fieldMeta.type | |
return castType( type, value ) | |
} | |
//----------------------------------- | |
//Function: castType | |
//Params: | |
//type - the fieldType retrieved from table.getField(field).type | |
//value - the string value to be converted based on the field type | |
//Description: Converts a String value to the | |
//corresponding value needed to perform an update | |
//----------------------------------- | |
const castType = ( type, value ) => { | |
if(type === 'singleSelect'){ | |
value = {name:value} | |
}else if(type === 'multipleSelects'){ | |
let items = value.split(',') | |
let values = [] | |
items.forEach( i => { | |
values.push({name: i.trim()}) | |
}) | |
value = values | |
}else if(type === 'number' || type === 'percent' || type === 'currency'){ | |
value = parseFloat(value) | |
}else if(type === 'multipleRecordLinks'){ | |
let items = value.split(',') | |
let values = [] | |
items.forEach( i => { | |
values.push({id: i.trim()}) | |
}) | |
value = values | |
}else if(type === 'singleRecordLink'){ | |
value = {id:value} | |
}else if(type === 'multipleAttachments'){ | |
let items = value.split(',') | |
let values = [] | |
items.forEach( i => { | |
values.push({url: i.trim()}) | |
}) | |
value = values | |
}else if(type === 'checkbox'){ | |
value = value === 1 || value === true || value === 'true' || value === '1' || value === 'yes' || value === 'on' | |
}else if(type === 'barcode'){ | |
value = {text: value} | |
}else if(type === 'rating' || type === 'duration'){ | |
value = parseInt(value) | |
}else if(type === 'singleCollaborator'){ | |
value = {id: getCollabId(value)} | |
}else if(type === 'multipleCollaborators'){ | |
let items = value.split(',') | |
let values = [] | |
items.forEach( i => { | |
values.push({id: getCollabId(i)}) | |
}) | |
value = values | |
}else if(type === 'date' || type === 'dateTime'){ | |
value = value.trim().toLowerCase() === 'now' ? new Date().toISOString() : value | |
} | |
return value | |
} | |
/** | |
* Function: toButtons | |
* Description: Create buttons based on an array of items (ie tables, views, or fields) | |
*/ | |
const toButtons = ( label, items, skip = [], buttonsStart = [], buttonsEnd = [] ) => { | |
let buttons = [] | |
for(let i=0; i<items.length; i++){ | |
let item = items[i] | |
if(!skip.includes(item.name)){ | |
buttons.push( item.name ) | |
} | |
} | |
buttons = [...buttonsStart,...buttons,...buttonsEnd] | |
return input.buttonsAsync(label, buttons) | |
} | |
const getDefaults = async ( tDef, table ) => { | |
let tDefRecs = await tDef.selectRecordsAsync() | |
let tDefValues = {} | |
for(let rec of tDefRecs.records){ | |
if(rec.getCellValueAsString(ddCode).indexOf( table.id ) >= 0){ | |
let code = JSON.parse(rec.getCellValueAsString( ddCode )) | |
let type = rec.getCellValueAsString(ddType).toLowerCase() | |
if(type === 'field'){ | |
let field_id = code['field_id'] | |
tDefValues[field_id] = { | |
id: rec.id, | |
type, | |
field: rec.getCellValueAsString( ddField ), | |
table: rec.getCellValueAsString( ddTable ), | |
value: rec.getCellValueAsString( ddValue ), | |
code: rec.getCellValueAsString( ddCode ), | |
field_id: field_id, | |
table_id: code['table_id'], | |
} | |
}else if(type === 'view'){ | |
let table_id = code['table_id'] | |
tDefValues['view_' + table_id] = { | |
id: rec.id, | |
type, | |
field: rec.getCellValueAsString( ddField ), | |
table: rec.getCellValueAsString( ddTable ), | |
value: rec.getCellValueAsString( ddValue ), | |
code: rec.getCellValueAsString( ddCode ), | |
view_id: code['view_id'], | |
table_id: table_id, | |
} | |
} | |
} | |
} | |
return tDefValues | |
} | |
let start = async (msg = null) => { | |
output.clear() | |
output.markdown('# Default Values'); | |
output.markdown('Configure and Set Default Values for the Base'); | |
if(msg){output.markdown(msg)} | |
let perform = await input.buttonsAsync('Perform:',['Set Defaults','Configure Defaults']) | |
perform = perform.toLowerCase() | |
if( perform.indexOf('set') >= 0 ){ | |
output.clear() | |
output.markdown('Setting...') | |
let tables = base.tables | |
let selected = await toButtons('Select Table to Set Updates', tables,[dfTable]) | |
let table = base.getTable(selected) | |
let tDef = base.getTable( dfTable ) | |
let tDefValueList = await getDefaults( tDef, table ) | |
let tDefValues = Object.values(tDefValueList) | |
let defaults = {} | |
let view = '' | |
for(let f=0; f<tDefValues.length; f++){ | |
let defVal = tDefValues[f] | |
if(defVal['type'] === 'field'){ | |
let field_id = defVal['field_id'] | |
let value = defVal['value'] | |
if(value){ | |
defaults[field_id] = cast( table, field_id, value ) | |
} | |
}else if(defVal['type'] === 'view'){ | |
view = defVal['view_id'] | |
} | |
} | |
console.log('updating', defaults, view, tDefValues) | |
let records = await ( view ? table.getView(view).selectRecordsAsync() : table.selectRecordsAsync()) | |
let count = 0 | |
for(let rec of records.records){ | |
let defKeys = Object.keys(defaults) | |
let recDefaults = {} | |
let update = false | |
for(let k=0; k<defKeys.length; k++){ | |
let key = defKeys[k] | |
let currVal = rec.getCellValueAsString(key) | |
if(!currVal){ | |
update = true | |
recDefaults[key] = defaults[key] | |
} | |
} | |
if(update){ | |
// @ts-ignore | |
await table.updateRecordAsync(rec.id, recDefaults) | |
count++ | |
} | |
} | |
await start('Done Setting... ' + count + ' out of ' + records.records.length + ' updated') | |
}else if (perform.indexOf('configure') >= 0 ){ | |
output.clear() | |
output.markdown('Configuring...') | |
let tables = base.tables | |
let selected = await toButtons('Select Table to Configure', tables,[dfTable]) | |
let table = base.getTable(selected) | |
let tDef = base.getTable( dfTable ) | |
let tDefValues = await getDefaults( tDef, table ) | |
let fields = table.fields | |
let views = table.views | |
const NOVIEW = '- NO VIEW -' | |
let vExisting = tDefValues['view_' + table.id] || false | |
let vExistingVal = vExisting ? vExisting['value'] : '' | |
let currentView = [] | |
let skipViews = [] | |
if(vExistingVal){ | |
currentView.push({label: vExisting['value'], variant: 'primary'}) | |
skipViews.push(vExisting['value']) | |
} | |
let viewP = toButtons('Select View to Use for Defaults', views, skipViews,currentView, vExistingVal ? [NOVIEW] : [{label:NOVIEW,variant: 'primary'}]) | |
output.markdown(vExistingVal ? `The current selection is ${vExistingVal}` : 'Select a View to filter on what fields will get the defaults') | |
let processView = async( viewP ) => { | |
let view = await viewP | |
view = view.replace('* ','').replace(' *','') | |
if(view === NOVIEW){ | |
view = '' | |
} | |
let newRecV = { | |
[ddField]: 'TABLE VIEW', | |
[ddType]: cast( tDef, ddType, 'View'), | |
[ddTable]: table.name, | |
[ddValue]: view, | |
[ddCode]: JSON.stringify({ | |
view_id: view ? table.getView(view).id : '', | |
table_id: table.id | |
}) | |
} | |
if(vExisting){ | |
await tDef.updateRecordAsync(vExisting.id, newRecV) | |
}else{ | |
await tDef.createRecordAsync( newRecV ) | |
} | |
} | |
let view = await viewP | |
await processView(view) | |
let fieldDefaultValues = [] | |
let processField = ( field, index, existing ) => { | |
return async ( ) => { | |
//let newDefVal = await newDefValP | |
let newDefVal = fieldDefaultValues[index] | |
let existingVal = existing ? existing['value'] : '' | |
// console.log('processing field...', field, index, newDefVal, existing) | |
let newRec = { | |
[ddField]: field.name, | |
[ddType]: cast( tDef, ddType, 'Field' ), | |
[ddTable]: table.name, | |
[ddValue]: newDefVal ? newDefVal.trim() : existingVal, | |
[ddCode]: JSON.stringify({ | |
field_id: field.id, | |
table_id: table.id | |
}) | |
} | |
if(existing){ | |
await tDef.updateRecordAsync(existing.id, newRec) | |
}else{ | |
await tDef.createRecordAsync( newRec ) | |
} | |
} | |
} | |
output.markdown('# Field Default Values'); | |
let fieldPromises = [] | |
for(let f=0; f < fields.length; f++ ){ | |
let field = fields[f] | |
if(!field.isComputed && !['checkbox'].includes(field.type)){ | |
let existing = tDefValues[field.id] || false | |
let existingVal = existing ? existing['value'] : '' | |
let help = existingVal ? `Current Default: '${existingVal}'.\n\n To keep this, leave blank. If entering a new value, you must hit Next to save it` : `There is currently no default value for ${field.name}.\n\nEnter one here and hit Next or leave blank to have no default value` | |
let newDefValP = input.textAsync(`${field.name} [${field.type}]:`) | |
newDefValP.then( newDefVal => { | |
fieldDefaultValues[fIndex] = newDefVal | |
return newDefValP | |
}).catch( err => { | |
console.log('error on field',err) | |
}) | |
output.markdown(help) | |
let fIndex = fieldDefaultValues.length | |
fieldDefaultValues[fIndex] = '' | |
fieldPromises.push( processField(field, fIndex, existing )) | |
} | |
} | |
let done = await input.buttonsAsync('Finished',['Done','Quit']) | |
if(done === 'Done'){ | |
for(let p=0; p<fieldPromises.length; p++){ | |
// console.log('about to run fp',p) | |
await fieldPromises[p]( ) | |
} | |
await start('Done Configuring...') | |
}else if(done === 'Quit'){ | |
await start('Cancelled...') | |
} | |
} | |
} | |
await start() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment