Skip to content

Instantly share code, notes, and snippets.

@danielneal
Created July 16, 2021 15:08
Show Gist options
  • Save danielneal/401417a29dac004ff802f06a056d4a57 to your computer and use it in GitHub Desktop.
Save danielneal/401417a29dac004ff802f06a056d4a57 to your computer and use it in GitHub Desktop.
SQA Common

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"))

(see section 9 for more on period handling)

\\ 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. Getting/setting title and descrition

The view title and description can be retrieved with:

sqa.common.getTitle(<view>)

sqa.common.getDescription(<view>)

To update:

sqa.common.setTitle(<view>, <value>)

sqa.common.setDescription(<view>, <value>)

9. 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"
}

9. Periods, granularities and timezones

The UI needs to provide a period dropdown, a granularity dropdown and custom period selector.

The options for periods are available from the api via /periods, and the granularities from /granularities.

Each period provides a default-granularity which should be picked when it is selected, and a range of granularities which should be used for the granularity dropdown.

To set the values for the date picker you can use the method getPeriodParameterWithRange(view).

This returns an object with the following values

{
    period: "last-24-hours", 
    start: "2021-07-12T23:23:00.000",
    end: "2021-07-13T23:23:00.000"
}

The start and end datetimes are strings, not datetime objects, and should be passed to the datetimepacker as such, without intermediate processing or parsing.

This is because we want to do all our calculations in the user's timezone, not the browser timezone.

To set the start and end date for the period, use setPeriodStart(view,start) and setPeriodEnd(view,end). The period will automatically be changed to a custom period.

If you set the period to custom, it will default to a custom range with the currently selected start and end times.

For timezone support to work in highcharts, you need to require the library moment-timezone, and initialize it with MomentTimeZone().

The library can operate in different timezones and timezone modes.

  • To get the list of timezones, use the variable Timezones
  • To set the user timezone, call setUserTimezone(timezone)
  • To get the list of timezone modes, use the variable DataTimezoneModes
  • To set the timezone mode, call setDataTimezoneMode(mode)

These affect how the time ranges are calculated and how the charts and tables are displayed.

The user's preferred timezone mode and timezone are available through the api /user endpoint.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment