Skip to content

Instantly share code, notes, and snippets.

@on2air
Last active September 16, 2021 17:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save on2air/ae7b26f7254c02f8ea33af11290f0744 to your computer and use it in GitHub Desktop.
Save on2air/ae7b26f7254c02f8ea33af11290f0744 to your computer and use it in GitHub Desktop.
Script for setting default values in Airtable
/*****
* 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