Figma - Google Sheets Custom Integration

Examples of Custom Integrations in a Google Sheet

The Quickstart guide walked you through how to create an integration for a Hello World Custom Application in less than ten minutes. This guide will provide the steps to sync the member report downloaded from Figma's admin console into Torii and have the license usage displayed via the Licenses view in Torii.

A Google Sheet template with AppScript code is provided at this link.

Step 1 - Export the data from the respective product's admin console or API

  1. Here is a Figma example. The Figma API does not yet provide a detailed engagement audit log, however it does have a very rich activity log that can be downloaded from the admin console. In this case, the Figma documentation describes how to export the log here.

Step 2 - Customize the Google Sheet example for your environment

  1. Make a copy of the provided google sheet so you gain full access.
  2. Import/merge the exported data from Step 1.1 where you exported user, license and usage data from the product's admin console or API into the Google Sheet Example you made a copy of in Step 2.1 under the column headers that have been provided.
  3. Review the AppScript code that can be accessed from the Extensions --> AppScript top menu item.
  1. The Google AppScript code for the Figma Sample is provided below. This code includes requests to calls you can test in the API Explorer with the Custom Integration Quickstart guide.
    The code supplied in the different samples do not need to be modified. Instead, each sample will prompt for the API key and App ID specific to your environment when run.
    For more detailed documentation of the code within different examples, visit theRecipes section of the site.
let API_KEY;
let APP_ID;

function onOpen() {
    let ui = SpreadsheetApp.getUi();
    ui.createMenu('Torii')
        .addItem('Sync now', 'syncNow')
        .addItem('Reconfigure', 'readConfig')
        .addToUi();
}

function syncNow() {
    let docProperties = PropertiesService.getDocumentProperties();
    API_KEY = docProperties.getProperty('api_key');
    APP_ID = docProperties.getProperty('app_id');


    if (!API_KEY || !APP_ID) {
        readConfig();
        docProperties = PropertiesService.getDocumentProperties();
        API_KEY = docProperties.getProperty('api_key');
        APP_ID = docProperties.getProperty('app_id');
    }

    let file = createJsonFile();
    let fileId = uploadFile(file);
    syncIntegration(fileId);
}

function readConfig() {
    let ui = SpreadsheetApp.getUi();
    let result = ui.prompt("Please enter Torii API Key");
    API_KEY = result.getResponseText();

    result = ui.prompt("Please enter the APP ID");
    APP_ID = result.getResponseText();

    let docProperties = PropertiesService.getDocumentProperties();
    docProperties.setProperty('api_key', API_KEY);
    docProperties.setProperty('app_id', APP_ID);
}

function createJsonFile() {
    let = sheet = SpreadsheetApp.getActive();
    let data = sheet.getDataRange().getValues();
    Logger.log(SpreadsheetApp.getActiveSheet().getName());
    Logger.log(data);
    data.shift();


    let fullDataSet = [];
    let licensesMap = {};
    let userDataSet = [];

    data.forEach(function(row) {
            let fullName = row[0];
            let firstName = fullName.split(' ').slice(0, -1).join(' ');
            let lastName = fullName.split(' ').slice(-1).join(' ');
            let email = row[1];
            let role = row[2];
            let figmaAccount = "Figma - " + row[3];
            let figjamAccount = "Figjam - " + row[4];

            let figmalastseen = (row[9] == "") ? null : Utilities.formatDate(new Date(row[9]), "UTC", 'YYYY-MM-dd');
            let figjamlastseen = (row[10] == "") ? null : Utilities.formatDate(new Date(row[10]), "UTC", 'YYYY-MM-dd');
            let figmalastedit = (row[11] == "") ? null : Utilities.formatDate(new Date(row[11]), "UTC", 'YYYY-MM-dd');
            let figjamlastedit = (row[12] == "") ? null : Utilities.formatDate(new Date(row[12]), "UTC", 'YYYY-MM-dd');



            fullDataSet.push({
                email: email,
                firstName: firstName,
                lastName: lastName,
                status: "active",
                externalStatus: "active",
                role: role,
                figmaAccount: figmaAccount,
                figjamAccount: figjamAccount,
                figmalastseen: figmalastseen,
                figjamlastseen: figjamlastseen,
                figmalastedit: figmalastedit,
                figjamlastedit: figjamlastedit
            });

        }

    );

    for (var i = 0; i < Object.keys(fullDataSet).length; i++) {
        if (fullDataSet[i].role == "member" || fullDataSet[i].role == "admin" ) {
            let userFigma;
            if (licensesMap[fullDataSet[i].figmaAccount]) {
                userFigma = licensesMap[fullDataSet[i].figmaAccount]
            } else {
                userFigma = []
            }

            userFigma.push({
                email: fullDataSet[i].email,
                licenseStatus: "active",
                lastUsedDate: (fullDataSet[i].figmaAccount.indexOf("Editor") > 0) ? fullDataSet[i].figmalastedit : fullDataSet[i].figmalastseen
            })
            licensesMap[fullDataSet[i].figmaAccount] = userFigma;

            let userFigjam;
            if (licensesMap[fullDataSet[i].figjamAccount]) {
                userFigjam = licensesMap[fullDataSet[i].figjamAccount]
            } else {
                userFigjam = []
            }

            userFigjam.push({
                email: fullDataSet[i].email,
                licenseStatus: "active",
                lastUsedDate: (fullDataSet[i].figjamAccount.indexOf("Editor") > 0) ? fullDataSet[i].figjamlastedit : fullDataSet[i].figjamlastseen
            })
            licensesMap[fullDataSet[i].figjamAccount] = userFigjam;

        } else if(fullDataSet[i].role == "guest") {
          let userGuestFigma;
          let userGuestFigmaAcount = fullDataSet[i].figmaAccount.concat(" (Guest)");

          if (licensesMap[userGuestFigmaAcount]) {
                userGuestFigma = licensesMap[userGuestFigmaAcount]
            } else {
                userGuestFigma = []
            }

            userGuestFigma.push({
                email: fullDataSet[i].email,
                licenseStatus: "active",
                lastUsedDate: (userGuestFigmaAcount.indexOf("Editor") > 0) ? fullDataSet[i].figmalastedit : fullDataSet[i].figmalastseen
            })
            licensesMap[userGuestFigmaAcount] = userGuestFigma;

            let userGuestFigjam;
            let userGuestFigjamAcount = fullDataSet[i].figjamAccount.concat(" (Guest)");

            if (licensesMap[userGuestFigjamAcount]) {
                userGuestFigjam = licensesMap[userGuestFigjamAcount]
            } else {
                userGuestFigjam = []
            }

            userGuestFigjam.push({
                email: fullDataSet[i].email,
                licenseStatus: "active",
                lastUsedDate: (userGuestFigjamAcount.indexOf("Editor") > 0) ? fullDataSet[i].figjamlastedit : fullDataSet[i].figjamlastseen
            })
            licensesMap[userGuestFigjamAcount] = userGuestFigjam;

        } else {
            let userOther;
            if (licensesMap[fullDataSet[i].role]) {
                userOther = licensesMap[fullDataSet[i].role]
            } else {
                userOther = []
            }
            userOther.push({
                email: fullDataSet[i].email,
                licenseStatus: "active"
            })
            licensesMap[fullDataSet[i].role] = userOther;
        }
        //Create the main user data set
        const isUserFound = userDataSet.some(element => {
            if (element.email === fullDataSet[i].email) {
                return true;
            }

            return false;
        });
        if (isUserFound === false) {
            userDataSet.push({
                email: fullDataSet[i].email,
                firstName: fullDataSet[i].firstName,
                lastName: fullDataSet[i].lastName,
                status: "active"
            });

        }

    }

    const licenses = Object.keys(licensesMap).map(licenseName => {
        const users = licensesMap[licenseName]
        return {
            name: licenseName,
            users: users
        }
    })

    let file = {
        users: userDataSet,
        licenses: licenses
    }
    Logger.log(file);
    console.log(JSON.stringify(file));
    return file;
}

function syncIntegration(fileId) {
    response = UrlFetchApp.fetch('https://api.toriihq.com/beta/services/sync/custom', {
        muteHttpExceptions: false,
        'method': 'put',
        'contentType': 'application/json',
        'payload': JSON.stringify({
            idFile: fileId,
            idAppAccount: APP_ID
        }),
        headers: {
            'Authorization': `Bearer ${API_KEY}`
        }
    });
    Logger.log(response);
}

function uploadFile(file) {
    let response = UrlFetchApp.fetch('https://api.toriihq.com/beta/files/url?name=my_file.json&type=customIntegrationData', {
        headers: {
            'Authorization': `Bearer ${API_KEY}`
        }
    });

    let jsonResponse = JSON.parse(response);
    let s3url = jsonResponse.uploadFileParameters.url;
    let filePath = jsonResponse.uploadFileParameters.filePath;
    Logger.log(s3url);
    Logger.log(filePath);

    response = UrlFetchApp.fetch(s3url, {
        'method': 'put',
        'contentType': 'customIntegrationData',
        'payload': JSON.stringify(file)
    });

    // Get file id
    response = UrlFetchApp.fetch('https://api.toriihq.com/beta/files', {
        method: 'post',
        contentType: 'application/json',
        headers: {
            'Authorization': `Bearer ${API_KEY}`
        },
        payload: JSON.stringify({
            path: filePath,
            type: 'customIntegrationData'
        }),
        headers: {
            'Authorization': `Bearer ${API_KEY}`
        }
    });

    jsonResponse = JSON.parse(response);
    let fileId = jsonResponse.id;
    Logger.log(fileId);
    return fileId;
};

Step 3 - Create the API Key and Custom Integration in your Torii console

  1. Create an API key from the Settings tab in the API Access submenu item.
  1. Create a custom integration in the Integrations tab and map it to the app you are creating the integration for.

🚧

Warning: Make sure to copy the App Account ID value that is returned as you will need it when you sync the data from the Google Sheet.

Step 4 - Sync the integration from the Google Sheet

  1. Using the Torii --> Sync Now menu option, run the AppScript to sync the data from the spreadsheet into Torii. This will execute the code, format the data, and push said data to the Torii database. The script will prompt you for both the API key and the App Account ID.

  1. The first run of the script in your environment will request authorization. Use your account or a service account in your environment to grant access to the necessary scopes.
  1. Once you've completed the authorization, you will have to select the Torii --> Sync Now menu item again. The script will prompt you for the API Key and App Account ID.

Step 5 - Confirm the custom integration synced successfully via the Torii console

  1. From the Integrations tab, check that the custom integration tile has a green checkmark.
  1. Check that the data passed in through the integration has been processed by the platform and is available in the Licenses view.

👍

Congratulations! You've successfully synced a custom integration. You can use this approach to sync data into Torii for any apps that don't have APIs but do have some form of downloadable extract of users, licenses, and usage data.