Skip to content

Commit 979e8b6

Browse files
authored
Feature Page: Add conformance translation tooltip and external documentation link (#1626)
* display translation of conformance expression and link external documentation
1 parent fd75334 commit 979e8b6

File tree

12 files changed

+394
-21
lines changed

12 files changed

+394
-21
lines changed

cypress/support/commands.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,11 @@ Cypress.Commands.add('checkAndEnableFeature', (feature) => {
114114
cy.contains('[data-test="feature-row"]', feature.name).within(() => {
115115
cy.get('[data-test="feature-name"]').should('have.text', feature.name)
116116
cy.get('[data-test="feature-code"]').should('have.text', feature.code)
117-
cy.get('[data-test="feature-conformance"]').should(
118-
'have.text',
119-
feature.conformance
120-
)
117+
cy.get('[data-test="feature-conformance"]')
118+
.invoke('text')
119+
.then((text) => {
120+
expect(text.trim()).to.eq(feature.conformance)
121+
})
121122
cy.get('[data-test="feature-bit"]').should('have.text', feature.bit)
122123
cy.get('[data-test="feature-toggle"]')
123124
.should('exist')

docs/api.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20381,6 +20381,9 @@ This module provides utilities for evaluating conformance expressions.
2038120381
* [~getOperandsFromExpression(expression)](#module_Validation API_ Evaluate conformance expressions..getOperandsFromExpression) ⇒ <code>Array.&lt;string&gt;</code>
2038220382
* [~filterRelatedDescElements(elements, featureCode)](#module_Validation API_ Evaluate conformance expressions..filterRelatedDescElements) ⇒
2038320383
* [~checkFeaturesToUpdate(updatedFeatureCode, clusterFeatures, elementMap)](#module_Validation API_ Evaluate conformance expressions..checkFeaturesToUpdate) ⇒ <code>Object</code>
20384+
* [~translateConformanceTag(expression)](#module_Validation API_ Evaluate conformance expressions..translateConformanceTag) ⇒ <code>string</code>
20385+
* [~translateBooleanExpr(expr)](#module_Validation API_ Evaluate conformance expressions..translateBooleanExpr) ⇒ <code>string</code>
20386+
* [~translateConformanceExpression(expression)](#module_Validation API_ Evaluate conformance expressions..translateConformanceExpression) ⇒ <code>string</code>
2038420387

2038520388
<a name="module_Validation API_ Evaluate conformance expressions..evaluateConformanceExpression"></a>
2038620389

@@ -20495,6 +20498,42 @@ Check which features need to be updated or have conformance changes due to the u
2049520498
| clusterFeatures | <code>\*</code> |
2049620499
| elementMap | <code>\*</code> |
2049720500

20501+
<a name="module_Validation API_ Evaluate conformance expressions..translateConformanceTag"></a>
20502+
20503+
### Validation API: Evaluate conformance expressions~translateConformanceTag(expression) ⇒ <code>string</code>
20504+
Translate a conformance tag to its corresponding value.
20505+
20506+
**Kind**: inner method of [<code>Validation API: Evaluate conformance expressions</code>](#module_Validation API_ Evaluate conformance expressions)
20507+
**Returns**: <code>string</code> - The translated conformance value.
20508+
20509+
| Param | Type |
20510+
| --- | --- |
20511+
| expression | <code>string</code> |
20512+
20513+
<a name="module_Validation API_ Evaluate conformance expressions..translateBooleanExpr"></a>
20514+
20515+
### Validation API: Evaluate conformance expressions~translateBooleanExpr(expr) ⇒ <code>string</code>
20516+
Translate a boolean expression into natural language.
20517+
20518+
**Kind**: inner method of [<code>Validation API: Evaluate conformance expressions</code>](#module_Validation API_ Evaluate conformance expressions)
20519+
**Returns**: <code>string</code> - The translated boolean expression.
20520+
20521+
| Param | Type |
20522+
| --- | --- |
20523+
| expr | <code>string</code> |
20524+
20525+
<a name="module_Validation API_ Evaluate conformance expressions..translateConformanceExpression"></a>
20526+
20527+
### Validation API: Evaluate conformance expressions~translateConformanceExpression(expression) ⇒ <code>string</code>
20528+
Translate a conformance expression into natural language.
20529+
20530+
**Kind**: inner method of [<code>Validation API: Evaluate conformance expressions</code>](#module_Validation API_ Evaluate conformance expressions)
20531+
**Returns**: <code>string</code> - The translated conformance expression.
20532+
20533+
| Param | Type |
20534+
| --- | --- |
20535+
| expression | <code>string</code> |
20536+
2049820537
<a name="module_Validation API_ Parse conformance data from XML"></a>
2049920538

2050020539
## Validation API: Parse conformance data from XML

src-electron/db/db-mapping.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
const dbApi = require('./db-api.js')
2525
const dbEnums = require('../../src-shared/db-enum.js')
2626
const bin = require('../util/bin')
27+
const conformEvaluator = require('../validation/conformance-expression-evaluator')
2728

2829
exports.map = {
2930
package: (x) => {
@@ -268,6 +269,9 @@ exports.map = {
268269
includeServer: x.INCLUDE_SERVER,
269270
includeClient: x.INCLUDE_CLIENT,
270271
conformance: x.DEVICE_TYPE_CLUSTER_CONFORMANCE,
272+
translation: conformEvaluator.translateConformanceExpression(
273+
x.DEVICE_TYPE_CLUSTER_CONFORMANCE
274+
),
271275
featureId: x.FEATURE_ID,
272276
name: x.FEATURE_NAME,
273277
code: x.CODE,
@@ -288,6 +292,9 @@ exports.map = {
288292
bit: x.BIT,
289293
description: x.DESCRIPTION,
290294
conformance: x.CONFORMANCE,
295+
translation: conformEvaluator.translateConformanceExpression(
296+
x.CONFORMANCE
297+
),
291298
packageRef: x.PACKAGE_REF,
292299
clusterRef: x.CLUSTER_REF
293300
}

src-electron/validation/conformance-checker.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ function generateWarningMessage(
8989
// Check 2: if the feature conformance contains the operand 'desc'
9090
let featureContainsDesc = conformEvaluator.checkIfExpressionHasOperand(
9191
featureData.conformance,
92-
dbEnum.conformanceTag.desc
92+
dbEnum.conformanceTag.described
9393
)
9494
if (featureContainsDesc) {
9595
result.warningMessage.push(

src-electron/validation/conformance-expression-evaluator.js

Lines changed: 112 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ function evaluateConformanceExpression(expression, elementMap) {
7575
// if any operand is desc, the conformance is too complex to parse
7676
for (let part of parts) {
7777
let operands = getOperandsFromExpression(part)
78-
if (operands && operands.includes(dbEnum.conformanceTag.desc)) {
79-
return dbEnum.conformanceTag.desc
78+
if (operands && operands.includes(dbEnum.conformanceTag.described)) {
79+
return dbEnum.conformanceTag.described
8080
}
8181
}
8282
for (let part of parts) {
@@ -169,7 +169,7 @@ function filterRelatedDescElements(elements, featureCode) {
169169
let operands = getOperandsFromExpression(element.conformance)
170170
return (
171171
operands &&
172-
operands.includes(dbEnum.conformanceTag.desc) &&
172+
operands.includes(dbEnum.conformanceTag.described) &&
173173
operands.includes(featureCode)
174174
)
175175
})
@@ -238,9 +238,118 @@ function checkFeaturesToUpdate(
238238
return { updatedFeatures, changedConformFeatures }
239239
}
240240

241+
/**
242+
* Translate a conformance tag to its corresponding value.
243+
*
244+
* @param {string} expression
245+
* @returns {string} The translated conformance value.
246+
*/
247+
function translateConformanceTag(expression) {
248+
let tagKeys = Object.keys(dbEnum.conformanceTag)
249+
for (let key of tagKeys) {
250+
if (expression === dbEnum.conformanceTag[key]) {
251+
return dbEnum.conformanceVal[key]
252+
}
253+
}
254+
return ''
255+
}
256+
257+
/**
258+
* Translate a boolean expression into natural language.
259+
*
260+
* @param {string} expr
261+
* @returns {string} The translated boolean expression.
262+
*/
263+
function translateBooleanExpr(expr) {
264+
// match operands and operators
265+
let tokens = expr.match(/[A-Za-z0-9_]+|[!&|()]/g) || []
266+
let output = []
267+
268+
for (let i = 0; i < tokens.length; i++) {
269+
let token = tokens[i]
270+
271+
if (token === '&') {
272+
output.push(dbEnum.logicalOperators.and)
273+
} else if (token === '|') {
274+
output.push(dbEnum.logicalOperators.or)
275+
} else if (token === '(' || token === ')') {
276+
output.push(token)
277+
} else if (token === '!') {
278+
// For the '!' operator: if it precedes '()', translate as 'not';
279+
// otherwise, combine with the next operand as '<operand> is not enabled'
280+
let next = tokens[i + 1]
281+
if (next === '(') {
282+
output.push(dbEnum.logicalOperators.not)
283+
} else {
284+
output.push(`${next} is not enabled`)
285+
i++ // Skip the next token since we consumed it
286+
}
287+
} else {
288+
// if none of the above is matched, it is an element operand
289+
output.push(`${token} is enabled`)
290+
}
291+
}
292+
293+
return output.join(' ')
294+
}
295+
296+
/**
297+
* Translate a conformance expression into natural language.
298+
*
299+
* @export
300+
* @param {string} expression
301+
* @returns {string} The translated conformance expression.
302+
*/
303+
function translateConformanceExpression(expression) {
304+
if (!expression) return ''
305+
306+
let conformanceTag = translateConformanceTag(expression)
307+
if (conformanceTag) return conformanceTag
308+
309+
// special case on provisional conformance in format of "P, <expression>"
310+
// handle 'P,' separately and use recursion to translate the rest
311+
if (expression.startsWith(dbEnum.conformanceTag.provisional + ',')) {
312+
let rest = expression.slice(2).trim()
313+
let translatedRest = translateConformanceExpression(rest)
314+
return `provisional for now. When not provisional in the future, it is ${translatedRest}`
315+
}
316+
317+
// split by ',' to handle each expression in otherwise conformance separately
318+
let parts = expression.split(',').map((p) => p.trim())
319+
let translatedParts = parts.map((part) => {
320+
let conformanceTag = translateConformanceTag(part)
321+
if (conformanceTag) return conformanceTag
322+
323+
// handle optional expressions surrounded by '[]'
324+
let optionalMatch = part.match(/^\[(.*)\]$/)
325+
if (optionalMatch) {
326+
// optionalMatch[1] is the expression inside '[]'
327+
let optionalText = translateBooleanExpr(optionalMatch[1])
328+
return `${dbEnum.conformanceVal.optional} if ${optionalText}`
329+
}
330+
331+
// otherwise it's a regular mandatory expression
332+
let translated = translateBooleanExpr(part)
333+
return `${dbEnum.conformanceVal.mandatory} if ${translated}`
334+
})
335+
336+
// join translated parts with 'otherwise'
337+
let result = ''
338+
for (let i = 0; i < translatedParts.length; i++) {
339+
let prefix = i === 0 ? '' : ', otherwise it is '
340+
result += `${prefix}${translatedParts[i]}`
341+
}
342+
// if the last part is not a conformance tag, fall back to not supported
343+
if (!translateConformanceTag(parts[parts.length - 1])) {
344+
result += ', otherwise it is not supported'
345+
}
346+
return result
347+
}
348+
241349
exports.evaluateConformanceExpression = evaluateConformanceExpression
242350
exports.checkMissingOperands = checkMissingOperands
243351
exports.checkIfExpressionHasOperand = checkIfExpressionHasOperand
244352
exports.checkFeaturesToUpdate = checkFeaturesToUpdate
245353
exports.filterRelatedDescElements = filterRelatedDescElements
246354
exports.getOperandsFromExpression = getOperandsFromExpression
355+
exports.translateConformanceExpression = translateConformanceExpression

src-electron/validation/conformance-xml-parser.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ function parseConformanceRecursively(operand, depth = 0, parentJoinChar = '') {
148148
} else if (operand.deprecateConform) {
149149
return dbEnum.conformanceTag.deprecated
150150
} else if (operand.describedConform) {
151-
return dbEnum.conformanceTag.desc
151+
return dbEnum.conformanceTag.described
152152
} else {
153153
// reach base level terms, return the name directly
154154
for (const term of baseLevelTerms) {
@@ -157,7 +157,7 @@ function parseConformanceRecursively(operand, depth = 0, parentJoinChar = '') {
157157
}
158158
}
159159
// reaching here means the term is too complex to parse
160-
return dbEnum.conformanceTag.desc
160+
return dbEnum.conformanceTag.described
161161
}
162162
}
163163

src-shared/db-enum.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -253,20 +253,29 @@ exports.featureMapAttribute = {
253253
exports.conformanceTag = {
254254
mandatory: 'M',
255255
optional: 'O',
256+
provisional: 'P',
256257
disallowed: 'X',
257258
deprecated: 'D',
258-
provisional: 'P',
259-
desc: 'desc'
259+
described: 'desc'
260260
}
261261

262262
exports.conformanceVal = {
263263
mandatory: 'mandatory',
264264
optional: 'optional',
265-
notSupported: 'notSupported',
266-
provisional: 'provisional'
265+
provisional: 'provisional',
266+
disallowed: 'disallowed',
267+
deprecated: 'deprecated',
268+
described: 'described conformance that cannot be displayed as an expression',
269+
notSupported: 'notSupported'
267270
}
268271

269272
exports.clusterSide = {
270273
client: 'client',
271274
server: 'server'
272275
}
276+
277+
exports.logicalOperators = {
278+
and: 'and',
279+
or: 'or',
280+
not: 'not'
281+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<!--
2+
Copyright (c) 2008,2020 Silicon Labs.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
-->
16+
17+
<template>
18+
<q-tr :props="props">
19+
<q-th v-for="col in props.cols" :key="col.name" :props="props">
20+
<div>
21+
<template v-if="col.name === 'conformance'">
22+
<a
23+
:href="documentSource"
24+
class="cursor-pointer"
25+
target="_blank"
26+
style="text-decoration: none; color: black"
27+
>
28+
{{ col.label }}
29+
<q-icon
30+
name="info"
31+
class="q-pl-sm q-pb-xs"
32+
style="font-size: 1rem"
33+
color="primary"
34+
>
35+
<q-tooltip>
36+
{{ conformanceSourceTip }}
37+
</q-tooltip>
38+
</q-icon>
39+
</a>
40+
</template>
41+
<template v-else>
42+
{{ col.label }}
43+
</template>
44+
</div>
45+
</q-th>
46+
</q-tr>
47+
</template>
48+
49+
<script>
50+
import featureMixin from '../util/feature-mixin'
51+
52+
export default {
53+
name: 'FeatureTableHeader',
54+
mixins: [featureMixin],
55+
props: {
56+
props: { type: Object, required: true }
57+
}
58+
}
59+
</script>

src/components/ZclClusterFeatureManager.vue

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,7 @@ limitations under the License.
2929
id="ZclClusterFeatureManager"
3030
>
3131
<template v-slot:header="props">
32-
<q-tr :props="props">
33-
<q-th v-for="col in props.cols" :key="col.name" :props="props">
34-
{{ col.label }}
35-
</q-th>
36-
</q-tr>
32+
<FeatureTableHeader :props="props" />
3733
</template>
3834
<template v-slot:body="props">
3935
<q-tr :props="props" class="table_body" data-test="feature-row">
@@ -67,6 +63,9 @@ limitations under the License.
6763
data-test="feature-conformance"
6864
>
6965
{{ props.row.conformance }}
66+
<q-tooltip>
67+
{{ props.row.translation }}
68+
</q-tooltip>
7069
</q-td>
7170
<q-td key="bit" :props="props" auto-width data-test="feature-bit">
7271
{{ props.row.bit }}
@@ -170,11 +169,12 @@ import featureMixin from '../util/feature-mixin.js'
170169
import uiOptions from '../util/ui-options'
171170
import CommonMixin from '../util/common-mixin'
172171
import dbEnum from '../../src-shared/db-enum'
172+
import FeatureTableHeader from './FeatureTableHeader.vue'
173173
174174
export default {
175175
name: 'ZclClusterFeatureManager',
176176
mixins: [EditableAttributesMixin, uiOptions, CommonMixin, featureMixin],
177-
computed: {},
177+
components: { FeatureTableHeader },
178178
methods: {
179179
isToggleDisabled(conformance) {
180180
// disable toggling features with unsupported conformance

0 commit comments

Comments
 (0)