Compliance example

Since Nodelab is based on the Edgescript® programming language, Nodelab can handle fairly complex and complicated tasks.

For instance, compliance standards can be massive. The PCI DSS compliance standard contains more than 200 requirements, each with a number of controls, looking into logs and snapshot files. The status for many of these controls must be evaluated for every configurable item in the environment.
This means that tens of thousands of status points must be calculated every time you perform an environment review. You want to do this continuously, not once per year, to keep track of improvements or other changes.
Since each control definition is unique, it must be dynamically implemented. A configuration definition that includes every control difference would be too complicated and hard to maintain.

A sample of PCI DSS definitions can be found here.

Nodelab can automate these tasks, using parameterized Edgescript® calls to a number of methods, and present the result as a hierarchical traffic-light tree view.

This example starts with an Excel file. The first column is used for determining which database table the row is stored in, with references to other row types above.

Excel is a very good tool for manual bulk editing and for viewing current states of complicated systems. It is one of many ways to input data into Nodelab.
When calculating status, a control can look into any other data source, both automatically collected or manually edited.

This Excel requirement definition file can either be added to a file structure that is part of the Nodelab resources, or it can be periodically or manually downloaded to a specific file directory.

Class

First, we define different clases for the rows. These classes are used by the server for automatically creating or modifying corresponding database tables. They are also used by the server and the browser in Edgescript® code.
This is the Requirement class.

class Requirement {
  @propertyTitle('Requirement Code')
  let reqCode: Char<255>

  @propertyTitle('Order Id')
  let orderId: Int

  @propertyTitle('Name')
  let name: Char<255>

  @propertyTitle('Review Time')
  let reviewTime: Datetime

  @propertyTitle('Description')
  let description: Text

  @propertyTitle('Comment')
  let comment: Text
}

Pattern

This is an Edgescript® example of a pattern parsing definition for the Excel file above.

    RecordPattern(
        isResource: true,
        pattern: 'Requirement(?:-.*)\.xlsx',
        processRecord: (storage: RecordStorage, record: [String], pathData: [String: Node], env: [String: Node]): Boolean -> {
            let std = ReqStandard()
            let version = ReqVersion()
            let req = Requirement()
            var rowType, stdOrg, parentOrg, parentCode, ctrlCode: String
            RecordPattern.parse(record, \.rowType, \.stdOrg, \.std.stdCode, \.version.versionCode, \.version.effectiveFrom,
                \.req.reqCode, \.req.name, \.parentOrg, \.parentCode, \.req.reviewTime, \.ctrlCode, \.req.description,
                \.req.comment)
            if (rowType=='STD') {
                std._orgCode = env['orgCode']
                std.name = std.stdCode
                storage.store(std)
                env['std'] = std
            } else if (rowType=='VER') {
                version._orgCode = env['orgCode']
                version.standard = env['std']
                version.name = version.versionCode
                storage.store(version)
                env['version'] = version
            } else if (rowType=='REQ') {
                req._orgCode = env['orgCode']
                req.orderId = if env['orderId'] Int(env['orderId']) + 1 else 1
                storage.store(req)
                let content = ReqContent()
                content._orgCode = env['orgCode']
                content.version = env['version']
                content.req = req
                storage.store(content)
                env['req'] = req
                env['orderId'] = req.orderId
                if (parentCode) {
                  let parent = Requirement()
                  parent.reqCode = parentCode
                  let link = ReqParent()
                  link._orgCode = env['orgCode']
                  link.parent = parent
                  link.child = req
                  storage.store(link)
                }
            } else if (rowType=='CTRL') {
                let ctrl = Control()
                ctrl.ctrlCode = ctrlCode
                let link = ReqControl()
                link._orgCode = env['orgCode']
                link.req = env['req']
                link.ctrl = ctrl
                storage.store(link)
            }
            return true
        })

Evaluation

The script associated with each control is evaluated, periodically or at request, to produce status points for each control or configurable item, depending on the nature of the control.

Visualization

This is an example of code for a browser page. It looks similar to SwiftUI.
Note that the tree view uses a single query instance for the collection of a whole tree consisting of data from eleven different database tables, where each table can have its own parent-child dependency.
The status category, which determines the traffic-light color of each node, is aggregated from children to parents.

class ComplianceDashboard: Page {
  state layer = @pathClass(StatusLayer) QueryState(valuesQuery: Query(
    scripts: ['\.layerId/', '\.name/'],
    orderScripts: ['\.layerId/'],
    limit: 1000,
    isDistinct: true
  ))
  state reqCode = State()
  state req = @pathClass(Requirement) QueryState(Query(
    scripts: ['\.name/', '\.description/'],
    filters: ['\.reqCode/==\(reqCode)'],
    limit: 1
  ))
  init() {
    let treeQuery = @pathClass(ReqStandard) Query(
      titleScript: '\.name/',
      scripts: ['\.stdCode/'],
      orderScripts: ['\.name/'],
      levels: [(
        parent: [(
          from: \ReqVersion.standard,
          to: \ReqStandard
        )],
        query: @pathClass(ReqVersion) Query(
          titleScript: '\.name/',
          scripts: ['\.versionCode/'],
          orderScripts: ['\.name/'],
          maxClassCount: 2,
          levels: [(
            parent: [(
              from: \Requirement,
              to: \ReqContent.req
            ), (
              from: \ReqContent.version,
              to: \ReqVersion
            )],
            query: @pathClass(Requirement) Query(
              titleScript: '\.name/',
              scripts: ['\.reqCode/'],
              orderScripts: ['\.orderId/'],
              limit: 100000,
              maxClassCount: 5,
              parent: [(
                from: \Requirement,
                to: \ReqParent.child
              ), (
                from: \ReqParent.parent,
                to: \Requirement
              )],
              levels: [(
                parent: [(
                  from: \Control,
                  to: \ReqControl.ctrl
                ), (
                  from: \ReqControl.req,
                  to: \Requirement
                )],
                query:
                @pathClass(Control)
                @edgeSpaces([
                  EdgeSpace(Status, name: 'status', filters: ['\Control/==\status.ctrl/']),
                  EdgeSpace(StatusCategory, name: 'statusCat', filters: ['\status.category/==\statusCat/'])
                ])
                Query(
                  titleScript: '\.name/',
                  scripts: ['\.ctrlCode/', '\statusCat.level..max/'],
                  orderScripts: ['\.orderId/'],
                  limit: 100000,
                  maxClassCount: 12,
                  parent: [(
                    from: \Control,
                    to: \ControlParent.child
                  ), (
                    from: \ControlParent.parent,
                    to: \Control
                  )],
                  layers: [(
                    from: \StatusLayer,
                    to: \StatusLayer.parent
                  )],
                  layerPaths: [(
                    from: \StatusLayer,
                    to: \status.layer
                  )]
                )
              )]
            )
           )]
         )
      )]
    )
    super(
      VStack(
        HStack(
          StateSelector(\.layer, title: 'Layer', titleScript: '\.name/', defaultSelectionIndex: 0, css: 'col-md-2')
        ),
        HStack(
          TreeView(
            title: 'Standards',
            stateIcons: ['status-none', 'status-valid', 'status-partial', 'status-pending', 'status-invalid'],
            stateScript: '\StatusCategory.level/|0',
            query: treeQuery,
            css: 'col-md-4'
          )
        )
      )
    )
  }
}

UNQL

The query is translated into a UNQL request:

{
  "siteCode": "ref",
  "packCode": "ref.test",
  "appCode": "analytics",
  "orgCode": "org.test",
  "limit": 100,
  "joinBranches": 1,
  "query": {
    "levels": [
      {
        "parent": [
          {
            "from": "\\ReqVersion.standard",
            "to": "\\ReqStandard"
          }
        ],
        "query": {
          "levels": [
            {
              "parent": [
                {
                  "from": "\\Requirement",
                  "to": "\\ReqContent.req"
                },
                {
                  "from": "\\ReqContent.version",
                  "to": "\\ReqVersion"
                }
              ],
              "query": {
                "levels": [
                  {
                    "parent": [
                      {
                        "from": "\\Control",
                        "to": "\\ReqControl.ctrl"
                      },
                      {
                        "from": "\\ReqControl.req",
                        "to": "\\Requirement"
                      }
                    ],
                    "query": {
                      "pathClassName": "Control",
                      "edgeSpaces": [
                        {
                          "name": "status",
                          "pathClassName": "Status",
                          "filters": [
                            "\\Control/==\\status.ctrl/"
                          ]
                        },
                        {
                          "name": "statusCat",
                          "pathClassName": "StatusCategory",
                          "filters": [
                            "\\status.category/==\\statusCat/"
                          ]
                        }
                      ],
                      "parent": [
                        {
                          "from": "\\Control",
                          "to": "\\ControlParent.child"
                        },
                        {
                          "from": "\\ControlParent.parent",
                          "to": "\\Control"
                        }
                      ],
                      "layers": [
                        {
                          "from": "\\StatusLayer",
                          "to": "\\StatusLayer.parent"
                        }
                      ],
                      "layerPaths": [
                        {
                          "from": "\\StatusLayer",
                          "to": "\\status.layer"
                        }
                      ],
                      "titleScript": "\\.name/",
                      "scripts": [
                        "\\.ctrlCode/",
                        "\\statusCat.level..max/"
                      ],
                      "orderScripts": [
                        "\\.orderId/"
                      ],
                      "limit": 100000,
                      "maxClassCount": 12,
                      "validity": null
                    }
                  }
                ],
                "pathClassName": "Requirement",
                "edgeSpaces": [],
                "parent": [
                  {
                    "from": "\\Requirement",
                    "to": "\\ReqParent.child"
                  },
                  {
                    "from": "\\ReqParent.parent",
                    "to": "\\Requirement"
                  }
                ],
                "titleScript": "\\.name/",
                "scripts": [
                  "\\.reqCode/"
                ],
                "orderScripts": [
                  "\\.orderId/"
                ],
                "limit": 100000,
                "maxClassCount": 5,
                "validity": null
              }
            }
          ],
          "pathClassName": "ReqVersion",
          "edgeSpaces": [],
          "titleScript": "\\.name/",
          "scripts": [
            "\\.versionCode/"
          ],
          "orderScripts": [
            "\\.name/"
          ],
          "limit": 100,
          "maxClassCount": 2,
          "validity": null
        }
      }
    ],
    "pathClassName": "ReqStandard",
    "edgeSpaces": [],
    "titleScript": "\\.name/",
    "scripts": [
      "\\.stdCode/"
    ],
    "orderScripts": [
      "\\.name/"
    ],
    "limit": 100,
    "maxClassCount": 1,
    "validity": null
  },
  "lang": "en"
}

SQL

The UNQL request is translated into four SQL queries.

SELECT T1.NAME AS T1_NAME,
	T1.STDCODE AS T1_STDCODE
FROM ORG_TEST_REF_TEST_REQSTANDARD T1
ORDER BY T1.NAME

SELECT T1.STDCODE AS T1_STDCODE,
	T2.NAME AS T2_NAME,
	T2.VERSIONCODE AS T2_VERSIONCODE,
	T2.STANDARD_STDCODE AS T2_STANDARD_STDCODE,
	T2.STANDARD_STDCODE AS T2_STANDARD_STDCODE
FROM ORG_TEST_REF_TEST_REQSTANDARD T1
LEFT JOIN ORG_TEST_REF_TEST_REQVERSION T2 ON (T1.STDCODE = T2.STANDARD_STDCODE)
ORDER BY T2.NAME

SELECT T2.VERSIONCODE AS T2_VERSIONCODE,
	T2.STANDARD_STDCODE AS T2_STANDARD_STDCODE,
	T3.REQ_REQCODE AS T3_REQ_REQCODE,
	T3.VERSION_VERSIONCODE AS T3_VERSION_VERSIONCODE,
	T3.VERSION_VERSION_STDCODE AS T3_VERSION_VERSION_STDCODE,
	T4.NAME AS T4_NAME,
	T4.ORDERID AS T4_ORDERID,
	T4.REQCODE AS T4_REQCODE,
	T4.REQCODE AS T4_REQCODE,
	T4.REQCODE AS T4_REQCODE,
	T5.CHILD_REQCODE AS T5_CHILD_REQCODE,
	T5.PARENT_REQCODE AS T5_PARENT_REQCODE
FROM ORG_TEST_REF_TEST_REQSTANDARD T1
LEFT JOIN ORG_TEST_REF_TEST_REQVERSION T2 ON (T1.STDCODE = T2.STANDARD_STDCODE)
LEFT JOIN ORG_TEST_REF_TEST_REQCONTENT T3 ON (T2.STANDARD_STDCODE = T3.VERSION_VERSION_STDCODE)
AND (T2.VERSIONCODE = T3.VERSION_VERSIONCODE)
LEFT JOIN ORG_TEST_REF_TEST_REQUIREMENT T4 ON (T3.REQ_REQCODE = T4.REQCODE)
LEFT JOIN ORG_TEST_REF_TEST_REQPARENT T5 ON (T5.CHILD_REQCODE = T4.REQCODE)
ORDER BY T4.ORDERID

SELECT T4.REQCODE AS T4_REQCODE,
	T6.CTRL_CTRLCODE AS T6_CTRL_CTRLCODE,
	T6.REQ_REQCODE AS T6_REQ_REQCODE,
	T7.CTRLCODE AS T7_CTRLCODE,
	T7.CTRLCODE AS T7_CTRLCODE,
	T7.NAME AS T7_NAME,
	T7.ORDERID AS T7_ORDERID,
	MAX(STATUSCAT_T8.LEVEL) AS STATUSCAT_T8_LEVEL,
	T9.CHILD_CTRLCODE AS T9_CHILD_CTRLCODE,
	T9.PARENT_CTRLCODE AS T9_PARENT_CTRLCODE,
	T10.LAYERID AS T10_LAYERID,
	STATUS_T11.LAYER_LAYERID AS STATUS_T11_LAYER_LAYERID
FROM ORG_TEST_REF_TEST_REQSTANDARD T1
LEFT JOIN ORG_TEST_REF_TEST_REQVERSION T2 ON (T1.STDCODE = T2.STANDARD_STDCODE)
LEFT JOIN ORG_TEST_REF_TEST_REQCONTENT T3 ON (T2.STANDARD_STDCODE = T3.VERSION_VERSION_STDCODE)
AND (T2.VERSIONCODE = T3.VERSION_VERSIONCODE)
LEFT JOIN ORG_TEST_REF_TEST_REQUIREMENT T4 ON (T3.REQ_REQCODE = T4.REQCODE)
LEFT JOIN ORG_TEST_REF_TEST_REQPARENT T5 ON (T5.CHILD_REQCODE = T4.REQCODE)
LEFT JOIN ORG_TEST_REF_TEST_REQCONTROL T6 ON (T4.REQCODE = T6.REQ_REQCODE)
LEFT JOIN ORG_TEST_REF_TEST_CONTROL T7 ON (T6.CTRL_CTRLCODE = T7.CTRLCODE)
LEFT JOIN ORG_TEST_REF_TEST_CONTROLPARENT T9 ON (T9.CHILD_CTRLCODE = T7.CTRLCODE)
LEFT JOIN ORG_TEST_REF_TEST_STATUS STATUS_T11 ON (T7.CTRLCODE = STATUS_T11.CTRL_CTRLCODE)
LEFT JOIN ORG_TEST_REF_TEST_STATUSLAYER T10 ON (STATUS_T11.LAYER_LAYERID = T10.LAYERID)
LEFT JOIN ORG_TEST_REF_TEST_STATUSCATEGORY STATUSCAT_T8 ON (STATUS_T11.CATEGORY_NAME = STATUSCAT_T8.NAME)
GROUP BY STATUS_T11_LAYER_LAYERID,
	T10_LAYERID,
	T4_REQCODE,
	T6_CTRL_CTRLCODE,
	T6_REQ_REQCODE,
	T7_CTRLCODE,
	T7_CTRLCODE,
	T7_NAME,
	T7_ORDERID,
	T9_CHILD_CTRLCODE,
	T9_PARENT_CTRLCODE
ORDER BY T7.ORDERID

The results are combined into a tree structure, and the server returns a JSON tree structure based on data from eleven different database tables.