Any Airtable folks here that can help with triggering / translating a custom Airscript?

Hi all!

I’m a new Bubbler with no tech background and could use some expertise if anyone can help:
For the past few years, I have used Airtable as my primary platform to do my work through. Now that I have started learning Bubble to build a custom admin interface, I am debating on transferring my data over so that everything can be consolidated.
However, one of the foundational steps to my workflow is a custom script that someone created for me in Airtable Scripts. I would like to find a way to translate my script from Airscript so that I may use it on other platforms, though I am not opposed to figuring out how to simply trigger my script via a button/API call in my Bubble dashboard either.

I have taught myself some basic programming “stuff”, but can’t seem to figure out how to re-work the input.config (it was created to run for a single row upon button click).

Any help/advice/tips would be greatly appreciated!

(My script is quite complex - but the only part I really need assistance with is changing the “await input.recordAsync” portion to be able to accept the inputs from an external source instead of in Airtable. :blush: included below for reference!)

Airtable Script
let twoLettersToNumber = {
    "ah": 5,
    "ch": 8,
    "wh": 16,
    "tz": 18,
    "sh": 21,
    "ta": 22,
    "th": 22,
};
let lastLetterToNumber = {
    "m": 12,
    "p": 12,
};
let letterToNumber = {
    "a": 1,
    "b": 2,
    "c": 11,
    "d": 4,
    "e": 5,
    "f": 18,
    "g": 3,
    "h": 5,
    "i": 10,
    "j": 10,
    "k": 19,
    "l": 12,
    "m": 13,
    "n": 14,
    "o": 6,
    "p": 17,
    "q": 19,
    "r": 20,
    "s": 15,
    "t": 9,
    "u": 6,
    "v": 6,
    "w": 6,
    "x": 15,
    "y": 16,
    "z": 7,
};
/**
 * @param {string} token
 * @param {{ ah?: number; ch?: number; wh?: number; tz?: number; sh?: number; ta?: number; th?: number; a?: number; b?: number; c?: number; d?: number; e?: number; f?: number; g?: number; h?: number; i?: number; j?: number; k?: number; l?: number; m?: number; n?: number; o?: number; p?: number; q?: number; r?: number; s?: number; t?: number; u?: number; v?: number; w?: number; x?: number; y?: number; z?: number; }} dict
 * @param {number[]} arr
 */
function tryInsert(token, dict, arr) {
    if (token in dict) {
        arr.push(dict[token]);
        return true;
    }
    return false;
}
/**
 * @param {string} name
 */
function nameToNumbers(name) {
    var result = [];
    for (var current = 0; current < name.length;) {
        var token = name.substr(current, 2);
        if (token.length == 2) {
            if (tryInsert(token, twoLettersToNumber, result)) {
                current += 2;
            } else {
                token = name[current];
                if (tryInsert(token, letterToNumber, result)) {
                    current += 1;
                } else {
                    throw `Invalid character found: ${token}`
                }
            }
        } else {
            if (tryInsert(token, lastLetterToNumber, result)) {
                current += 1;
            } else {
                if (tryInsert(token, letterToNumber, result)) {
                    current += 1;
                } else {
                    throw `Invalid character found: ${token}`
                }
            }
        }
    }
    return result;
}
/**
 * @param {number[]} columns
 * @param {number[]} numbers
 * @param {number} colIndex
 */
function addToColumns(columns, numbers, colIndex) {
    for (let number of numbers) {
        columns[colIndex] += number;
        colIndex += 1;
        colIndex = colIndex % columns.length;
    }
    return colIndex;
}
/**
 * @param {number} number
 */
function sumDigits(number) {
    let total = 0;
    while (number) {
        total += number % 10;
        number = Math.floor(number / 10);
    }
    return total;
}
/**
 * @param {number} number
 * @param {{ (number: number): boolean }} predicate
 */
function reduceUntil(number, predicate) {
    while (predicate(number)) {
        number = sumDigits(number);
    }
    return number;
}
/**
 * @param {number} number
 */
let greaterThan22 = number => number > 22;
/**
 * @param {number} number
 */
let singleDigit = number => number > 9;
/**
 * @param {number} number
 * @param {number} firstNumberTotal
 * @param {number} secondNumberTotal
 */
function reduce(number, firstNumberTotal, secondNumberTotal) {
    number = reduceUntil(number, greaterThan22);
    let firstNumber = number;
    let secondNumber = reduceUntil(number, singleDigit);
    firstNumberTotal += firstNumber;
    secondNumberTotal += secondNumber;
    return {
        columnValue: `${firstNumber}-${secondNumber}`,
        fnt: firstNumberTotal,
        snt: secondNumberTotal
    };
}

function reduceColumns(columns) {
    var firstNumberTotal = 0;
    var secondNumberTotal = 0;
    for (var i = 0; i < columns.length; i++) {
        var {
            columnValue,
            fnt,
            snt
        } = reduce(columns[i], firstNumberTotal, secondNumberTotal);
        columns[i] = columnValue;
        firstNumberTotal = fnt;
        secondNumberTotal = snt;
    }
    firstNumberTotal = reduceUntil(firstNumberTotal, greaterThan22);
    secondNumberTotal = reduceUntil(secondNumberTotal, singleDigit);
    columns.push(`${firstNumberTotal}-${secondNumberTotal}`);
    return columns;
}
/**
 * @param {string} firstName
 * @param {string} middleName
 * @param {string} lastName
 */
function computeColumns(firstName, middleName, lastName) {
    if (!firstName && !middleName && !lastName) {
        throw "At least one of first name, middle name or last name should be given"
    }
    firstName = firstName ? firstName.toLowerCase() : "";
    middleName = middleName ? middleName.toLowerCase() : "";
    lastName = lastName ? lastName.toLowerCase() : "";
    var columns = [0, 0, 0, 0, 0, 0];
    var numbers = nameToNumbers(firstName);
    var colIndex = 0;
    colIndex = addToColumns(columns, numbers, colIndex);
    numbers = nameToNumbers(middleName);
    colIndex = addToColumns(columns, numbers, colIndex);
    numbers = nameToNumbers(lastName);
    colIndex = addToColumns(columns, numbers, colIndex);
    return reduceColumns(columns);
}
//--------------------Airtable specific code-----------------
let columnNames = ["PK", "SK", "PT", "ST", "PG", "SG", "SD"];
const config = input.config({
    title: 'Numerology Script',
    items: [
        input.config.table('namesTable', {
            label: 'Names table',
            description: 'The table in which you track names'
        }),
        input.config.field('firstNameField', {
            label: 'First name field',
            parentTable: 'namesTable',
        }),
        input.config.field('middleNameField', {
            label: 'Middle name field',
            parentTable: 'namesTable',
        }),
        input.config.field('lastNameField', {
            label: 'Last name field',
            parentTable: 'namesTable',
        }),
        input.config.field('pkField', {
            label: 'PK field',
            parentTable: 'namesTable',
        }),
        input.config.field('skField', {
            label: 'SK field',
            parentTable: 'namesTable',
        }),
        input.config.field('ptField', {
            label: 'PT field',
            parentTable: 'namesTable',
        }),
        input.config.field('stField', {
            label: 'ST field',
            parentTable: 'namesTable',
        }),
        input.config.field('pgField', {
            label: 'PG field',
            parentTable: 'namesTable',
        }),
        input.config.field('sgField', {
            label: 'SG field',
            parentTable: 'namesTable',
        }),
        input.config.field('sdField', {
            label: 'SD field',
            parentTable: 'namesTable',
        }),
        input.config.table('linkedTable', {
            label: 'Templates table',
            description: "The table which the names table's PK, SK, PT, ST, PG, SG and SD fields link to"
        }),
        input.config.field('linkedTableNameField', {
            label: 'Templates table attributes field',
            description: 'Templates table field containing the values 13-4, 5-5 etc.',
            parentTable: 'linkedTable',
        }),
        input.config.field('aspectField', {
            label: 'Templates table aspect field',
            description: 'Templates table field containing the values "Physical Karma", "Spiritual Karma" etc.',
            parentTable: 'linkedTable',
        }),
    ]
});
async function createLinkedRecordDict() {
    let dict = {};
    let query = await config.linkedTable.selectRecordsAsync({
        fields: [config.linkedTableNameField.name, config.aspectField.name]
    });
    for (let r of query.records) {
        let name = r.getCellValueAsString(config.linkedTableNameField.name);
        let aspect = r.getCellValueAsString(config.aspectField.name);
        var key = `${name}${aspect}`;
        key = key.trim();
        dict[key] = r.id;
    }
    return dict;
}
let dict = await createLinkedRecordDict();

function findLinkedRecordId(attribute, aspect) {
    var key = `${attribute}${aspect}`;
    return dict[key];
}
let record = await input.recordAsync('Pick a record', config.namesTable);
if (record) {
    let firstName = record.getCellValueAsString(config.firstNameField.name);
    let middleName = record.getCellValueAsString(config.middleNameField.name);
    let lastName = record.getCellValueAsString(config.lastNameField.name);
    let columns = computeColumns(firstName, middleName, lastName);
    var error = false;
    let pkLinkedRecordId = findLinkedRecordId(columns[0], 'Physical Karma');
    if (!pkLinkedRecordId) {
        console.error(`Failed to find a record with name ${columns[0]} and aspect Physical Karma in ${config.linkedTable.name} table`);
        error = true;
    }
    let skLinkedRecordId = findLinkedRecordId(columns[1], 'Spiritual Karma');
    if (!skLinkedRecordId) {
        console.error(`Failed to find a record with name ${columns[1]} and aspect Spiritual Karma in ${config.linkedTable.name} table`);
        error = true;
    }
    let ptLinkedRecordId = findLinkedRecordId(columns[2], 'Physical Talents');
    if (!ptLinkedRecordId) {
        console.error(`Failed to find a record with name ${columns[2]} and aspect Physical Talents in ${config.linkedTable.name} table`);
        error = true;
    }
    let stLinkedRecordId = findLinkedRecordId(columns[3], 'Spiritual Talents');
    if (!stLinkedRecordId) {
        console.error(`Failed to find a record with name ${columns[3]} and aspect Spiritual Talents in ${config.linkedTable.name} table`);
        error = true;
    }
    let pgLinkedRecordId = findLinkedRecordId(columns[4], 'Physical Goals');
    if (!pgLinkedRecordId) {
        console.error(`Failed to find a record with name ${columns[4]} and aspect Physical Goals in ${config.linkedTable.name} table`);
        error = true;
    }
    let sgLinkedRecordId = findLinkedRecordId(columns[5], 'Spiritual Goals');
    if (!sgLinkedRecordId) {
        console.error(`Failed to find a record with name ${columns[5]} and aspect Spiritual Goals in ${config.linkedTable.name} table`);
        error = true;
    }
    let sdLinkedRecordId = findLinkedRecordId(columns[6], 'Soul Destiny');
    if (!sdLinkedRecordId) {
        console.error(`Failed to find a record with name ${columns[6]} and aspect Soul Destiny in ${config.linkedTable.name} table`);
        error = true;
    }
    if (!error) {
        await config.namesTable.updateRecordAsync(record, {
            [config.pkField.name]: [{
                id: pkLinkedRecordId
            }],
            [config.skField.name]: [{
                id: skLinkedRecordId
            }],
            [config.ptField.name]: [{
                id: ptLinkedRecordId
            }],
            [config.stField.name]: [{
                id: stLinkedRecordId
            }],
            [config.pgField.name]: [{
                id: pgLinkedRecordId
            }],
            [config.sgField.name]: [{
                id: sgLinkedRecordId
            }],
            [config.sdField.name]: [{
                id: sdLinkedRecordId
            }],
        });
        output.text(`Updated columns for ${firstName} ${middleName} ${lastName}`);
    }
} else {
    output.text('Please pick a record');
}

Hi!

Just like u i started Bubbling to create a dashboard for my Airtable.

Unfortunately l cant advise u on your script cause after no-coding for the last 2 years, my basic JS knowledge is in shambles

Though i would like to share my experience to encourage you to switch.

When building my frontend I eventually i realized its alot better in the long run to switch completely to Bubble. There’s alot of additional work and cost to deal with both and I came to a point where i found it very hard to debug where a problem is coming from.

Here are some pain points i can remember:

  1. With Bubble you can use their debugger for all your worklows whereas at this moment of time you’ll have to debug both Bubble and Airtable just to find and then fix one issue.

  2. Whenever you make changes to your Airtable you’ll have to do a little bit of reconfiguring of the API connector. It’s okay for now but when u want to scale your feature set it is really really painful.

  3. In order to be able to fully manipulate your data, you’re gonna eventually have to create intermediary workflows just so u don’t accidentally mess your data in Airtable

  4. Scheduling automation is gonna be a mess of confusion cause u can automate on both ends. So you might end up with conflicting automation and unnecessary make changes to ur data in Airtable.

These are some of the pain points i experienced and hope it convinces u to switch! :smiley:

1 Like

Thank you so much for sharing your experience and thoughts!
This is so beneficial for me as I have been at a crossroads about sharing data between the two - your points confirmed my suspicions on the topic. All in all it is just a ton of extra work no matter how you look at it.

I am very happy with my Bubble experience thus far - the capabilities go far beyond most of the other no-code app builders I’ve tried. Hopefully someone will be able to provide some insight on the script so that I can have everything all tidy in one place. :blush:

If you don’t mind me asking, how is your experience with Bubble’s performance with higher numbers of records?
My push to seek other platforms started when I maxed out the record capacity of the pro plan and had to start splitting my database. :woozy_face:
(Higher numbers of records, in my case, being around 5,000-8,000)

Thanks again for the insight! It is very helpful for me. :slight_smile:

No worries! It was great at first but I went through a lot of pain when i tried adding more features.

I have a CRM built into my app that alread accumulated over a thousand records in total and growing everyday and Bubble has been a great experience for my users so far. I use the other features in my app (thus share the same capacity) as a daily driver too and performance has been great also.

A good thing about Bubble is that there are many ways to optimise data interaction so it can always be better.

There are certain things that you’ll have to be aware to maintain performance but the Bubble community has been a great resource!

This topic was automatically closed after 70 days. New replies are no longer allowed.