Collecting User Configuration

When the user installs an IntegrationIntegration - An integration is the place the you write your code to get things done! Written in NodeJS, an integration runs in Fusebit's secure and scalable environment to translate between the needs of your backend application and the remote service or services you're connecting to., a series of steps take place to collect the necessary authorization information to allow the Integration to connect to any third-party systems it needs. Optionally, the developer can add steps to that flow to collect any additional parameters the Integration needs from the user.

For example, the user may need to specify the project they are interested to work with in the third-party system, or they may need to select which fields of a given object they are interested in. This configuration information gets stored in that user's InstallInstall - An install is a configured integration with a set of identity's and other values that represent a single user of the integration. You can search for instances by tags or by the special value of a Tenant (shh - it's also a tag, but don't tell anyone!). and is available to the integration code at runtime.

This guide walks you through the process of adding a configuration screen to your Integration's install flow.

Design the Configuration Screen

Fusebit's configuration screens are based on the commonly-used JSONForms library, please refer to it's documentation as necessary.

The first step is to define the screen's UI Schema. Create a new file in your Integration and name it uiSchema.json. This example defines a single control with it's properties bound to team-name.

{
  "type": "VerticalLayout",
  "elements": [
    {
      "type": "HorizontalLayout",
      "elements": [
        {
          "type": "Control",
          "scope": "#/properties/team-name"
        }
      ]
    }
  ]
}

Create a separate JSON Schema file named schema.json, where you can define the data backing the control:

{
  "type": "object",
  "properties": {
    "team-name": {
      "type": "string",
      "enum": ["Engineering", "Growth"]
    }
  }
}

Add an Endpoint in the Integration

In the integration.js file, add the following two endpoints, which will render the form and collect the data submitted by the user.

const schema = require('./schema.json');
const uiSchema = require('./uiSchema.json');

router.get('/api/form', async (ctx) => {  
  const [form, contentType] = integration.response.createJsonForm({
    schema,
    uiSchema,
    dialogTitle: 'Choose Your Team',
    submitUrl: 'form/submitted',
    state: {
      session: ctx.query.session,
    },
  });
  ctx.body = form;
  ctx.header['Content-Type'] = contentType;
});

router.post('/api/form/submitted', async (ctx) => {
  const pl = JSON.parse(ctx.req.body.payload);
  await superagent
    .put(`${ctx.state.params.baseUrl}/session/${pl.state.session}`)
    .set('Authorization', `Bearer ${ctx.state.params.functionAccessToken}`)
    .send({ output: pl.payload });
  return ctx.redirect(`${ctx.state.params.baseUrl}/session/${pl.state.session}/callback`);
});

In the components section of fusebit.json add the following section for the form. Set any ConnectorConnector - A connector is the package from Fusebit that manages the relationship between one or more integrations and a specific service. One of the most common types of connector is an OAuth connector, which takes care of the OAuth negotiation between your customers and the service you're integrating, so that you don't have to!s as dependencies using the dependsOn property.

{
  "components": [
    {
      "name": "linearConnector"
      // Removed for brevity
    },
    {
      "name": "form",
      "path": "/api/form",
      "skip": false,
      "entityId": "{ this integration name }",
      "dependsOn": ["linearConnector"],
      "entityType": "integration"
    }
  ]
  // Removed for brevity
}

πŸ‘

Check that the entityId is correct

Make sure that the entityId in the form component is the entityId of the Integration you are adding it to, and not the Connector that is also present.

Access the Configuration Value

After the Integration is installed, you can access any configuration values as shown below:

router.post('/api/tenant/:tenantId/test', integration.middleware.authorizeUser('install:get'), async (ctx) => {
  const installs = await integration.tenant.getTenantInstalls(ctx, ctx.params.tenantId)

  if (installs.length === 0) {
    ctx.throw(404, `Cannot find an Integration Install associated with tenant ${ctx.params.tenantId}`);
  }

  if (installs.length > 1) {
    ctx.throw(400, `Too many Integration Installs found with tenant ${ctx.params.tenantId}`);
  }

  const teamName = installs[0].data.form['team-name'];

  // Rest of the integration logic, using the teamName config value
  
});

Supplying custom defaults

Many configuration options will have sensible default values which can be supplied prior to the configuration of the form. Sometimes these values are unique defaults for a specific Tenant's configuration form, and other times they are secrets used by a Connector - such as the Client Credential Flow Connector.

Sessions support inputs on a per-component basis supplied as part of the initial POST to the session endpoint. Suppose your integration has these components:

{
  "components": [
    {
      "name": "clientCredentialFlowConnector"
      // Removed for brevity
    },
    {
      "name": "form",
      "path": "/api/form",
      "skip": false,
      "entityId": "{ this integration name }",
      "dependsOn": ["linearConnector"],
      "entityType": "integration"
    }
  ]
  // Removed for brevity
}

When creating a session, default input's could be supplied to the session endpoint in this fashion:

{
  "redirectUrl": "http://example.com/redirect",
  "components": ["input", "form"],
  "input": {
    "clientCredentialFlowConnector": {
      "client_id": "AAAA",
      "client_secret": "BBBB"
    },
    "form": {
      "option": "A Sensible Default"
    }
  }
}

Note that the above example also demonstrates the components parameter in the session object, which allows for a session to specify an explicit sequence or smaller sample of the available components on an integration.

A form in an integration would then be able to access that value by looking in the input section of the session object:

router.get('/api/form', async (ctx) => {
  // Retrieve the current session
  const session = superagent.get(`${ctx.state.params.baseUrl}/session/${ctx.query.session}`)
    .set('Authorization', `Bearer ${ctx.state.params.functionAccessToken}`);
  
  const [form, contentType] = integration.response.createJsonForm({
    ...
    state: {
      option: session.body.input.option,
    },
        ...
  });
});

Each component gets a unique session, with the input for that component isolated from all of the other components.

Using data from previous components

If a form or other element needs access to parameters specified in a previous step - for example, to query for an endpoint and then use that endpoint in a subsequent authorization step - the component in the integration configuration can specify a dependsOn parameter:

{
  "components": [
    {
      "name": "conn",
      "entityType": "connector",
      "entityId": "my-connector",
      "provider": "@fusebit-int/oauth-provider"
    },
    {
      "name": "form",
      "entityType": "integration",
      "entityId": "my-integration",
      "dependsOn": [
        "conn"
      ],
      "path": "/api/my-form"
    }
  ]
}

This parameter allows the form component, during the execution of the /api/my-form endpoint, to have access to the session id of the related session:

{
  "id": "sid-12345",
  "dependsOn": {
    "conn": {
      "parentEntityType": "connector",
      "parentEntityId": "my-connector",
      "entityId": "sid-9876"
    }
  }
}

The entityId can be used as to retrieve an access token from the connector's /api/session/{entityId}/token endpoint, allowing the form to have access to the customer account. This is often used to populate a list of servers, channels, or other artifacts.


Did this page help you?