Skip to content

Commit 7e39b98

Browse files
committed
feat(notebook): add support for attributes in sequences
Add support for sequences.globalFilters with attributes. Main attributes supported are: - ${attributes.ecu} - ${attributes.lifecycles.id}.
1 parent eb3de8b commit 7e39b98

File tree

4 files changed

+153
-14
lines changed

4 files changed

+153
-14
lines changed

docs/fishbone/docs/sequences.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ attribute | description
4040
`name` | Name of the sequence. Should be well defined as all sequences share the same namespace and the DLT-logs extension shows the results in the tree view under Events/Sequences/name.
4141
`steps` | Array with objects defining the events aka steps. Those steps are checked for being executed in order. See [step definition](#step-definition)
4242
`failures` | Object/map with filters defining a possible failure for the sequence. The object key defines the name of the failure and the key value defines the filter used to detect that. See [failures definition](#failures-definition)
43+
`globalFilters`| Optional array with filters that are applied first. Main intention is to use neg. filters here to filter for attributes.ecu or attributes.lifecycles.id. `{type: 1, not: true, ecu: ${attributes.ecu}}` or `{type: 1, not: true, lifecycles: ${attributes.lifecycles.id}}` This allows to apply the filters ECU/lifecycles for fishbones.
4344

4445
An example with one failure but without step details :
4546

@@ -73,7 +74,7 @@ attribute | description
7374
`canCreateNew` | Optional: Determines whether this step can create a new sequence occurrence. Defaults to `true`. Must not be `false` for the first step in a sequence. Set to `false` if this step shall only be checked for a created occurrence from an earlier step. So the `filter`, `sequence`, `alt` or `par` will be ignored then.
7475
`ignoreOutOfOrder` | Optional: if true, any matches/occurrences of this step that are out of order/sequence are ignored. Can only be used if the step before this step is mandatory. Defaults to `false`. This can be used if some messages occur often but you expect it exactly after one step and you do ignore any other occurrences.
7576
`filter` | [DLT filter](https://mbehr1.github.io/dlt-logs/docs/filterReference#details) definition. If this filter matches a msg the step is seen as "matching". Either `filter`, `sequence`, `alt` or `par` must be provided.
76-
`sequence` | A definition of a `sub-sequence`. For this step a full sequence is used. This is useful to either break down a bigger sequence into smaller parts of if this step can be executed multiple times (e.g. with `card:*`) but consists of multiple events/steps. See [example](#example).
77+
`sequence` | A definition of a `sub-sequence`. For this step a full sequence is used. This is useful to either break down a bigger sequence into smaller parts or if this step can be executed multiple times (e.g. with `card:*`) but consists of multiple events/steps. See [example](#example).
7778
`alt` | A definition for a list of alternative steps. The `alt` attribute is an array/list of step definitions. Any `card` or `canCreateNew` attribute will automatically be applied to the alternative steps. For this step to be `ok` exactly one step needs to be `ok`. See [example alt](#example-alternative-steps).
7879
`par` | A definition for a list of parallel steps. The `par` attribute is an array/list of step definitions. Single steps can have their own `card` or `canCreateNew` attribute and the step with `par` as well. For this step to be `ok` all mandatory steps ( `card` not `?,*` ) need to be `ok`. The order in which the parallel steps are fulfilled doesn't matter.
7980

src/extension/fbaEditor.ts

+144-8
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ import * as path from 'path'
1010
import * as fs from 'fs'
1111
import * as vscode from 'vscode'
1212
import { getNonce, performHttpRequest } from './util'
13-
import * as yaml from 'js-yaml'
1413
import TelemetryReporter from '@vscode/extension-telemetry'
1514
import ShortUniqueId from 'short-unique-id'
1615
import { FBAFSProvider } from './fbaFSProvider'
1716
import { FBANotebookProvider } from './fbaNotebookProvider'
1817
import { currentFBAFileVersion, fbaToString, fbaYamlFromText, getFBDataFromText } from './fbaFormat'
18+
import { RQ, rqUriEncode } from 'dlt-logs-utils/restQuery'
19+
import * as JSON5 from 'json5'
1920

2021
const uid = new ShortUniqueId({ length: 8 })
2122

@@ -161,7 +162,7 @@ export class FBAEditorProvider implements vscode.CustomTextEditorProvider, vscod
161162
_postMsgOnceAlive(docData, {
162163
type: 'CustomEvent',
163164
eventType: 'ext:drop',
164-
detail: { mimeType: 'application/vnd.dlt-logs+json', values: JSON.parse(value) },
165+
detail: { mimeType: 'application/vnd.dlt-logs+json', values: JSON5.parse(value) },
165166
})
166167
})
167168
}
@@ -235,7 +236,7 @@ export class FBAEditorProvider implements vscode.CustomTextEditorProvider, vscod
235236
console.log(`fishbone restQuery('/get/version')=${versResp}`)
236237
{
237238
// add some version checks:
238-
const versObj = JSON.parse(versResp)
239+
const versObj = JSON5.parse(versResp)
239240
if ('data' in versObj && versObj['data'].type === 'version') {
240241
const versAttrs = versObj['data'].attributes
241242
if (versAttrs && 'name' in versAttrs) {
@@ -513,7 +514,7 @@ export class FBAEditorProvider implements vscode.CustomTextEditorProvider, vscod
513514
{
514515
// {"type":"restQuery","request":"ext:dlt-logs/get/sw-versions"}
515516
const url: string = typeof e.req.request === 'string' ? e.req.request : e.req.request.url // todo request.url should not occur any longer!
516-
this.performRestQuery(docData, url).then(
517+
this.performRestQueryUri(docData, url).then(
517518
(resJson) => {
518519
webviewPanel.webview.postMessage({ type: e.type, res: resJson, id: e.id })
519520
},
@@ -547,12 +548,147 @@ export class FBAEditorProvider implements vscode.CustomTextEditorProvider, vscod
547548
this.updateWebview(docData, document)
548549
}
549550

550-
public performRestQuery(docData: DocData, url: string): Promise<any> {
551+
/**
552+
* Perform a rest query.
553+
*
554+
* This will replace ${attributes.<attribute>} with the value of the attribute for
555+
* the commands 'query' and 'report' in the rq.commands array. But only for direct members
556+
* of the filter objects in the array.
557+
*
558+
* Attributes in the form ${attributes.<attribute>.<member>} or ${attributes.<attribute>} are supported.
559+
* Attributes will be replaced by the value of the attribute if its a string/number or an array of strings/numbers.
560+
*
561+
* Undefined attributes will be removed from the filter object. This is different to the restQuery from
562+
* utils.js where the attributes are e.g. replaced with null values.
563+
* @param docData
564+
* @param rq
565+
* @returns
566+
*/
567+
public performRestQuery(docData: DocData, rq: RQ): Promise<any> {
568+
569+
//console.warn(`performRestQuery: got ext query for extName='${rq.path}' #commands='${rq.commands.length}'`)
570+
571+
// attribute support:
572+
const attrCache = new Map<string, string | number | (string | number)[] | undefined>()
573+
const getAttribute = (attribute: string): string | number | (string | number)[] | undefined => {
574+
// check if we already have the attribute in the cache
575+
if (attrCache.has(attribute)) {
576+
return attrCache.get(attribute)
577+
}
578+
const attrs = docData.lastPostedObj?.attributes
579+
if (Array.isArray(attrs)) {
580+
// iterate over all attributes and check if the attribute is in there
581+
// it can be attributename.member e.g. lifecycles.id or attributename like ecu
582+
// the attribute value can be a single string/value or an array with string/values
583+
// for ecu usually value is just a single member (string)
584+
// for lifecycles value is an array of objects with id (number), label,...
585+
586+
const [attrName, attrMember] = attribute.split('.')
587+
588+
for (const attr of attrs) {
589+
if (typeof attr === 'object' && attrName in attr) {
590+
// check if the attribute is in the object
591+
const attrVal = attr[attrName]?.value
592+
if (attrVal === undefined) {
593+
attrCache.set(attribute, attrVal)
594+
return undefined
595+
}
596+
if (attrMember) {
597+
// check if the attribute is in the object/array
598+
if (Array.isArray(attrVal)) {
599+
// check if the attribute is in the array
600+
if (attrVal.length === 0) {
601+
attrCache.set(attribute, attrVal as (string | number)[])
602+
return attrVal
603+
}
604+
605+
const attrMemberVals = attrVal.map((e: any) => e[attrMember])
606+
const toRet =
607+
attrMemberVals.length > 0
608+
? typeof attrMemberVals[0] === 'string' || typeof attrMemberVals[0] === 'number'
609+
? (attrMemberVals as (string | number)[])
610+
: undefined
611+
: []
612+
attrCache.set(attribute, toRet)
613+
return toRet
614+
} else {
615+
const toRet = typeof attrVal[0] === 'string' || typeof attrVal[0] === 'number' ? (attrVal as (string | number)[]) : []
616+
attrCache.set(attribute, toRet)
617+
return toRet
618+
}
619+
} else {
620+
let toRet
621+
if (Array.isArray(attrVal)) {
622+
toRet =
623+
attrVal.length > 0
624+
? typeof attrVal[0] === 'string' || typeof attrVal[0] === 'number'
625+
? (attrVal as (string | number)[])
626+
: undefined
627+
: []
628+
} else {
629+
toRet = typeof attrVal === 'string' || typeof attrVal === 'number' ? attrVal : undefined
630+
}
631+
attrCache.set(attribute, toRet)
632+
return toRet
633+
}
634+
}
635+
}
636+
}
637+
return undefined
638+
}
639+
640+
// search for filters and replace ${attribute.ecu} with the value of the attribute
641+
for (const cmd of rq.commands) {
642+
switch (cmd.cmd) {
643+
case 'report':
644+
case 'query':
645+
{
646+
const param = JSON5.parse(cmd.param)
647+
if (Array.isArray(param)) {
648+
let doChange = false
649+
for (const filter of param) {
650+
Object.keys(filter).forEach((key) => {
651+
if (typeof filter[key] === 'string' && filter[key].startsWith('${attributes.')) {
652+
// console.warn(`performRestQuery: got key: '${key}' with attribute '${filter[key]}'`)
653+
const attribute = filter[key].slice(13, -1) // remove ${attributes. and }
654+
const attrVal = getAttribute(attribute)
655+
console.info(`FBAEditorProvider.performRestQuery: got attribute '${attribute}' with value: ${JSON.stringify(attrVal)}`)
656+
if (attrVal !== undefined) {
657+
filter[key] = attrVal
658+
} else {
659+
// remove key:
660+
delete filter[key]
661+
}
662+
doChange = true
663+
}
664+
})
665+
}
666+
if (doChange) {
667+
cmd.param = JSON.stringify(param)
668+
}
669+
}
670+
}
671+
break
672+
}
673+
}
674+
return this.performRestQueryUri(docData, rqUriEncode(rq))
675+
}
676+
677+
/**
678+
* perform rest query via an uri.
679+
*
680+
* Does not perform the ${attributes.<attribute>} replacement.
681+
* @param docData
682+
* @param url
683+
* @returns
684+
*/
685+
public performRestQueryUri(docData: DocData, url: string): Promise<any> {
551686
const webviewPanel = docData.webviewPanel
552687

553688
if (url.startsWith('ext:')) {
554689
const extName = url.slice(4, url.indexOf('/'))
555690
const query = url.slice(url.indexOf('/'))
691+
// console.warn(`performRestQueryUri: got ext query for extName='${extName}' query='${query}'`)
556692
// did this extension offer the restQuery?
557693
return new Promise((resolve, reject) => {
558694
const rq = this._restQueryExtFunctions.get(extName)
@@ -561,11 +697,11 @@ export class FBAEditorProvider implements vscode.CustomTextEditorProvider, vscod
561697
let res: string | Thenable<string> = rq(query) // restQuery can return a string or a Promise<string>
562698
if (typeof res !== 'string') {
563699
res.then((value) => {
564-
const asJson = JSON.parse(value)
700+
const asJson = JSON5.parse(value)
565701
resolve(asJson)
566702
})
567703
} else {
568-
const asJson = JSON.parse(res)
704+
const asJson = JSON5.parse(res)
569705
resolve(asJson)
570706
}
571707
} else {
@@ -591,7 +727,7 @@ export class FBAEditorProvider implements vscode.CustomTextEditorProvider, vscod
591727
console.warn(` body first 2000 chars='${result.body.slice(0, 2000)}'`)
592728
}
593729
}
594-
const json = JSON.parse(result.body)
730+
const json = JSON5.parse(result.body)
595731
resolve(json)
596732
})
597733
.catch((err) => {

src/extension/fbaFSProvider.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,8 @@ export class FBAFSProvider implements vscode.FileSystemProvider {
332332
// the elem should be the restQuery
333333
if (elemMember) {
334334
if (FBANBRestQueryRenderer.editRestQuery(elemObj, elemMember, entry.elem.fbUid, members, newData)) {
335+
// updates elemObj[elemMember]. as this is a reference to the elem (entry.elem child) entry.elem is updated
336+
// entry.elem is a member of the lastFBA = doc.docData.lastPostedObj
335337
this.updateEntry(entry, false, newData)
336338
this.syncChangesToEditorProvider(entry)
337339
}
@@ -410,7 +412,7 @@ export class FBAFSProvider implements vscode.FileSystemProvider {
410412
content: Uint8Array,
411413
options: { readonly create: boolean; readonly overwrite: boolean },
412414
): void | Thenable<void> {
413-
console.log(`FBAFSProvider.writeFile nyi!(${uri.toString()}, content.length=${content.length}, options=${JSON.stringify(options)}})`)
415+
console.log(`FBAFSProvider.writeFile(${uri.toString()}, content.length=${content.length}, options=${JSON.stringify(options)}})`)
414416
const entry = this.getDataForUri(uri)
415417
if (entry) {
416418
this.editEntry(entry, content)

src/extension/fbaNBRQRenderer.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,7 @@ export class FBANBRestQueryRenderer {
464464
},
465465
],
466466
}
467-
editorProvider.performRestQuery(docData, rqUriEncode(filterRq)).then(
467+
editorProvider.performRestQuery(docData, filterRq).then(
468468
function (resJson: any) {
469469
if ('error' in resJson) {
470470
exec.appendOutput(
@@ -721,7 +721,7 @@ export class FBANBRestQueryRenderer {
721721
let perfStep = performance.now()
722722
let perfInterims = perfStep - perfStart
723723
console.log(`executeSequences: triggering rest query after ${perfInterims}ms, total:${perfStep - perfStart}ms`)
724-
await editorProvider.performRestQuery(docData, rqUriEncode(allFiltersRq)).then(
724+
await editorProvider.performRestQuery(docData, allFiltersRq).then(
725725
async (resJson: any) => {
726726
const perfNow = performance.now()
727727
perfInterims = perfNow - perfStep
@@ -882,7 +882,7 @@ export class FBANBRestQueryRenderer {
882882
}
883883
},
884884
(errTxt) => {
885-
console.log(`FBANBRestQueryRenderer.execRestQuery got error:`, errTxt)
885+
console.log(`FBANBRestQueryRenderer.performRestQuery got error:`, errTxt)
886886
exec.appendOutput(new NotebookCellOutput([vscode.NotebookCellOutputItem.stderr(`query got error:${JSON.stringify(errTxt)}`)]))
887887
},
888888
)
@@ -911,7 +911,7 @@ export class FBANBRestQueryRenderer {
911911
jsonPath: string,
912912
convFunction: string,
913913
) {
914-
editorProvider.performRestQuery(docData, rqUriEncode(rq)).then(
914+
editorProvider.performRestQuery(docData, rq).then(
915915
function (resJson: any) {
916916
if ('error' in resJson) {
917917
exec.appendOutput(

0 commit comments

Comments
 (0)