Embedding Marketplace
The video above walks through the steps to embed Prismatic in your app, starting from a blank React app.
Embed the integration marketplace
You can embed the Integration Marketplace into your application with just a few lines of code, giving your customers a native integration deployment experience. This article details how to embed Prismatic's integration marketplace into your application.
For information on how to install the embedded SDK and authenticate customer users, see Installing Embedded SDK
Once a user has been authenticated, you can display the integration marketplace to them using the prismatic.showMarketplace()
function.
The showMarketplace()
function takes three optional arguments:
usePopover
is a boolean that determines if the marketplace should display as a popover.
By default, the screen is injected as an iframe into an existing DOM element.selector
identifies which element in the DOM to inject the iframe into whenusePopover
is false.theme
can override custom theming default behavior, and can be set to"LIGHT"
or"DARK"
mode.
iframe injection method
When showPopover
is set to false
, an iframe HTML element is injected into an element that you select with the selector
argument.
The iframe contains the integration marketplace.
For example, if you have a section of your app that reads:
<div id="integration-marketplace-placeholder" style="height:100%">
Loading...
</div>
You can inject the integration marketplace iframe into that div, replacing the "Loading..." text, by invoking:
prismatic.showMarketplace({
selector: `#integration-marketplace-placeholder`,
usePopover: false,
});
The result is that the integration marketplace appears to be a portion of the rest of your application:
Note: at this point marketplace has not been themed to match your app. To match your app branding, see Theming Embedded Marketplace.
For an example of the iframe injection method, see GitHub.
Popover method
If you elect to set usePopover
to true
, a popover will be opened containing the integration marketplace:
prismatic.showMarketplace({
usePopover: true,
});
Filtering integrations
You can choose to show a subset of your integrations by applying filters
to your showMarketplace()
invocation.
Simple integration filters
You can filter your integrations by a single category or label (or both).
If you filter by both, integrations will appear only if they match both category
and label
.
For example, if you would like to show only integrations with the category "ERP" and the label "enterprise" in an embedded marketplace, you can issue this invocation:
prismatic.showMarketplace({
selector: `#my-div-id`,
usePopover: false,
filters: {
marketplace: {
category: "ERP",
label: "enterprise",
},
},
});
Advanced integration filters
You can use a filterQuery
to perform more advanced logic when filtering integrations.
You can query for integrations given the integration's name
, labels
or category
.
The following operators are at your disposal:
-
TermOperator.equal
checks if the first term is equal to the second term and can be used forname
andcategory
. For example, to show only the integration named "Dropbox" enter:prismatic.showMarketplace({
filters: {
marketplace: {
filterQuery: [TermOperator.equal, "name", "Dropbox"],
},
},
}); -
TermOperator.notEqual
checks if the first term is not equal to the second term and can be used forname
andcategory
. For example, to show all integrations except those with the category "ERP" use:prismatic.showMarketplace({
filters: {
marketplace: {
filterQuery: [TermOperator.notEqual, "category", "ERP"],
},
},
}); -
TermOperator.in
is used to check if the integration'slabels
include a specific label. For example, if you want to show integrations with a "paid" label, use:prismatic.showMarketplace({
filters: {
marketplace: {
filterQuery: [TermOperator.in, "labels", "paid"],
},
},
}); -
TermOperator.notIn
will show integrations that do not have certain labels. -
TermOperator.startsWith
checks if aname
orcategory
begins with a certain string. For example, to find all integrations that start with "Algolia" use:prismatic.showMarketplace({
filters: {
marketplace: {
filterQuery: [TermOperator.startsWith, "name", "Algolia"],
},
},
});That will match integrations with names like
Algolia - Dropbox
orAlgolia - SFTP
. -
TermOperator.doesNotStartWith
returns integrations with name or category that does not start with a certain string. -
TermOperator.endsWith
returns integrations with name or category that ends with a certain string. -
TermOperator.doesNotEndWith
returns integrations with name or category that does not end with a certain string.
Several conditionals can be applied in unison with and
and or
logic.
For example, if you would like to show all integrations that have a category "ERP" and label "paid", and would also like the Dropbox and Slack integrations, a filter could look like:
import prismatic, {
BooleanOperator,
TermOperator,
} from "@prismatic-io/embedded";
prismatic.showMarketplace({
filters: {
marketplace: {
filterQuery: [
BooleanOperator.or,
[
BooleanOperator.and,
[TermOperator.equal, "category", "ERP"],
[TermOperator.in, "labels", "paid"],
],
[TermOperator.equal, "name", "Dropbox"],
[TermOperator.equal, "name", "Slack"],
],
},
},
});
Configure a specific integration
If you'd like to use your own UI elements (a button, div, hyperlink, etc) for selecting an integration to deploy, you can.
Invoke the prismatic.configureInstance()
function from your UI element to display a configuration screen for a specific integration by passing in the name of the integration as integrationName
.
You can pass in other display information (like usePopover
and selector
) as you would with the iframe injection and popover methods described above.
You can also pass in an optional skipRedirectOnRemove
boolean, which defaults to false
.
This determines if your user should be redirected to the list of integrations upon removing an integration.
If you are embedding marketplace, you probably want to keep this value false
.
If you're wrapping our API and handling displaying of integrations yourself, you might want this to be true
.
For example, if you would like your "Salesforce" integration to appear when someone clicks one of your buttons, you could invoke configureInstance
like this:
const deploySalesforce = () => {
prismatic.configureInstance({
integrationName: "Salesforce",
skipRedirectOnRemove: false,
usePopover: true,
});
};
<Button onClick={deploySalesforce}>Configure Salesforce Integration</Button>;
For an example of rendering a custom UI for marketplace, and opening the configuration screen for a specific integration with prismatic.configureInstance()
, see GitHub.
Listening to Marketplace events
The embedded marketplace emits custom JavaScript events when certain things happen:
- An
INSTANCE_CREATED
event is emitted the first time an integration's configuration screen is opened. - An
INSTANCE_CONFIGURATION_OPENED
event is emitted when an instance configuration screen is opened. - An
INSTANCE_CONFIGURATION_LOADED
event is emitted when an instance configuration screen has loaded. This is a good event to hook into if you want to programmatically set config variables. - An
INSTANCE_CONFIGURATION_PAGE_LOADED
event is emitted when an instance configuration screen has moved from one page to another. - An
INSTANCE_CONFIGURATION_CLOSED
event is emitted when an instance configuration screen is closed (regardless of if configuration was completed or not). - An
INSTANCE_DEPLOYED
event is emitted when an instance has been configured and enabled for a customer. - An
INSTANCE_DELETED
event is emitted when an integration has been deactivated (the instance has been deleted).
You can subscribe to these events so your app can take additional steps when an instance has been created, configured, deleted, etc.
All events return data structured similar to this (except for INSTANCE_CONFIGURATION_LOADED
, which returns additional data):
{
"event": "INSTANCE_CONFIGURATION_OPENED",
"data": {
"customerId": "Q3VzdG9tZXI6OThiMjU3MDUtZmMzNC00NWYwLTk0ZDItODA0ZjFkYzEyYTZk",
"customerName": "Smith Rockets",
"instanceId": "SW5zdGFuY2U6YzJlYTliZjEtY2Y3MS00NTg1LTk2MjMtYjZhNDAxYjQyNWRm",
"instanceName": "Salesforce",
"integrationName": "Salesforce",
"integrationVersionNumber": 18,
"readOnly": false
}
}
In this example we log out the name of the customer and what integration they opened:
import { PrismaticMessageEvent } from "@prismatic-io/embedded";
window.addEventListener("message", (event) => {
// Check if the message event is of type INSTANCE_CONFIGURATION_OPENED
if (
event.data.event === PrismaticMessageEvent.INSTANCE_CONFIGURATION_OPENED
) {
// Grab some data
const { customerName, integrationName } = event.data.data;
// Log out the customer and integration name. You can replace this with whatever data or helper functions you want
console.log(
`${customerName} opened the configuration page for ${integrationName}`,
);
}
});
Similar event listeners can be added for when an instance configuration page is closed or loads, or an instance is initially created or deleted.
User-level configuration Marketplace events
If user-level configuration is enabled for an integration, you will see the following marketplace events:
- A
USER_CONFIGURATION_OPENED
event is emitted when a ULC configuration screen is opened. - A
USER_CONFIGURATION_LOADED
event is emitted when a ULC configuration screen has loaded. This is a good event to hook into if you want to programmatically set config variables. - A
USER_CONFIGURATION_PAGE_LOADED
event is emitted when aa ULC configuration screen has moved from one page to another. - A
USER_CONFIGURATION_CLOSED
event is emitted when a ULC configuration screen is closed (regardless of if configuration was completed or not). - A
USER_CONFIGURATION_DEPLOYED
event is emitted when a ULC configuration is completed. - A
USER_CONFIGURATION_DELETED
event is emitted when a ULC configuration is deleted.
For all of these events, the event's data
property will look like this:
{
"event": "USER_CONFIGURATION_LOADED",
"data": {
"customerId": "Q3VzdG9tZXI6ZjYxMzY4MzItMzYxYi00NmI3LTlmMmItN2ZkNTc3YzY1YTg1",
"customerName": "Smith Real Estate",
"instanceId": "SW5zdGFuY2U6N2I1MjFiNzItYzllNS00YjkwLWIzZGEtZTY4OTY5OWU2ZjBl",
"instanceName": "Dropbox",
"integrationName": "Dropbox",
"integrationVersionNumber": 9,
"userConfigId": "VXNlckxldmVsQ29uZmlnOjI0NDU5MjA2LWQxNGUtNDRhMy04NWE4LTJjMzgwMzg2Y2NmZg==",
"userEmail": "2E52B7CB-071B-4EA2-8E9D-F64910EBDBB1",
"userId": "VXNlcjpmYjc4YzE1OS1kOWMwLTQxMDctYjIyNC0zYmNhMDFlOTQ5NzY=",
"userLevelConfigVariables": {
"Dropbox Connection": {
"status": "ACTIVE"
}
},
"readOnly": false,
"userName": "Phil Embedmonson"
}
}
Dynamically setting config variables in marketplace
You can programmatically set values for your customers' config variables by leveraging marketplace events. This is handy if you know some value (an API key, endpoint, etc), and would like to set a config variable on your customer's behalf (so they don't need to look up the values themselves).
When your app receives an INSTANCE_CONFIGURATION_LOADED
event message (or USER_CONFIGURATION_LOADED
for ULC), the message's payload contains the properties listed above, plus current config variable values.
You can inspect that event's data, and conditionally set values for some config variables using the prismatic.setConfigVars()
function.
Let's look at the event message, and how to respond to it with some config variable values:
- Event Payload
- NodeJS Example
- React Example
Here's what the INSTANCE_CONFIGURATION_LOADED
payload looks like.
The event message's .data.configVars
property contains all currently set config variables and their values:
{
"event": "INSTANCE_CONFIGURATION_LOADED",
"data": {
"instanceId": "SW5zdGFuY2U6ZTE4NTNkYWItZjJhMi00OGIyLTk1ZWItODRjYzQ3YzRiMzc4",
"instanceName": "Test Embedded config vars",
"integrationName": "Test Embedded config vars",
"integrationVersionNumber": 1,
"customerId": "Q3VzdG9tZXI6OThiMjU3MDUtZmMzNC00NWYwLTk0ZDItODA0ZjFkYzEyYTZk",
"customerName": "Smith Rockets",
"readOnly": false,
"configVars": {
"Acme Connection": {
"inputs": {
"username": { "value": "" },
"password": { "value": "" }
},
"status": "PENDING"
},
"My Key/Value List": {
"value": [],
"collectionType": "keyvaluelist",
"codeLanguage": null,
"dataType": "string",
"pickList": null,
"scheduleType": null,
"timeZone": null
},
"My String": {
"value": "",
"collectionType": null,
"codeLanguage": null,
"dataType": "string",
"pickList": null,
"scheduleType": null,
"timeZone": null
},
"My List": {
"value": [],
"collectionType": "valuelist",
"codeLanguage": null,
"dataType": "string",
"pickList": null,
"scheduleType": null,
"timeZone": null
},
"My String With Default": {
"value": "Some Default",
"collectionType": null,
"codeLanguage": null,
"dataType": "string",
"pickList": null,
"scheduleType": null,
"timeZone": null
},
"My List With Default": {
"value": ["Foo1", "Foo2"],
"collectionType": "valuelist",
"codeLanguage": null,
"dataType": "string",
"pickList": null,
"scheduleType": null,
"timeZone": null
},
"My Picklist": {
"value": "",
"collectionType": null,
"codeLanguage": null,
"dataType": "picklist",
"pickList": ["Foo", "Bar", "Baz"],
"scheduleType": null,
"timeZone": null
},
"My Boolean": {
"value": false,
"collectionType": null,
"codeLanguage": null,
"dataType": "boolean",
"pickList": null,
"scheduleType": null,
"timeZone": null
}
}
}
}
In this example we add a message event listener to listen for the PrismaticMessageEvent.INSTANCE_CONFIGURATION_LOADED
event.
If that event contains a config variable named Acme Connection
, a value for that config variable is assigned.
Additionally, if this is the Salesforce
integration, values for some additional example config variables are assigned.
import prismatic, {
getMessageIframe,
PrismaticMessageEvent,
} from "@prismatic-io/embedded";
const myListener = (message: MessageEvent) => {
// Destructure the event and data information from the message
const { event, data } = message.data;
// Check if this is an "INSTANCE_CONFIGURATION_LOADED" event and
// that the config screen was not opened in read-only mode
if (
event === PrismaticMessageEvent.INSTANCE_CONFIGURATION_LOADED &&
!data.readOnly
) {
// Destructure the integration name and config variables
const { integrationName, configVars } = data;
// Determine which iframe sent the message, so we can send one back
const iframe = getMessageIframe(message);
// Check if the instance has an "Acme Connection" config variable:
if (Object.keys(configVars).includes("Acme Connection")) {
// Check if they're empty, or have already been filled in:
if (
configVars["Acme Connection"].inputs.username === "" &&
configVars["Acme Connection"].inputs.password === "" &&
configVars["Acme Connection"].status === "PENDING"
) {
// Set username and password fields for "Acme Connection"
prismatic.setConfigVars({
iframe, // Send back to the iframe that sent us a message
configVars: {
"Acme Connection": {
inputs: {
username: { value: "My-User" },
password: { value: "supersecretpassword" },
},
},
},
});
}
}
// Set a couple of config variables if this is the Salesforce integration
if (integrationName === "Salesforce") {
prismatic.setConfigVars({
iframe,
configVars: {
"String Config Var": { value: "Updated Value" }, // Update a simple string
"String Valuelist": { value: ["Value 1", "Value 2"] }, // Update a value list of strings
// Update a key-value list
"String Keyvaluelist": {
value: [
{ key: "A Key", value: "A Value" },
{ key: "B Key", value: "B Value" },
],
},
},
});
}
}
};
window.addEventListener("message", myListener);
Here's how to wrap a similar JavaScript example in a React useEffect hook:
import React, { useEffect } from "react";
import prismatic, {
getMessageIframe,
PrismaticMessageEvent,
} from "@prismatic-io/embedded";
import Loading from "../components/Loading";
const id = "embedded-marketplace-container";
const EmbeddedMarketplace = () => {
useEffect(() => {
prismatic.showMarketplace({
selector: `#${id}`,
usePopover: false,
theme: "LIGHT",
});
}, []);
useEffect(() => {
const listener = (message: MessageEvent) => {
const { event, data } = message.data;
if (
event === PrismaticMessageEvent.INSTANCE_CONFIGURATION_LOADED &&
!data.readOnly
) {
const iframe = getMessageIframe(message);
if (data.integrationName === "Test Embedded config vars") {
prismatic.setConfigVars({
iframe,
configVars: {
"Amazon S3 Connection": {
inputs: {
accessKeyId: { value: "supersecretpassword" },
secretAccessKey: { value: "My-User" },
},
},
},
});
}
}
};
window.addEventListener("message", listener);
return () => {
window.removeEventListener("message", listener);
};
}, []);
return (
<div id={id} style={{ height: "100%" }}>
<Loading />
</div>
);
};
export default EmbeddedMarketplace;
The getMessageIframe helper function
Your app needs to know which iframe to send a setConfigVars
message to.
Your app might have multiple iframes, and potentially multiple instances of the embedded marketplace on a single page.
The getMessageIframe
function helps to identify which iframe generated the INSTANCE_CONFIGURATION_LOADED
event.
If you would like to send a setConfigVars
message to a specific iframe, you can instead pass a selector
property to the setConfigVars
function (like you did for prismatic.showMarketplace()
):
prismatic.setConfigVars({
selector: "#my-marketplace-container",
configVars: {
"My Config Var Name": { value: "My config var value" },
},
});
Hiding UI elements in marketplace
You can optionally hide the Back to Marketplace link, some tabs from the instance screen, and elements of the instance configuration wizard. This is useful if you want to prevent your customers from running tests from the Test tab or reconfiguring alert monitors for themselves.
To disable certain UI elements, add a screenConfiguration
block to your prismatic.showMarketplace()
or prismatic.configureInstance()
invocations:
prismatic.showMarketplace({
selector: `#${id}`,
usePopover: false,
theme: "LIGHT",
screenConfiguration: {
instance: {
hideBackToMarketplace: true,
hideTabs: ["Monitors", "Test"],
},
configurationWizard: {
hideSidebar: true,
isInModal: true,
triggerDetailsConfiguration: "hidden",
},
marketplace: {
hideSearch: true,
hideActiveIntegrationsFilter: true,
},
},
});
instance.hideBackToMarketplace
determines whether or not to hide the Back to Marketplace link in the instance configuration screen and defaults tofalse
.instance.hideTabs
determines which tabs to hide from the instance configuration screen. You can choose from the Test, Executions, Monitors or Logs tabs. No tabs are hidden by default.configurationWizard.hideSidebar
hides the left-hand sidebar from the configuration wizard (the config wizard page titles and numbers).configurationWizard.isInModal
affects if the config wizard should appear over the current page in a modal. When set totrue
, it's assumed that your embedded marketplace is already in a modal, (and since a modal-in-a-modal isn't the best look), opens the config wizard to fill its containing<div>
.configurationWizard.triggerDetailsConfiguration
determines if and how trigger details are shown in the config wizard. Your options arehidden
to hide trigger details completely,default
to include the element, but collapse them, ordefault-open
to include the element and have it expanded. Endpoint details may also be hidden if they are not configured to be Secured by Customer (see Endpoint Configuration).marketplace.hideSearch
will hide the search bar in the marketplace.marketplace.hideActiveIntegrationsFilter
will hide top-right filter for active integrations in the marketplace.
If you would like to apply your preferences globally (in the case that you have multiple invocations showMarketplace()
and configureInstance()
), you can configure settings when you run prismatic.init()
:
prismatic.init({
screenConfiguration: {
instance: {
hideBackToMarketplace: true,
hideTabs: ["Test", "Monitors"],
},
},
});
Screen configuration settings in prismatic.init()
can be overridden within a specific showMarketplace()
or configureInstance()
invocation.
hideTabs
properties are not merged if yo do this - the screenConfiguration
settings in showMarketplace
/ configureInstance
completely override the default settings in init
.
Integration configuration detail screen
The embedded SDK configuration allows you to fine-tune the user experience for accessing the integration configuration details screen. It includes the ability to prevent a marketplace user from accessing the detail screen. This is useful if you do not want your customer to access the Test, Executions, Monitors or Logs functionality.
allow-details
Will redirect the marketplace user back to the marketplace listing after the user finishes the configuration wizard. When a user clicks on an integration from the marketplace listing, they will see a summary of the integration including the integration status. The user can manage the instances from the overflow menu at the bottom. "View Details" will open the full details integration screen. You can hide some UI elements of this page. This is the default behavior if no configuration is provided, starting in SDK version 2.0.0.disallow-details
Will provide the same experience as theallow-details
option described above. However, users will not have access to the details option from the overflow menu.always-show-details
Will redirect the marketplace user to the full integration details screen after the integration has been configured or when an instance is selected from the marketplace listing.
prismatic.showMarketplace({
selector: `#my-div`,
screenConfiguration: {
marketplace: {
configuration: "allow-details",
},
},
});
Showing all instances in marketplace
Sometimes you will have an integration that you would like to deploy to a specific customer via Marketplace, but you don't want to make it generally available to all customers.
To accomplish that, deploy an unconfigured instance of the integration to your customer as an organization user.
Then, set the screenConfiguration.marketplace.includeActiveIntegrations
marketplace option to true
to show instances that are enabled for a customer (even if the integration is not in marketplace):
prismatic.showMarketplace({
selector: `#my-div`,
screenConfiguration: {
marketplace: {
includeActiveIntegrations: true,
},
},
});
includeActiveIntegrations
by defaultNote that if you have filters enabled, the includeActiveIntegrations
option will not override the filters.
By default, active integrations that are deployed to a customer must match the filters you specify in order to be shown.
If you would like to show all active integrations, regardless of filters, in addition to the marketplace integrations that match your filters, set the includeActiveIntegrations
option to true
, and set an additional property strictMatchFilterQuery
to false
.
Multiple instances of one integration in Marketplace
By default customer users can enable a single instance of an integration through embedded marketplace. There are situations, though, where you want customers to be able to deploy multiple instances of an integration. For example, your customers may have multiple Shopify stores, and require a separate instance of your Shopify integration for each store.
Enabling customers to deploy multiple instances of an integration is configured on a per-integration basis. From the marketplace configuration screen or the marketplace drawer in the integration designer, enable the Allow Multiple Instances toggle.
Embedded customer users who select your integration will be able to view existing instances of the integration that are enabled, and will have the option to add additional instances of the integration.
Customers will see a similar view if you have enabled multiple instances of an integration for them.
Custom marketplace UI
The embedded marketplace listview screen is presented as an iframe. If you would prefer to present marketplace integrations in your own custom UI elements, you can. This is helpful if you build some integrations in Prismatic, and others outside of Prismatic - you can display both together using the same UI elements.
To fetch a list of marketplace integrations, use your customer's signed JWT to query the Prismatic API for marketplaceIntegrations
.
An example of a query you can issue is available in our example embedded app.
This query will return an array of integrations that have been added to marketplace.
You can map that array to whatever custom UI elements you'd like.