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 :
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:
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 (itsproperties
), 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.
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:
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.
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: