Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(integrations): Add Airtable integration #12951

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
6 changes: 6 additions & 0 deletions integrations/airtable/.eslintrc.js
charlescatta marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
extends: ['eslint:recommended', 'plugin:prettier/recommended'],
rules: {
'prettier/prettier': 'warn',
},
}
50 changes: 50 additions & 0 deletions integrations/airtable/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# These are some examples of commonly ignored file patterns.
charlescatta marked this conversation as resolved.
Show resolved Hide resolved
# You should customize this list as applicable to your project.
# Learn more about .gitignore:
# https://www.atlassian.com/git/tutorials/saving-changes/gitignore

# Node artifact files
node_modules/
dist/

# Compiled Java class files
*.class

# Compiled Python bytecode
*.py[cod]

# Log files
*.log

# Package files
*.jar

# Maven
target/
dist/

# JetBrains IDE
.idea/

# Unit test reports
TEST*.xml

# Generated by MacOS
.DS_Store

# Generated by Windows
Thumbs.db

# Applications
.botpress
*.app
*.exe
*.war

# Large media files
*.mp4
*.tiff
*.avi
*.flv
*.mov
*.wmv
4 changes: 4 additions & 0 deletions integrations/airtable/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"semi": false,
"singleQuote": true
}
6 changes: 6 additions & 0 deletions integrations/airtable/icon.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions integrations/airtable/integration.definition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { IntegrationDefinition } from '@botpress/sdk'
import { name } from './package.json'
charlescatta marked this conversation as resolved.
Show resolved Hide resolved

import {
configuration,
states,
user,
channels,
actions,
} from './src/definitions'

export default new IntegrationDefinition({
name,
version: '0.2.0',
readme: 'readme.md',
icon: 'icon.svg',
configuration,
channels,
user,
actions,
events: {},
states,
})
26 changes: 26 additions & 0 deletions integrations/airtable/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "@botpresshub/airtable",
"scripts": {
"start": "pnpm bp serve",
"build": "pnpm bp build",
"deploy": "pnpm bp deploy",
"type:check": "tsc --noEmit",
"test": "echo \"Tests not implemented yet.\""
},
"keywords": [],
"private": true,
"author": "",
"license": "MIT",
"dependencies": {
"@botpress/client": "workspace:*",
"@botpress/sdk": "workspace:*",
"airtable": "^0.12.2",
"axios": "^1.5.0",
"zod": "3.20.6"
},
"devDependencies": {
"@types/node": "^18.11.17",
"ts-node": "^10.9.1",
"typescript": "^4.9.4"
}
}
58 changes: 58 additions & 0 deletions integrations/airtable/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Botpress Airtable Integration

This integration allows you to connect your Botpress chatbot with Airtable, a popular cloud-based database and collaboration platform. With this integration, you can easily manage your Airtable bases, tables, and records directly from your chatbot.

## Setup

To set up the integration, you will need to provide your Airtable `accessToken`, `baseId`, and `endpointUrl` (Optional). Once the integration is set up, you can use the built-in actions to manage your Airtable data.

For more detailed instructions on how to set up and use the Botpress Airtable integration, please refer to our documentation.

### Prerequisites

Before enabling the Botpress Airtable Integration, please ensure that you have the following:

- A Botpress cloud account.
- `accessToken`, `baseId`, and `endpointUrl` (Optional) generated from Airtable.

### Enable Integration

To enable the Airtable integration in Botpress, follow these steps:

1. Access your Botpress admin panel.
2. Navigate to the “Integrations” section.
3. Locate the Airtable integration and click on “Enable” or “Configure.”
4. Provide the required `accessToken`, `baseId`, and `endpointUrl` (Optional).
5. Save the configuration.

### Scope and resources/access of the Personal Access Token

Personal Access Token act as the user account granting access, with the following limitations:

- Scope: What actions the token can perform.
- Resources/access: Which bases and workspaces the token can access. Tokens may be granted access to individual or all bases/workspaces. These can be listed using the list bases endpoint.

For example, to update a record in a base via the API, the user who granted the token must have editor access to the base. In addition, the token must have both the correct scope (**data.records:write**) and the base added as a resource.

For personal access tokens, scopes and resources/access are configured individually from **/create/tokens**.

## Usage
charlescatta marked this conversation as resolved.
Show resolved Hide resolved

Once the integration is enabled, you can start using Airtable features from your Botpress chatbot. The integration offers several actions for interacting with Airtable, such as `getBaseTables`, `getTableRecords`, `createTable`, `updateTable`, `createRecord`, and `updateRecord`. These actions allow you to get tables and records from a base, create and update tables, and create and update records.

For more details and examples, refer to the Botpress and Airtable documentation.

## Limitations

- Free Plan Limits:
charlescatta marked this conversation as resolved.
Show resolved Hide resolved
- Records: Limited to **1,000 records per base**.
- API: Limited to **1,000 API calls per month**.
- Commenters: Limited to **50 commenters per workspace**.
- Sync and extensions: Available on the **Team plan and above**. To continue using Airtable with higher limits, you can upgrade to the Team plan.
- Rate limiting: Airtable employs a number of safeguards against bursts of incoming traffic to help maximize its stability. The API is limited to **5 requests per second per base**. If you exceed this rate, you will receive a **429 status code** and will need to wait **30 seconds** before subsequent requests will succeed. Airtable may change the enforced API rate limits or enforce additional types of limits in their sole discretion, including tiered based on pricing plan. Upon receiving a 429 status code, API integrations should back-off and wait before retrying the API request. The official JavaScript client has built-in back-off and retry logic. If you anticipate a higher read volume, it is recommended to use a caching proxy.

## Contributing
charlescatta marked this conversation as resolved.
Show resolved Hide resolved

Contributions are welcome! Please submit issues and pull requests.

Enjoy seamless helpdesk and customer support integration between Botpress and Airtable!
32 changes: 32 additions & 0 deletions integrations/airtable/src/actions/create-record.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { createRecordInputSchema } from '../misc/custom-schemas'
import type { Implementation } from '../misc/types'
import { getClient } from '../utils'

export const createRecord: Implementation['actions']['createRecord'] = async ({
ctx,
logger,
input,
}) => {
const validatedInput = createRecordInputSchema.parse(input)
const AirtableClient = getClient(ctx.configuration)
let record
try {
record = await AirtableClient.createRecord(
validatedInput.tableIdOrName,
JSON.parse(validatedInput.fields)
)
record = {
_rawJson: record.fields,
id: record.id,
}
logger.forBot().info(`Successful - Create Record - ${record.id}`)
} catch (error) {
record = {
_rawJson: {},
id: '',
}
logger.forBot().debug(`'Create Record' exception ${JSON.stringify(error)}`)
}

return record
}
28 changes: 28 additions & 0 deletions integrations/airtable/src/actions/create-table.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { createTableInputSchema } from '../misc/custom-schemas'
import type { Implementation } from '../misc/types'
import { fieldsStringToArray, getClient } from '../utils'

export const createTable: Implementation['actions']['createTable'] = async ({
ctx,
logger,
input,
}) => {
const validatedInput = createTableInputSchema.parse(input)
const AirtableClient = getClient(ctx.configuration)
let table
try {
table = await AirtableClient.createTable(
validatedInput.name,
fieldsStringToArray(validatedInput.fields),
validatedInput.description
)
logger
.forBot()
.info(`Successful - Create Table - ${table.id} - ${table.name}`)
} catch (error) {
table = {}
logger.forBot().debug(`'Create Table' exception ${JSON.stringify(error)}`)
}

return table
}
35 changes: 35 additions & 0 deletions integrations/airtable/src/actions/get-table-records.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {
getTableRecordsInputSchema,
getTableRecordsOutputSchema,
} from '../misc/custom-schemas'
import type { Implementation } from '../misc/types'
import { getClient } from '../utils'

export const getTableRecords: Implementation['actions']['getTableRecords'] =
async ({ ctx, logger, input }) => {
const validatedInput = getTableRecordsInputSchema.parse(input)
const AirtableClient = getClient(ctx.configuration)
let records
try {
records = await AirtableClient.getTableRecords(
validatedInput.tableIdOrName
)
records = records.map((record) => {
return {
_rawJson: record.fields,
id: record.id,
}
})
logger
.forBot()
.info(
`Successful - Get Table Records - ${validatedInput.tableIdOrName}`
)
} catch (error) {
logger
.forBot()
.debug(`'Get Table Records' exception ${JSON.stringify(error)}`)
}

return getTableRecordsOutputSchema.parse({ records })
}
13 changes: 13 additions & 0 deletions integrations/airtable/src/actions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createRecord } from './create-record'
import { createTable } from './create-table'
import { getTableRecords } from './get-table-records'
import { updateRecord } from './update-record'
import { updateTable } from './update-table'

export default {
getTableRecords,
createTable,
updateTable,
createRecord,
updateRecord,
}
33 changes: 33 additions & 0 deletions integrations/airtable/src/actions/update-record.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { updateRecordInputSchema } from '../misc/custom-schemas'
import type { Implementation } from '../misc/types'
import { getClient } from '../utils'

export const updateRecord: Implementation['actions']['updateRecord'] = async ({
ctx,
logger,
input,
}) => {
const validatedInput = updateRecordInputSchema.parse(input)
const AirtableClient = getClient(ctx.configuration)
let record
try {
record = await AirtableClient.updateRecord(
validatedInput.tableIdOrName,
validatedInput.recordId,
JSON.parse(validatedInput.fields)
)
record = {
_rawJson: record.fields,
id: record.id,
}
logger.forBot().info(`Successful - Update Record - ${record.id}`)
} catch (error) {
record = {
_rawJson: {},
id: '',
}
logger.forBot().debug(`'Update Record' exception ${JSON.stringify(error)}`)
}

return record
}
28 changes: 28 additions & 0 deletions integrations/airtable/src/actions/update-table.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { updateTableInputSchema } from '../misc/custom-schemas'
import type { Implementation } from '../misc/types'
import { getClient } from '../utils'

export const updateTable: Implementation['actions']['updateTable'] = async ({
ctx,
logger,
input,
}) => {
const validatedInput = updateTableInputSchema.parse(input)
const AirtableClient = getClient(ctx.configuration)
let table
try {
table = await AirtableClient.updateTable(
validatedInput.tableIdOrName,
validatedInput.name,
validatedInput.description
)
logger
.forBot()
.info(`Successful - Update Table - ${table.id} - ${table.name}`)
} catch (error) {
table = {}
logger.forBot().debug(`'Update Table' exception ${JSON.stringify(error)}`)
}

return table
}