LogoLogo
  • Getting Started
    • Pandium Documentation
    • What is Pandium?
      • The Pandium Platform
      • What Companies Use Pandium For
      • Platform Structure
      • Users of Pandium
    • Anatomy of an Integration
      • Run Triggers
      • PANDIUM.yaml
        • Schema
        • UiSchema
        • Dynamic Configurations
        • Dependent Selector Configurations
      • Environment Variables
        • Context: StdOut
        • Logging (StdErr)
    • Key Terminology
    • Pandium Integration Tutorial
      • Pokémon of the Day, Part 1
        • Create App in Slack to get Credentials
        • Create Integration on the Pandium Integration Hub
        • Make a Tenant
        • Write the Integration in Typescript
          • Add the .env
          • Configure the PANDIUM.yaml
          • Check the Customized Connection Settings Page
          • Add the Pokémon client
          • Add the Slack Client
          • Add the pokemonSync flow
          • Run Normal Sync on the Tenant
        • Update the Tenant Schedule
      • Pokémon of the Day, Part 2
        • Update the PANDIUM.yaml
        • Check the Updated Connection Settings Page
        • Add Dynamic Configs
        • Run Init Sync on the Tenant
        • Update the pokemonSync flow
        • Run updated Normal Sync on the Tenant
  • Integration Hub
    • Setting Up Source Control
    • Creating An Integration
      • Getting Started with Creating an Integration
      • Demo Video: Creating an Integration With Pandium
    • Managing Internal Integrations
    • Creating a Tenant In the Integration Hub
    • Managing and Updating Tenants
    • Managing and Updating Releases
    • Managing External Integrations
    • Managing Tenant Connection Settings
    • Creating Users
    • Managing Users
    • Administrator Settings
    • Site Metrics
  • Marketplaces
    • Integration Onboarding Experiences Overview
      • Embedding the In-App Marketplace
      • Embedding the Integration Install Only
      • Embedding Auth-Only Connections
    • Customizing the JWT
    • Marketplace Settings
    • App Installation Options
    • Flags, Tags, and Categories
    • Public Gallery
  • Connectors
    • Connectors 101
      • Active Campaign
      • Afterpay
      • AfterShip
      • Airship
      • Alasco
      • Algolia
      • Amadeus
      • Amazon
      • Ankored
      • Apollo.io
      • AppSignal
      • AskNicely
      • Assembled
      • Attentive
      • AWS
      • Azure Devops
      • Azure Personal Access Token
      • Bandcamp
      • Bazaarvoice
      • BigCommerce
      • Bitbucket
      • Booker
      • Box
      • Braze
      • Brightpearl
      • Campaign Monitor
      • Capabl
      • Chargebee
      • Chargify
      • Chubb
      • Cin7
      • Coach Packet
      • ConnectSports
      • Constant Contact
      • Customer Thermometer
      • Datadog
      • Datev
      • Delighted
      • DHL
      • DHL Unified
      • Domo
      • Dotdigital
      • Drip
      • Dropbox
      • Dynamic Yield
      • Easyship
      • Eloqua
      • Emotive
      • Endear
      • Envision
      • eTip
      • EvaluAgent
      • Exact Online
      • eZCom
      • Fabric
      • Facebook
      • Falcon.io
      • Famer
      • Fedex
      • Field Nation
      • Finch
      • Fivetran
      • Fleetio
      • Flowcode
      • Follow Up Boss
      • Fortnox
      • Foundation Software
      • Fulfil
      • GetResponse
      • GitHub
      • GitLab
      • Gladly
      • Google
      • Google Service Account
      • Gooten
      • Gorgias
      • Greenhouse.io
      • Happy Returns
      • HootSuite
      • Hubspot
      • Image Relay (Basic)
      • Imgur
      • Iterable
      • Jasper
      • JDP
      • Justuno
      • Kentico Kontent
      • Klaus
      • Klaviyo
      • Kombo
      • Kustomer
      • kvCORE
      • LeagueApps
      • Lessonly
      • Lexoffice
      • Linga rOS
      • Linnworks
      • Listrak
      • Loop Returns
      • LoyaltyLion
      • Lucid Travel
      • Lytx
      • MaestroQA
      • Magento (OAuth)
      • Mailchimp
      • Marketo
      • Medallia
      • Microsoft Entra
      • Mintsoft
      • NCSA Athletic Recruiting
      • Netomi
      • Netsuite
      • Nicereply
      • Nylas
      • Omnisend
      • OnPay
      • OnRamp
      • Ontraport
      • Optimizely Data Platform (ODP)
      • Pandium
      • Personio
      • PayCom
      • Perspective
      • Player's Health
      • Playvox
      • Pleo
      • Postscript
      • Promoter
      • Quickbooks Online
      • Qualtrics
      • Recart
      • ReCharge
      • Recurly
      • Returnly
      • ReverseLogix
      • Rydership (formerly Whiplash)
      • Sage Intacct
      • SailThru
      • Salesforce
      • Salesforce Marketing Cloud
      • Salesforce Pardot
      • SendGrid
      • Sendlane
      • SevenRooms
      • SFTP
      • ShipBob
      • ShipHero
      • ShipMonk
      • Shippo
      • Shipstation
      • Shopify
      • Skubana
      • Slack (OAuth2)
      • SmartrMail
      • Smartrr
      • Smartvatten
      • Smile.io
      • Solidus
      • Springbig
      • Square
      • Square (Sandbox)
      • Stamped
      • Stella Connect
      • SugarCRM
      • Swell
      • Talkable
      • TeamGenius
      • Tether
      • Thankful
      • TikTok
      • Trinet-B2B Test
      • Triple Whale
      • TrustPilot
      • Twilio
      • Twitter
      • Unbabel
      • UPS
      • Upscribe
      • USPS
      • Visma e-conomic
      • Visma.net
      • Walmart
      • Wix
      • WorkMax
      • Xero (OAuth)
      • Yardstik
      • Yotpo
      • Zendesk (Support)
      • Zingtree
      • Zonos
  • Partners
    • Inviting Partners
    • Partner Form
    • Integration Form
    • Managing Partners
    • Partner User Guide
  • Reference
    • Pandium API
    • Pandium CLI
    • FAQ
    • Sample Integrations
    • Repository Permissions
      • Bitbucket
      • Azure
      • GitLab
      • GitHub
    • Email Support
Powered by GitBook
On this page
  • Here is an example of a dependent selector configuration:
  • Putting it all together
  • Another example

Was this helpful?

  1. Getting Started
  2. Anatomy of an Integration
  3. PANDIUM.yaml

Dependent Selector Configurations

Last updated 10 months ago

Was this helpful?

The last article explained how to make a dynamic configuration, a setting where the options are fetched from an API or database so they change from tenant to tenant

This article will explain how to make a dependent selector configuration, a setting where the options are fetched from an API or data base and vary based on the user's selection of an earlier config (which can also be dynamic)

Here is an example of a dependent selector configuration:

A tenant settings page that allows the the user select their favorite food.

The options for the favorite food will be fetched from an API or database, so they will be different from tenant to tenant.

There will also be a food type configuration.

  • The user's selection for food type will also affect what options are presented to the user when they are selecting their favorite food.

  • The food type config is also dynamic, so the food type options will vary from tenant to tenant.

Here are what the options could look like to different users:

  • Tenant A: food type options will be Sweet and Savory.

    • If the user chooses food type Sweet then the favorite food options will be: Chocolate, Caramel, Jello

    • If the user chooses food type Savory then the favorite food options will be: Chips, Pizza, Bread

  • Tenant B: food type options will be Fruits and Veggies.

  • If the user chooses food type Fruits then the favorite food options will be: Strawberry, Banana, Kiwi

  • If the user chooses food type Veggies then the favorite food options will be: Carrot, Broccoli, Pepper

A dependent dynamic configuration is implemented by creating a parent selector and a dependent selector:

  1. Create the parent selector: This is the dynamic configuration whose selected value determines the options available for the dependent selector.

    • In this example the parent selector is the food_type.

    • Here is a quick review on how to set up a dynamic configuration:

      1. Like other dynamic configurations, the schema property for this must have a reference to a list of options in the schema's definitions.

        • In this example the food_type schema property must have a reference to food_type_options.

      2. Like other dynamic configurations, the schema needs to have a definitions section with an entry that has the options for the parent selector.

        • In this example the schema definitions must include a list of food_type_options.

      3. Like a normal configuration, the uischema elements section must have an entry for the parent selector.

        • In this example there must be a uischema element with type: control and scope: '#/properties/food_type'

      4. Like other dynamic configurations, the integration's code must have an init sync which fetches the tenant specific options for the parent selector and prints them to the standard out.

        • Its value must be an array of objects, each of which has a const and title property. The const will be the ID of that option, and the title will be the name of the option.

        • In this example the standard out of an init sync must include food_type_options

          • It could look like this: food_type_options = [

            {'const': 1, 'title': 'Fruits'},

            {'const': 2, 'title': 'Veggies'},

            ]

          • It could look like this: food_type_options = [

            {'const': 3, 'title': 'Sweet'},

            {'const': 4, 'title': 'Savory'},

            ]

  2. Create a dependent selector: This is the configuration whose options will depend on the parent selector and the values fetched from a database/API.

    • In this example the dependent selector is favorite_food.

    • Here is how a dependent selector is set up:

      1. Like a normal configuration, the schema property for this must have a type. The type for a dependent selector should be string.

      2. Like other dynamic configurations, the schema needs to have a definitions section with an entry that has the options for the dependent selector. It's type should be object.

        • In this example the schema definitions must include a list of food_options.

      3. Like a normal configuration, the uischema elements section must have an entry for the dependent selector. However a dependent selector's uischema element has some additional requirements:

        1. Its type must be DependentSelectControl.

        2. It must have an options property which includes:

          1. dependentMappingsScope: This must reference the schema definition that has the options for the dependent selector.

            • In this example it will be '#/definitions/food_options'.

          2. parentScope: This must reference the schema property for the parent selector which controls the options that will be present.

            • In this example it will be '#/properties/food_type'

      4. Like other dynamic configurations, the integration's code must have an init sync which fetches the tenant specific options for the dependent selector and prints them to the standard out.

        • It's value must be a map of the option type IDs (from the parent selector) to a list of the options for that type.

        • All the options for every list must be either entirely string or entirely oneOf(an objects with a title and const)

        • In this example each option for every list will be a string.

        • In this example the standard out of an init sync must include food_options

          • It could look like this food_options = {

            1: ['Strawberry', 'Banana', 'Kiwi'],

            2: ['Carrot', 'Broccoli', 'Pepper']

            }

          • It could look like this food_options = {

            3: ['Chocolate', 'Caramel', 'Jello'],

            4: ['Chips', 'Pizza', 'Bread'],

            }

Putting it all together

The configs section of the PANDIUM.yaml would include this:

configs:

  schema:
    definitions:
      food_type_options:
        oneOf:
          - const: Placeholder
            title: Placeholder
      food_options:
        type: object
    properties:
      food_type:
        $ref: "#/definitions/food_type_options"
      favorite_food:
        type: string

  uischema:
    type: VerticalLayout
    elements:
      # Parent
      - label: Food Type
        scope: '#/properties/food_type'
        type: Control
      # Dependent
      - label: Favorite Food
        scope: '#/properties/favorite_food'
        options: 
          dependentMappingScope: '#/definitions/food_options'
          parentScope: '#/properties/food_type'
        type: DependentSelectControl

A TypeScript implementation of the init sync of the integration in could look like this:

if (pandium.context.runMode === 'init') {
    const foods = await foodClient.getMany('food');
    /* foods is a list that could look like this: 
    [
     { type: 'Sweet', name: 'Chocolate' },
     { type: 'Sweet', name: 'Caramel' },
     { type: 'Sweet', name: 'Jello' },
     { type: 'Savory', name: 'Chips' },
     { type: 'Savory', name: 'Pizza' },
     { type: 'Savory', name: 'Bread' },
    ]
    */
    
    // Loop over the foods to identify the food types.
    const uniqueFoodTypes = foods.reduce((foodTypesList, currentFood) => {
      if(!foodTypesList.includes(currentFood.type)){
        foodTypesList.push(currentFood.type)
      }
      return foodTypesList
    }, [] )
    
    // Map each food type to the oneOf format
    const foodTypes = uniqueFoodTypes.map((type, index) => {
      return {
        const: index + 1,
        title: type
      }
    })
    /* foodTypes could look like this:
    [
     { const: '1', title: 'Sweet' },
     { const: '2', title: 'Savory' },
    ]
    */
    
    // Create a map from food type to ID, which can be used to organize foods into lists by type.
    const foodTypeToIdMap = foodTypes.reduce((typeToIdMap, foodType) => {
      typeToIdMap[foodType.title] = foodType.const
      return typeToIdMap
    }, {})
    /* foodTypeToIdMap could look like this:
    {
      'Sweet': '1',
      'Savory': '2'
    }
    */
    
    // Organize the foods into lists of their types
    foodListByTypeId = foods.reduce((foodListByType,currentFood) => {
      const typeId = foodTypeToIdMap[currentFood.type]
      if(!foodListByType[typeId]) {
        foodListByType[typeId] = [] 
      }
      foodListByType[typeId].push(currentFood.name)
      return foodListByType
    }, {} )
    /* foodListByTypeId could look like this:
    [
      '1': ['Chocolate', 'Caramel', 'Jello' ],
      '2': ['Chips', 'Pizza', 'Bread']
    ]
    */

    const stdout = {
      food_type_options: foodTypes,
      food_options: foodListByTypeId
    };
    // Print json string to stdout and end the script.
    console.log(JSON.stringify(stdout));
    return
}

Another example

This example has a static parent selector and the options for the dependent selector will be oneOf rather than string.

The configs section of the PANDIUM.yaml would include this:

configs:
  schema:
    type: object
    definitions:
      pokemon_types:
        oneOf:
          - const: Fire
            title: Fire
          - const: Water
            title: Water
          - const: Rock
            title: Rock
          - const: Flying
            title: Flying
      pokemon_options:
        type: object
    properties:
      pokemon_type:
        $ref: "#/definitions/pokemon_types"
        type: string
      pokemon_1:
        type: string
      pokemon_2:
        type: string

  uischema:
    type: VerticalLayout
    elements:
      - label: Pokemon Type
        scope: "#/properties/pokemon_type"
        type: Control
      - type: Section
        label: Pokemon Selection
        hintText: Choose your Pokemon
        elements:
          - label: First Pokemon
            scope: "#/properties/pokemon_1"
            options:
              dependentMappingScope: "#/definitions/pokemon_options"
              parentScope: "#/properties/pokemon_type"
            type: DependentSelectControl
          - label: Second Pokemon
            scope: "#/properties/pokemon_2"
            options:
              dependentMappingScope: "#/definitions/pokemon_options"
              parentScope: "#/properties/pokemon_type"
            type: DependentSelectControl

A TypeScript implementation of the init sync of this integration in could look like this:

if (pandium.context.runMode === 'init') {
    const pokemon = await pokeClient.getMany('pokemon');
    /* pokemon is a list that could look like this: 
    [
     { type: 'Fire', name: 'Charmander', id: 1 },
     { type: 'Fire', name: 'Vulpix', id: 2  },
     { type: 'Water', name: 'Squirtle', id: 3  },
     { type: 'Water', name: 'Poliwag', id: 4  },
     { type: 'Rock', name: 'Geodude', id: 5  },
     { type: 'Rock', name: 'Onix', id: 6  },
     { type: 'Flying', name: 'Butterfree', id: 7 },
     { type: 'Flying', name: 'Pidgeot', id: 8  },
    ]
    */

    // The parent control, pokemon_type, has a static list of options: pokemon_types. 
    // so the unique options for pokemon_types do not need to be identified.
    const pokemonOptions = {
      Fire:[],
      Water:[],
      Rock: [],
      Flying: [],
    }

    // Add each pokemon to the list for the appropriate type.
    for (const currentPokemon of pokemon) {
      pokemonOptions[currentPokemon.type].push({
        title: currentPokemon.name,
        const: currentPokemon.id
      })
    }
 
    /* pokemonOptions could now look like this:
    {
      Fire: [ 
          { title: 'Charmander', const: '1'},  
          { title: 'Vulpix', const: '2' },
      ],
      Water: [ 
          { title: 'Squirtle', const: '3'},  
          { title: 'Poliwag', const: '4' },
      ],
      Rock: [ 
          { title: 'Geodude', const: '5'},  
          { title: 'Onix', const: '6' },
      ],
      Flying: [ 
          { title: 'Butterfree', const: '7'},  
          { title: 'Pidgeot', const: '8' },
      ]
    }
    */
    

    const stdout = {
      pokemon_options: pokemonOptions
    };
    // Print json string to stdout and end the script.
    console.log(JSON.stringify(stdout));
    return
}
The only food type option for Tenant A are "Sweet" and "Savory"
When the user selects the "Sweet: food type, the options for favorite food are only sweet foods.
When the user selects the "Savory" food type, the options for favorite food are only savory foods.
The only food type option for Tenant B are "Fruits" and "Veggies"
When the user selects the "Fruits" food type, the options for favorite food are only fruits.
When the user selects the "Veggies" food type, the options for favorite food are only vegetables.