Skip to content

Instantly share code, notes, and snippets.

@danielneal
Created July 2, 2021 16:23
Show Gist options
  • Save danielneal/3a339bb0fe415003e00d18794c153271 to your computer and use it in GitHub Desktop.
Save danielneal/3a339bb0fe415003e00d18794c153271 to your computer and use it in GitHub Desktop.
SQA common docs

sqa-common utility library

The sqa-common JS library is a collection of utilities to support the clients of the SQA api. It is provided as a node module published on the public npm registry.

Usage

Add the following dependency to package.json:

"@3e/sqa-common": "^1.1.0"

and import the module where relevant:

import sqa_common from '@3e/sqa-common';

Reference

Reference documentation for the utility functions provided by the sqa-common library.

Views

Example payload returned by the /views endpoint:

[{
  "category": "GENERAL",
  "sqid": "view-sqid",
  "ref": "app/views/view-sqid",
  "name": "Plant level template view",
  "type": "CHART",
  "type_option": "TIME",
  "owner_type": "SYNAPTIQ",
  "label": "Plant level template view",
  "id": 10001,
  "parameters": [
    {
      "id": "Period",
      "class": "Parameter",
      "parameter": {
        "class": "Period",
        "label": "Period",
        "initial": "last-12-months"
      }
    },
    {
      "id": "Granularity",
      "class": "Parameter",
      "parameter": {
        "class": "Granularity",
        "label": "Granularity",
        "initial": "1-months"
      }
    },
    {
      "id": "plants",
      "class": "Parameter",
      "parameter": {
        "class": "ObjectRef",
        "label": "Plant",
        "labelKey": "assetType.PLANT.label",
        "constraint": {
          "object_category": "plants",
          "include_obsolete": false
        }
      }
    }
  ]
},...]

In the following paragraph we will refer to the json payload in above as viewsPayload.

viewVariablesRefCategories

Given the parameter field from the payload returned by a call to /views, viewVariablesRefCategories returns an array of view asset ref categories.

For example:

sqa_common.sqa.common.viewVariablesRefCategories(viewsPayload[0].parameters);

returns ["plants"].

Rendering visualizations

IMPORTANT: this is very alpha and subject to change.

The sqa common library is able to create visualizations (specifically, highcharts) and populate them with data.

To get a simple chart working, use the following steps.

1. Provide an api fn

The library needs to be able to call the api so it can access /logical_devices, /indicators (to construct the highcharts spec) and /data (to populate it)

Create an api functon that will call an endpoint on our api with authentication, and call the callback with the json response.

Then set the api fn using

sqa.common.setApiFn(apiFn)

2. Get a view definition

This can be done (for example) by calling /views/<id> from the api

3. Set the parameters on the view

This is necessary before the view can be rendered.

\\ to set the granularity (setView here is coming from the react hook)
sqa.common.setView(sqa_common.setGranularityParameter(view, "15-minutes"))

\\ to set the period
sqa.common.setView(sqa_common.setPeriodParameter(view, "last-month"))
\\ or, for a custom period
sqa.common.setView(sqa_common.setPeriodParameter(view,
   {"start":1622498400,
    "end":1624226400}}))

\\ to set an object parameter by id
sqa.common.setView(sqa_common.setObjectParameter(view, "plants", "plants/P0237"))

4. Get the visualization for the view, and populate it with data

sqa.common.getVisualization.then((visualization) => {
    sqa.common.populateVisualization(visualization, (visualization) => {
      console.log(visualization);
    })
})

5. To cancel the data fetch of populateVisualization, capture the return value

let cancel = sqa.common.populateVisualization(visualization, (visualization) => {
      console.log(visualization);
})

cancel()

Creating new views

1. Create a new empty view

The function sqa.common.newEmptyView(options) will create a new view ready to add objects and indicators to.

The options available are currently as follows:

{
  granularity: "15-minutes",
  period: "last-24-hours",
  view-type: "time-chart",
  description: "description",
  label: "label"
}

2. Populate the dropdowns for the builder

2a. Get the list of available levels

The first dropdown is the list of available levels. This list is static, and stored in the variable sqa.common.Levels

It has a format like:

[{
  "value": "site",
  "label": "Site"
 }, 
 {
  "value": "ac-node",
  "label": "AC Node"
 }]

2b. Get the list of available containers

The list of available containers depends on the selected level, and can be retrieved using the function sqa.common.getContainers(value) where value is the value of the selected level.

It has a format like:

[{
  "value": "all-sites",
  "label": "All Sites"
 },
 {
  "value": "plants/P0397",
  "label": "BE Brugge"
 }]

3. For enum datasets

3a. Populate the available objects using logical_devices

Call /logical_devices?ld_type=&container=&envelope=true

Note: the logical devices is paged so to ensure you get all the objects, navigate through the pages using the after cursor. Using limit=100000 is not guaranteed to work as we may place a maximum on the limit property.

3b. Populate the available indicators using /indicators and /ld_indicators.

The endpoint indicators?level=<selectedLevel>&locale=<locale> will return the full indicator information with label, unit, key etc. This can be cached.

The endpoint ld_indicators?object=<selectedObject>&object=<anotherSelectedObject> will return the indicator keys that can be applied to the selected objects. This can be then combined with the full indicator information from /indicatorsto provide the label and key for the applicable indidators.

3c. Add the object/indicator dataset to the view

The function sqa.common.addEnumDataset(view, objects, indicators, options} will return a new view with the dataset added.

Objects: is a list of object refs e.g. ["plants/P0237","plants/P0350"] Indicators: is a list of indicator keys e.g. ["irradiance"]

Following the designs, this should be called once per indicator so that the graphical representation and options for each indicator can be independently modified.

4. For template datasets

4a. Get the available templates using the selectedLevel and selectedContainer.

Use the function sqa.common.getTemplates(<view>, <spec>) where spec is an object like:

{
  selectedLevel:"ac-nodes",
  selectedContainer:"plants/P0237"
}

The function needs to call the api to get the labels for logical devices, so will return a promise. The promise will resolve to a list of templates like so:

[{:value "select-one",
  :label "Selectable site"}
 {:value "select-all",
  :label "All sites"}]

4b. Populate the available indicators using /indicators.

With the templates, you only need to call `/indicators?level=&locale=

4c. Add the template dataset to the view

The function sqa.common.addTemplateDataset(view, spec, indicators, options} will return a new view with the dataset added.

Spec: is an object like:

{ 
  selectedLevel:"site", 
  selectedTemplate:"select-one",
  selectedContainer: "site_groups/GRdemo.7"
 }

Indicators: is a list of indicator keys e.g. ["irradiance"]

Following the designs, this should be called once per indicator so that the graphical representation and options for each indicator can be independently modified.

5. View the datasets

The function sqa.common.getDatasets(<view>) will return the datasets available for the view. It needs to call the api to resolve the indicators and logical devices so will return a promise, which resolves to an array with one entry per dataset, like the following:

[{id:"bb380abd-ea20-4239-b3dc-077cd3308c77",
  indicators:
  [{level: "sites",
    label: "Irradiance",
    unit: "W/m2",
    unit_key: "W_M2",
    description: "Irradiance in the plane of the array(s) calculated using the same configuration and data as <i>Irradiation</i>.",
    key: "irradiance"}],
  objectSetLabel: "All sites",
  objectSet: {template: "select-all", level: "site"},
  representation: "line"}]

For enum datasets, the object set will also contain the resolved objects, which can be used to populate the expanded view, and the additional counter label.

[{id:"bb380abd-ea20-4239-b3dc-077cd3308c77",
  indicators:
  [{level: "sites",
    label: "Irradiance",
    unit: "W/m2",
    unit_key: "W_M2",
    description: "Irradiance in the plane of the array(s) calculated using the same configuration and data as <i>Irradiation</i>.",
    key: "irradiance"}],
  objectSetLabel: "BE Brugge",
  objectSet: {
    template: "enum", 
    level: "site",
    objects: [{
        name: "BE Brugge",
        level: "site",
        ref: "sites/P0237",
        type: "value"
    }]
  },
  representation: "line"}]

6. Remove/update a dataset

The basket will return an id for each dataset, which can be used as a handle to remove or modify it.

To remove:

sqa.common.removeDataset(<view>,<id>)

To update:

sqa.common.setDatasetOptions(<view>,<id>,<options>)

Currently the only available option is:

{
  graphicalRepresentation:"line"
}

The list of representations is available from sqa.common.Representations

7. View types

The variable sqa.common.ViewTypes lists the available view types, currently as follows:

  • Time chart
  • Object heatmap
  • Scatter chart
  • Time table
  • Object table
  • Object chart

You can use the display key in the visualization spec to determine whether to show a highcharts view or a data table.

The tables (object table and time table) return a spec as follows:

{
  display:"table", 
  header: ["Object" "Irradiance" "Power AC"], 
  rows: [["BE Brugge" 100 200],
         ["BE Ghent" 300 400]
}

8. Error handling

Some views will only work in certain cases.

The object heatmap requires exactly one indicator The scatter chart requires exactly two indicators, and the same object set for each indicator.

If the requirements are not met, getting the visualization will return a message spec:

{
  display:"message", 
  value: "scatter-chart-object-sets-unmatched",
  label: "Object sets must match for each indicator in a scatter chart"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment