Skip to main content

Config Wizard Data Sources

Writing custom data sources

A Data Source fetches data from a third-party API that will be used to dynamically generate a config variable. When your customer deploys an instance, they use a connection to authenticate with a third-party API.

A data source can generate a variety of types of data including a string, date, picklist (which is a string[]), complex objectSelection objects, and more.

Here's a simple data source that fetches an array of customers, each with a name and id. It maps them to a label/key object so the customer's names show in a picklist, and the customer's id is :

Fetch a string from an external API
import { dataSource, Element } from "@prismatic-io/spectral";

interface Customer {
name: string;
id: string;
}

const companyName = dataSource({
display: {
label: "Fetch Customers",
description: "Fetch an array of customers' names",
},
inputs: {
connection: input({
label: "Connection",
type: "connection",
required: true,
}),
},
perform: async (context, params) => {
const client = createAcmeClient(params.connection);
const response = await client.get<{ customers: Customer[] }>("/customers");
const customers = response.data.customers?.map<Element>((customer) => ({
label: customer.name,
key: customer.id,
}));
return { result: customers };
},
dataSourceType: "picklist",
examplePayload: {
result: [
{ label: "Smith Rocket Company", key: "abc-123" },
{ label: "Mars Rocket Corp", key: "def-456" },
],
},
});

In this example, we fetch several items from an API, including metadata about each item, so that a user can select one or more of the items and get that metadata of each:

Fetch a string from an external API
const companyName = dataSource({
display: {
label: "Fetch Items",
description: "Fetch all available items",
},
inputs: {
connection: input({
label: "Connection",
type: "connection",
required: true,
}),
},
perform: async (context, params) => {
const client = createAcmeClient(params.connection);
const response = await client.get("/items");
const objects: ObjectSelection = response.data.items.map((item) => ({
object: { key: object.id, label: object.name },
fields: [
{ key: object.quantity, label: "Quantity" },
{ key: object.sku, label: "SKU" },
],
}));
return { result: response.data.name };
},
dataSourceType: "objectSelection",
examplePayload: {
result: [
{
object: { key: "abc-123", label: "widgets" },
fields: [
{ key: "5", label: "Quantity" },
{ key: "0000000000", label: "SKU" },
],
},
],
},
});

An example of a data source that generates a picklist is available in the Slack component.

JSON Forms Data Sources

JSON Forms is a form-generating framework that allows you to create forms through JSON schema that you generate. A JSON Form can contain any number of string, boolean, number, date, time, datetime or enum (dropdown menu) inputs, and you have some control over how the input elements are rendered (in tabs, grouped, vertical or horizontal layout, etc).

Full documentation on JSON Forms is available on their documentation page, including several examples. Prismatic offers a JSON Forms playground where you can create new forms and see how they would be rendered in Prismatic.

A JSON Form config data source must return two properties (and one optional property):

  • schema defines the types of inputs your form contains (its properties), and some optional validators, like which properties are required.
  • uiSchema defines how those inputs should be rendered, like whether the inputs should be vertically or horizontally aligned.
  • data (optional) allows you to specify some default values for your form inputs.

This simple example's schema declares two inputs - name (a string) and age (an integer between 0 and 150), and the uiSchema labels the first input "First Name" and places the input fields horizontally next to one another.

A simple JSON Form
return {
result: {
schema: {
type: "object",
properties: {
name: {
type: "string",
},
age: {
type: "integer",
minimum: 0,
maximum: 150,
},
},
},
uiSchema: {
type: "HorizontalLayout",
elements: [
{
type: "Control",
scope: "#/properties/name",
},
{
type: "Control",
scope: "#/properties/age",
},
],
},
data: { name: "Bob", age: 20 },
},
};

The resulting JSON Form looks like this:

Simple JSON form config variable

Data Mapping with JSON Forms

A common use-case for JSON Forms is presenting a data-mapping UI to a user. For the sake of illustration, we'll pull down users and to-do tasks from JSON Placeholder, and give our users the ability to map any number of users to tasks.

In order to provide any number of mappings of user-to-task, our JSON schema will need to contain an array of object. Each object will contain a user property and a task property. The user and task property will each have a oneOf property, which represents a dropdown menu.

For the sake of illustration, we also provide a data value containing some defaults that our UI should show. This property can be omitted.

Data mapping with JSON forms
import axios from "axios";
import { dataSource, util } from "@prismatic-io/spectral";

interface User {
id: number;
name: string;
email: string;
}

interface Task {
id: number;
title: string;
}

const userTaskExample = dataSource({
dataSourceType: "jsonForm",
display: {
label: "User/Task Mapper",
description: "Map users to to-do tasks",
},
inputs: {},
perform: async (context, params) => {
const { data: users } = await axios.get<User[]>(
"https://jsonplaceholder.typicode.com/users",
);
const { data: tasks } = await axios.get<Task[]>(
"https://jsonplaceholder.typicode.com/todos?_limit=10",
);

const schema = {
type: "object",
properties: {
mymappings: {
// Arrays allow users to make one or more mappings
type: "array",
items: {
// Each object in the array should contain a user and task
type: "object",
properties: {
user: {
type: "string",
// Have users select "one of" a dropdown of items
oneOf: users.map((user) => ({
// JSON Forms expects a string value:
const: util.types.toString(user.id),
title: user.name,
})),
},
task: {
type: "string",
oneOf: tasks.map((task) => ({
const: util.types.toString(task.id),
title: task.title,
})),
},
},
},
},
},
};

const uiSchema = {
type: "VerticalLayout",
elements: [
{
type: "Control",
scope: "#/properties/mymappings",
label: "User <> Task Mapper",
},
],
};

// Provide a default value, mapping of the first user to the first task
const defaultValues = {
mymappings: [
{
user: util.types.toString(users[0].id),
task: util.types.toString(tasks[0].id),
},
],
};

return {
result: { schema, uiSchema, data: defaultValues },
};
},
});

The resulting JSON Form looks like this:

Data mapping JSON form result