Quinn Keast

Closing the product gap: a new way of building internal tools with Airplane

Organization
Airplane

Role
Staff Product Designer

Date
2023 – 2024

Airplane is the developer platform for internal tools. I joined Airplane at the beginning of 2023 as the company’s second designer, shortly after their Series B, working to define the future of internal tooling with a focus on developer-centric concepts and workflows.

This case study is the story of a deeply systematic, cross-platform effort, where the role of a designer blurred: along with high fidelity design craft, I also designed and contributed to the underlying APIs, syntax, and the intersection from systems to surfaces. Visuals will be added over time.

Airplane started with a simple model around internal tools expressed through “Tasks.” Developers could configure these tasks with input parameters, some logic, and deploy those tasks to the Airplane platform. Their team could execute tasks from a library on Airplane’s web app and get a simple output response in the form of an auto-formatted table.

Over time, tasks were built out to become more powerful: developers could code complex workflows, orchestrate workflows that triggered other tasks, interrupt task execution to ask for external approvals or request more input data, interact with connected resources like external databases or LLMs, and more.

Before I joined Airplane, the team built out another product area called Views. Views were a response to customer feedback and prospective customer requirements: while tasks were a powerful tool for running actions and workflows, competing tools like Retool let people build their own dashboards and complex UIs. Airplane was invariably positioned against these tools by prospective customers. Views introduced a new entity and provided a complete library of React components that could be used to build UIs in Airplane while closing this positioning gap.

Tasks and views solved problems on two ends of a spectrum: on the tasks end, developers could create simple input UIs and orchestration with just YAML or JSON. On the views end, developers could build completely custom dashboards with Airplane’s component library, or even use their own dependencies, deploying the resulting entity on the Airplane platform for use.

However, there was a gap in the middle of this spectrum.

Tasks weren’t enough for many of our customers. Sometimes, customers wanted to do things like use the output from one task as the values for a select in another task. But since tasks didn’t provide these capabilities, they had no choice but to reach for views. For these customers, switching to views was like reaching for a sledgehammer when a ball peen hammer wasn’t enough.

Many customers also didn’t want to use views. One of Airplane’s core value propositions was the ease of configuring tasks, which were the core building block of the Airplane platform. Switching from a configuration-based way of building UIs with tasks to using React to build completely custom dashboard UIs represented a big conceptual and complexity leap that many developers resented.

I was asked to own this ambiguous problem area. There was pain here. We felt it ourselves. But it wasn't clear what the solution was. The way I phrased it was: how might we make it easier and faster for developers to build more complex UIs in Airplane before they needed to reach for views?

I drove this project and explored the space together with my team’s engineering leads. We held conversations and interviews with many of our customers to get a better sense of what was working or wasn’t with tasks and views, where they were experiencing pain, and what workarounds they were using as a result. We synthesized these together and came up with common themes and opportunities.

Based on this, I conceptualized and proposed Project Flight Deck: a future-looking, ambitious, yet iterative effort that would close the gap between tasks and views. It would reimagine tasks to make them more inherently powerful and useful, then extend them as a core building block for composing UIs with a new entity called Pages. Tasks and Pages would together form the basis of a complete platform for building UIs in Airplane.

Over the course of about 5 months, I led iterative cycles of shaping and building for a cross-product, 0-1 roadmap that would get us to our vision in a way that would deliver real value at each iterative step. Some of the projects we tackled included:

Dynamic task inputs and outputs. With dynamic task inputs, we introduced the concept of task-backed and logically-templated select parameters. With dynamic task outputs, we gave developers control over what and how task outputs would be presented beyond the default tables.

import airplane from "airplane";

export const catsTask = airplane.task(
  {
    slug: "cats",
    name: "Cats task",
    outputDisplay: { type: "descriptionList" },
  },

  async function (params) {
    return [
      { term: "Mittens", description: "Maine coon" },
      { term: "Hamilton", description: "Ragdoll" },
      { term: "Whiskers", description: "British shorthair" },
    ];
  },
);

Navigation between tasks. We introduced the ability for developers to trigger navigation from a task’s output to another task using links or peeks, passing along related data to pre-fill subsequent task inputs. With task output navigation and a redesigned peek system, developers could build complex apps in Airplane that treated the Airplane platform itself as a surface for building apps.

import airplane from "airplane";

export const listTeamsTask = airplane.task(
  {
    slug: "list_teams",
    name: "List teams",
    outputDisplay: {
      type: "table",
      actions: [
        {
          page: "team_inspector",
          label: "Open team inspector",
          as: "center_peek",
        },
      ],
      rowActions: [
        {
          task: "update_revenue",
          label: "Update revenue",
          paramValues: {
            id: "{{row.id}}",
            revenue: "{{row.revenue}}",
          },
        },
      ],
    },
  },
  ...

Pages as a way of organizing the library. An ongoing pain point in Airplane’s platform was that the library’s structure was distinct from the code and configuration files that defined tasks and views. While tasks and views could be checked into source control and deployed automatically as part of CI/CD, folders in the library could only be created manually by admins within the Airplane UI. This became a scaling challenge for organizations making heavy use of Airplane, and a usability challenge for helping team members find the tools relevant to them.

airplane/  PROJECT ROOT
 library/
  top_level_task.airplane.ts
  support/
   my_task_airplane.py
  sales/
  nested_page/
   rest_task.task.yaml
  my_sales_task.airplane.ts
  sales_query.sql
  sales_query.task.yaml
  my_view.airplane.tsx
 other_task.airplane.ts
 airplane.yaml
 package.json
 requirements.txt

Configurable Page layouts. We defined Pages as having a default layout that presented its children in a list or card view. Next, we gave folks control over basic top-level actions and descriptions, making pages an effective launching point for internal teams.

# index.airplane.yaml
name: Important page name
description: This is a very important page
icon: 🔨
additionalTasks:
  - top_level_task
hiddenTasks:
  - "*"
additionalRunbooks:
  - test_book
actions:
  - label: Get sales data
    task: sales_query
  - label: Go to other task
    task: other_task
    as: full_page

Task as building blocks. The magic moment that connected it all together came when a developer could configure a page’s layout to present either task inputs or outputs. Now dynamic task inputs and outputs could shine, as the task itself became a UI block shown on the page. Suddenly, developers could create useful, complex dashboards, cross-task and cross-page workflows, and more, all in a source-controlled, config-first way, all without having to reach for views.

name: Team inspector
description: Inspect teams
icon: 🔨
actions:
  - label: Create team
    task: create_team
layout:
  - type: markdown
    value: This is an example markdown text block within the page layout.
  - type: section
    columns:
      - type: task
        slug: get_total_revenue
        autoRun: true
      - type: task
        slug: get_top_team
        autoRun: true
  - type: task
    slug: list_teams
    autoRun: true

I worked directly with customers throughout this project effort to share updates and collect feedback. One of the most fascinating parts of designing and shipping this work was in how different customers responded to different aspects of the whole. For some customers, simply enabling task-backed select options meant they could stop using views altogether, and radically change how they build tools with Airplane (part of why we worked on this so early!). For other customers, using Pages as an organizing entity for tasks in code saved countless hours of manual work. Others were excited to bring together the different building blocks to build complex tools with Airplane as a canvas.

At this point in the story, ✨startup things✨ happened: after shipping the first complete iteration of Pages, Airplane was acquired by Airtable.