Airtable Blocks SDK

repository·master·Indexed 42 days ago

https://github.com/airtable/blocks

A development framework for creating Airtable extensions. Includes the @airtable/blocks core SDK, @airtable/blocks-cli for project initialization, local development, and deployment, and @airtable/blocks-testing for writing automated React tests using TestDriver and simulated Airtable environments.

Tokens
22.4K
Snippets
76
Records
94
Agent score
54%

What's inside Airtable Blocks

  1. Overview of the Blocks SDK

    master

    The Blocks SDK allows developers to create custom Airtable extensions, such as integrations, visualizations, and internal tools.

    Key components of the repository include:

    • @airtable/blocks: The core SDK used for building extensions.
    • @airtable/blocks-testing: A framework for writing automated tests for your extensions.
    • @airtable/blocks-cli: A CLI tool used to run your extensions locally.
  2. Key concepts in Airtable Extension testing

    master

    To write automated tests for Airtable Extensions, you must understand four core abstractions:

    • Test fixture data: A JSON-serializable representation of an Airtable Base's state, including Tables, Views, Fields, and Records. This data is used to initialize the simulated environment.
    • TestDriver: The primary JavaScript interface for creating and manipulating a simulated Airtable environment. It provides methods for operations not available in the standard SDK.
    • AirtableInterface: The layer that mediates between models and the backend. In testing, it simulates production behavior (like asynchronous message passing) without actual network transmission.
    • Mutations: Instructions describing changes to the Base that travel through the AirtableInterface to the backend.
  3. Extend the Testing API

    master

    If you need to simulate operations that are not currently supported, you can extend the testing platform:

    • Implementing internal support: Required for every Base operation to allow scripting in test environments. This can range from a simple "no-op" for optimistic mutations to complex simulation logic that mimics backend responses.
    • Implementing a dedicated API: Use this when an operation cannot be expressed through the public SDK API (e.g., operations the SDK intentionally omits, like changing user permissions). If an operation can be performed via the public SDK (like createTableAsync), you should use the SDK directly in your tests rather than adding a new TestDriver method.
  4. How to implement a custom bundler for Airtable Blocks

    master

    If the default Webpack bundler does not meet your needs, you can replace it entirely by providing a custom CommonJS module.

    Steps to implement:

    1. Create a JavaScript file (e.g., index.js) that exports a function returning an instance of a Bundler class.
    2. The Bundler class must implement the following asynchronous methods:
      • bundleAsync(options: ReleaseBundleOptions): Promise<void>: For release builds.
      • findDependenciesAsync(options: SubmitFindDependenciesOptions): Promise<{files: Array<string>}>: Used during block submit.
      • startDevServerAsync(options: RunDevServerOptions & RunDevServerMethods): Promise<void>: For development builds.
      • teardownAsync(): Promise<void>: For graceful shutdown.
    3. Update your block.json to point to your bundler entry file:
    {
        "bundler": {
            "module": "./bundler/index.js"
        }
    }

    Note: Custom bundlers must produce output that conforms to the Airtable platform's expected format, calling convention, and file structure.

  5. Write automated tests for Airtable Extensions

    master

    Automated testing for Airtable Extensions (which must be written using React) follows a specific lifecycle. Every test file must import TestDriver from @airtable/blocks-testing to ensure proper instrumentation.

    Test Lifecycle Pattern:

    1. Initialize TestDriver: Create an instance by passing in your Test fixture data.
    2. Render the Extension: Wrap your Extension component inside the testDriver.Container component using a testing library like @testing-library/react.
    3. Simulate Input: Provide input via simulated user interactions (e.g., using @testing-library/user-event) or simulated backend behaviors (e.g., using the TestDriver API or the Blocks SDK).
    4. Verify Results: Assert that the UI updated correctly (using @testing-library/react) or that specific internal behaviors occurred (using testDriver.watch).
    import { TestDriver } from '@airtable/blocks-testing';
    import { render, screen } from '@testing-library/react';
    import userEvent from '@testing-library/user-event';
    
    // 1. Create TestDriver with fixture data
    const testDriver = new TestDriver({
        /* fixture data here */
    });
    
    // 2. Render Extension inside TestDriver.Container
    render(
        <testDriver.Container>
            <MyExtension />
        </testDriver.Container,
    );
    
    // 3. Provide input (User Interaction)
    const input = screen.getByLabelText('Cheeses');
    const option = screen.getByText('Gruyere');
    await userEvent.selectOptions(input, [option]);
    
    // OR Provide input (Simulated Backend Behavior)
    await testDriver.base.createTableAsync('a new table', [
        {name: 'address', type: FieldType.EMAIL},
    ]);
    
    // 4. Verify response (UI check)
    const checkbox = screen.getByRole('checkbox');
    expect(checkbox.checked).toBe(true);
    
    // OR Verify response (Internal behavior check)
    const recordIds = [];
    testDriver.watch('expandRecord', ({recordId}) => recordIds.push(recordId));
    // ... interaction ...
    expect(recordIds).toEqual(['reca']);
  6. Use CSS in Airtable Blocks extensions

    master

    The CLI supports CSS files by default using css-loader and style-loader via Webpack. You can import CSS files directly into your JavaScript/TypeScript files.

    Example usage:

    // styles.css
    .red {
      color: red;
    }
    
    import {initializeBlock} from '@airtable/blocks/ui';
    import React from 'react';
    import './styles.css';
    
    function MyExtension() {
        return <div className="red">Hello world</div>;
    }
    
    initializeBlock(() => <MyExtension />);
    import {initializeBlock} from '@airtable/blocks/ui';
    import React from 'react';
    import './styles.css'
    
    function MyExtension() {
        return <div className="red">Hello world</div>;
    }
    
    initializeBlock(() => <MyExtension />);
  7. Customize the webpack configuration

    master

    To customize the webpack configuration used by @airtable/blocks-cli, follow these two steps:

    1. Create a bundler.js file in your project root. This file must export a default function that calls createBundler from @airtable/blocks-webpack-bundler, passing a configuration function. This configuration function receives the baseConfig and allows you to modify it.

    2. Update block.json to point to your custom bundler module by adding a bundler field with a module key.

    // bundler.js
    const createBundler = require('@airtable/blocks-webpack-bundler').default;
    
    function createConfig(baseConfig) {
        // Add any desired customizations here
        return baseConfig;
    }
    
    exports.default = () => {
        return createBundler(createConfig);
    };
    {
        "version": "1.0",
        "frontendEntry": "./frontend/index.js",
        "bundler": {
            "module": "./bundler.js"
        }
    }
  8. Configure Popover placement and viewport fitting

    master

    The Popover component allows you to control where the floating content appears relative to its anchor element and how it behaves when it approaches the edge of the screen.

    Placement

    You can specify horizontal (placementX) and vertical (placementY) positions using PopoverPlacements.

    Horizontal options (PopoverPlacementX):

    • PopoverPlacements.LEFT
    • PopoverPlacements.CENTER (Default)
    • PopoverPlacements.RIGHT (Default)

    Vertical options (PopoverPlacementY):

    • PopoverPlacements.TOP
    • PopoverPlacements.CENTER (Default)
    • PopoverPlacements.BOTTOM (Default)

    You can also use placementOffsetX and placementOffsetY to provide pixel offsets from these positions.

    Viewport Fitting

    Use fitInWindowMode to dictate behavior when the popover would otherwise be offscreen:

    • FitInWindowModes.NONE: Allows the popover to be placed offscreen.
    • FitInWindowModes.FLIP: If the placement would be offscreen, the component attempts to flip the placement to the opposite side.
    • FitInWindowModes.NUDGE: If the placement would be offscreen, the component nudges the popover just enough to keep it within the viewport.
  9. Handle permission check results

    master

    When performing actions that require authorization, the system returns a PermissionCheckResult. This is a discriminated union that tells you if the user has permission and, if not, provides a reason.

    • Success: Returns SuccessfulPermissionCheckResult where hasPermission is true.
    • Failure: Returns UnsuccessfulPermissionCheckResult where hasPermission is false and includes a reasonDisplayString intended for user display.
    type PermissionCheckResult =
        | SuccessfulPermissionCheckResult
        | UnsuccessfulPermissionCheckResult;
    
    interface SuccessfulPermissionCheckResult {
        hasPermission: true;
    }
    
    interface UnsuccessfulPermissionCheckResult {
        hasPermission: false;
        reasonDisplayString: string;
    }