Building dashboard delivery in Embeddable

Example implementation of dashboard delivery:

  1. We built a UI page thats called reports - this allows users to select an “Embeddable” to display. On that page we built a Nav with a button called “Export”. Clicking this button will open a dialog that supports the ability to add in some emails / cadence time that is recored in our DB. e.g john@gmail.com/Twice a week/3pm.

  2. We wrote a client in the backend that will query Embeddable API’s using /web-component/v1/query and builds up a payload - see below a short api-documentation that shows how we achieved this for one component - but the logic is repeatable for what ever components you want to choose. To query the api you can just follow the documentation to get your security token and then build your CSV from the attached .md

  3. We then pop that payload into a Pub/Sub - Scheduler - Cron job ( How ever you want to achieve it ) and then hit what ever SMTP service provider you want to use to send the email

Web Component Query API Documentation

Step 1: Retrieve Widget Information

API Endpoint

  • URL: https://app-api.eu.embeddable.com/web-component/v1/embeddable
  • Method: GET

curl Command

curl 'https://app-api.eu.embeddable.com/web-component/v1/embeddable' \
  -H 'accept: */*' \
  -H 'accept-language: en-GB,en-US;q=0.9,en;q=0.8' \
  -H 'authorization: Bearer [YOUR_ACCESS_TOKEN]' \
  -H 'origin: [YOUR_ORIGIN]' \
  -H 'referer: [YOUR_REFERER]' \
  -H 'sec-ch-ua: "Not;A=Brand";v="24", "Chromium";v="128"' \
  -H 'sec-ch-ua-mobile: ?0' \
  -H 'sec-ch-ua-platform: "macOS"' \
  -H 'sec-fetch-dest: empty' \
  -H 'sec-fetch-mode: cors' \
  -H 'sec-fetch-site: cross-site' \
  -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36'

Response Structure

{
  "embeddable": {
    "widgets": [
      {
        "id": "component_id",
        "inputConfiguration": {
          // ... other properties ...
        }
      }
    ]
  }
}

Step 2: Extract Required Data

From the response, extract the following information for each widget:

  1. componentId: widgets[i].id
  2. limit: widgets[i].inputConfiguration.limit.value
  3. datasetId: widgets[i].inputConfiguration.ds.value.datasetId
  4. inputName: widgets[i].inputConfiguration.columns.inputMeta.config.dataset
  5. dimensions: Extract all name properties from widgets[i].inputConfiguration.columns.value
  6. order:
    • Start with widgets[i].inputConfiguration.defaultSort.value.name as the first item
    • Add remaining dimensions in the order they appear in widgets[i].inputConfiguration.columns.value

Example Python code to extract dimensions:

dimensions = [item['name'] for item in widget['inputConfiguration']['columns']['value']]

Explanation of the order parameter

The order parameter is an array of arrays, where each inner array contains two elements:

  1. The dimension name
  2. The sort direction (“desc” for descending or “asc” for ascending)

The first item in the order array should always be the default sort dimension, which is specified by inputConfiguration.defaultSort.value.name. The remaining items should be the other dimensions in the order they appear in inputConfiguration.columns.value.

For example, if balances.account_id is the default sort, the order array would look like this:

[
  ["balances.account_id", "desc"],
  ["balances.account_number", "desc"],
  ["balances.country", "desc"],
  ["balances.account_code", "desc"],
  ["balances.amount", "desc"],
  ["balances.currency", "desc"],
  ["balances.balance_date", "desc"]
]

Example to construct this order array programmatically:

  1. Start with the default sort dimension:

    default_sort = widget['inputConfiguration']['defaultSort']['value']['name']
    order = [[default_sort, "desc"]]
    
  2. Add the remaining dimensions:

    dimensions = [item['name'] for item in widget['inputConfiguration']['columns']['value']]
    for dim in dimensions:
        if dim != default_sort:
            order.append([dim, "desc"])
    

Step 3: Construct Query Payload

Using the extracted data, construct a payload for the query API:

{
  "inputName": "<extracted_input_name>",
  "datasetId": "<extracted_dataset_id>",
  "dimensions": ["<dimension1>", "<dimension2>", ...],
  "order": [
    ["<default_sort_dimension>", "desc"],
    ["<dimension1>", "desc"],
    ["<dimension2>", "desc"],
    ...
  ],
  "offset": 0,
  "limit": <extracted_limit>,
  "componentId": "<extracted_component_id>",
  "timezone": "Europe/Amsterdam"
}

Step 4: Query Data

API Endpoint

  • URL: https://app-api.eu.embeddable.com/web-component/v1/query
  • Method: POST

curl Command

curl 'https://app-api.eu.embeddable.com/web-component/v1/query?' \
  -H 'accept: */*' \
  -H 'accept-language: en-GB,en-US;q=0.9,en;q=0.8' \
  -H 'authorization: Bearer [YOUR_ACCESS_TOKEN]' \
  -H 'content-type: application/json' \
  -H 'origin: [YOUR_ORIGIN]' \
  -H 'referer: [YOUR_REFERER]' \
  -H 'sec-ch-ua: "Not;A=Brand";v="24", "Chromium";v="128"' \
  -H 'sec-ch-ua-mobile: ?0' \
  -H 'sec-ch-ua-platform: "macOS"' \
  -H 'sec-fetch-dest: empty' \
  -H 'sec-fetch-mode: cors' \
  -H 'sec-fetch-site: cross-site' \
  -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36' \
  --data-raw '{
    "inputName": "ds",
    "datasetId": "[DATASET_ID]",
    "dimensions": [
        "balances.account_number",
        "balances.country",
        "balances.account_code",
        "balances.account_id",
        "balances.amount",
        "balances.currency",
        "balances.balance_date"
    ],
    "order": [
        ["balances.account_id", "desc"],
        ["balances.account_number", "desc"],
        ["balances.country", "desc"],
        ["balances.account_code", "desc"],
        ["balances.amount", "desc"],
        ["balances.currency", "desc"],
        ["balances.balance_date", "desc"]
    ],
    "offset": 0,
    "limit": 1000,
    "componentId": "[COMPONENT_ID]",
    "timezone": "Europe/Amsterdam"
  }'

Example Payload

{
  "inputName": "ds",
  "datasetId": "[DATASET_ID]",
  "dimensions": [
    "balances.account_number",
    "balances.country",
    "balances.account_code",
    "balances.account_id",
    "balances.amount",
    "balances.currency",
    "balances.balance_date"
  ],
  "order": [
    ["balances.account_id", "desc"],
    ["balances.account_number", "desc"],
    ["balances.country", "desc"],
    ["balances.account_code", "desc"],
    ["balances.amount", "desc"],
    ["balances.currency", "desc"],
    ["balances.balance_date", "desc"]
  ],
  "offset": 0,
  "limit": 1000,
  "componentId": "[COMPONENT_ID]",
  "timezone": "Europe/Amsterdam"
}

Explanation of the Example Payload:

  1. inputName: Set to “ds” as specified in the original data.
  2. datasetId: Taken from the inputConfiguration.ds.value.datasetId.
  3. dimensions: List of all dimension names from inputConfiguration.columns.value.
  4. order:
    • The first item is “balances.account_id”, assuming it was the defaultSort.value.name.
    • The remaining items are all other dimensions, in the order they appear in dimensions.
    • All are set to “desc” (descending) order.
  5. offset: Set to 0 to start from the beginning of the dataset.
  6. limit: Set to 1000, as specified in the original inputConfiguration.limit.value.
  7. componentId: Taken from the id field of the widget.
  8. timezone: Set to “Europe/Amsterdam” as a default value.

Notes

  • Filters are automatically applied by the API and don’t need to be included in the query payload - the data returned will be filtered automatically.
  • The timezone and variableValues fields in the query payload are optional.
  • Replace all placeholder values (marked with [BRACKETS]) with your actual values.