Skip to content

Commit d714f45

Browse files
author
Dennis Labordus
authored
Merge pull request #203 from com-pas/edit-element-from-diagnostic
Processed XPath Expression returned from CoMPAS Validation Service.
2 parents 23e57b4 + 289028e commit d714f45

File tree

7 files changed

+356
-6
lines changed

7 files changed

+356
-6
lines changed

src/Logging.ts

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,13 @@ import {
3232
LogEvent,
3333
Mixin,
3434
newActionEvent,
35+
newWizardEvent,
36+
SCLTag,
3537
} from './foundation.js';
3638
import { getFilterIcon, iconColors } from './icons/icons.js';
3739
import { Plugin } from './Plugging.js';
40+
import { wizards } from './wizards/wizard-library.js';
41+
import { nothing } from 'lit-html';
3842

3943
const icons = {
4044
info: 'info',
@@ -235,13 +239,38 @@ export function Logging<TBase extends LitElementConstructor>(Base: TBase) {
235239
</mwc-list-item>`;
236240
}
237241

242+
private openEditWizard(element: Element | undefined): void {
243+
if (element) {
244+
const wizard = wizards[<SCLTag>element.tagName]?.edit(element);
245+
if (wizard) this.dispatchEvent(newWizardEvent(wizard));
246+
}
247+
}
248+
249+
private hasEditWizard(element: Element | undefined): boolean {
250+
if (element) {
251+
return !!wizards[<SCLTag>element.tagName]?.edit(element);
252+
}
253+
return false;
254+
}
255+
238256
private renderIssueEntry(issue: IssueDetail): TemplateResult {
239-
return html` <abbr title="${issue.title + '\n' + issue.message}"
240-
><mwc-list-item ?twoline=${!!issue.message}>
241-
<span> ${issue.title}</span>
257+
return html` <abbr title="${issue.title + '\n' + issue.message}">
258+
<mwc-list-item
259+
?twoline=${!!issue.message}
260+
?hasMeta=${this.hasEditWizard(issue.element)}
261+
>
262+
<span>${issue.title}</span>
242263
<span slot="secondary">${issue.message}</span>
243-
</mwc-list-item></abbr
244-
>`;
264+
${this.hasEditWizard(issue.element)
265+
? html` <span slot="meta">
266+
<mwc-icon-button
267+
icon="edit"
268+
@click=${() => this.openEditWizard(issue.element)}
269+
></mwc-icon-button>
270+
</span>`
271+
: nothing}
272+
</mwc-list-item>
273+
</abbr>`;
245274
}
246275

247276
renderValidatorsIssues(issues: IssueDetail[]): TemplateResult[] {
@@ -329,6 +358,10 @@ export function Logging<TBase extends LitElementConstructor>(Base: TBase) {
329358
top: 14px;
330359
right: 14px;
331360
}
361+
362+
#diagnostic mwc-list-item {
363+
--mdc-list-item-meta-size: 48px;
364+
}
332365
</style>
333366
<mwc-dialog id="log" heading="${translate('log.name')}">
334367
${this.renderFilterButtons()}

src/foundation.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,7 @@ export function newLogEvent(
401401

402402
export interface IssueDetail extends LogDetailBase {
403403
validatorId: string;
404+
element?: Element;
404405
}
405406
export type IssueEvent = CustomEvent<IssueDetail>;
406407
export function newIssueEvent(

src/validators/CompasValidateSchema.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export default class CompasValidateSchema extends LitElement {
6969
validatorId: this.pluginId,
7070
title: this.createTitle(validationError),
7171
message: this.createMessage(validationError),
72+
element: this.getElement(validationError),
7273
})
7374
);
7475
});
@@ -92,15 +93,92 @@ export default class CompasValidateSchema extends LitElement {
9293
const columnNumber = validationError
9394
.getElementsByTagNameNS(SVS_NAMESPACE, 'ColumnNumber')!
9495
.item(0)?.textContent;
96+
const xpath = validationError
97+
.getElementsByTagNameNS(SVS_NAMESPACE, 'XPath')!
98+
.item(0)?.textContent;
9599

96100
const messageParts: string[] = [];
97101
if (ruleName) messageParts.push(`Rule: ${ruleName}`);
98102
if (lineNumber) messageParts.push(`Line: ${lineNumber}`);
99103
if (columnNumber) messageParts.push(`Column: ${columnNumber}`);
104+
if (xpath) messageParts.push(`XPath: ${xpath}`);
100105

101106
if (messageParts.length == 0) {
102107
return undefined;
103108
}
104109
return messageParts.join(', ');
105110
}
111+
112+
private getElement(validationError: Element): Element | undefined {
113+
const xpath = validationError
114+
.getElementsByTagNameNS(SVS_NAMESPACE, 'XPath')!
115+
.item(0)?.textContent;
116+
117+
if (xpath) {
118+
const fixedXPath = this.rewriteXPathForDefaultNamespace(xpath);
119+
const nsResolver = this.doc.createNSResolver(this.doc.documentElement);
120+
const result = this.doc.evaluate(
121+
fixedXPath,
122+
this.doc,
123+
this.createResolver(nsResolver),
124+
XPathResult.FIRST_ORDERED_NODE_TYPE,
125+
null
126+
);
127+
if (result.singleNodeValue) {
128+
return <Element>result.singleNodeValue;
129+
}
130+
}
131+
return undefined;
132+
}
133+
134+
135+
/**
136+
* For XPath to work correctly the default namespace of SCL needs to have a prefix to work.
137+
* The function evaluate expects HTML to be the default namespace.
138+
*
139+
* See https://developer.mozilla.org/en-US/docs/Web/XPath/Introduction_to_using_XPath_in_JavaScript
140+
* for more information on how XPath is working with evalaute and why this fix is needed.
141+
*
142+
* @param xpath - The XPath to rewrite to use a prefix 'scl' for the SCL Namespace.
143+
*/
144+
private rewriteXPathForDefaultNamespace(xpath: string): string {
145+
return (
146+
'/' +
147+
xpath
148+
.split('/')
149+
.filter(part => !!part)
150+
.map(part => {
151+
if (part && part.indexOf(':') < 0) {
152+
return 'scl:' + part;
153+
}
154+
return part;
155+
})
156+
.join('/')
157+
);
158+
}
159+
160+
/**
161+
* Create a XPath Namespace Resolver to handle the default and other custom namespaces.
162+
*
163+
* @param nsResolver - The automatically created namespace resolver.
164+
*/
165+
private createResolver(nsResolver: any): XPathNSResolver {
166+
return {
167+
lookupNamespaceURI(prefix: string | null): string | null {
168+
// Handle the fix default namespace.
169+
if (prefix === 'scl') {
170+
return 'http://www.iec.ch/61850/2003/SCL';
171+
}
172+
// Use the automatically created resolver for other prefixes.
173+
// The resolver can be used in two different ways.
174+
if (typeof nsResolver === 'function') {
175+
return nsResolver(prefix);
176+
}
177+
if (typeof nsResolver.lookupNamespaceURI === 'function') {
178+
return nsResolver.lookupNamespaceURI(prefix);
179+
}
180+
return null;
181+
},
182+
};
183+
}
106184
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<SCL version="2007" revision="B" release="4"
3+
xmlns="http://www.iec.ch/61850/2003/SCL"
4+
xmlns:compas="https://www.lfenergy.org/compas/extension/v1">
5+
<Private type="compas_scl">
6+
<compas:SclName>existing</compas:SclName>
7+
</Private>
8+
<Header id="TrainingIEC61850" version="1" revision="143" toolID="IEC 61850 System Configurator, Version: V5.90 " nameStructure="IEDName">
9+
<Text>TrainingIEC61850</Text>
10+
<History>
11+
<Hitem version="1" revision="143" when="Wednesday, September 25, 2019 9:11:36 AM" who="Licenced User: OMICRON electronics GmbH JakVog00 Machine: JAKVOG00LW01 User: JakVog00" what="Station is upgraded from IEC 61850 System Configurator, Version: V5.80 HF1 to V5.90 ." why="IEC 61850 System Configurator Automatic Startup: Insert Comment" />
12+
</History>
13+
</Header>
14+
<Substation name="AA1" desc="Substation">
15+
<VoltageLevel name="J1" desc="Voltage Level">
16+
<Voltage unit="V" multiplier="k">20</Voltage>
17+
<Bay name="Bay1" desc="Bay1">
18+
</Bay>
19+
</VoltageLevel>
20+
</Substation>
21+
</SCL>

test/unit/Logging.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,5 +278,43 @@ describe('LoggingElement', () => {
278278
expect(issue.title).to.equal('test run 3');
279279
});
280280
});
281+
282+
describe('with a CoMPAS issue coming in - CoMPAS validator', () => {
283+
let substation: Element;
284+
beforeEach(async () => {
285+
const doc = await fetch('/test/testfiles/valid2007B4.scd')
286+
.then(response => response.text())
287+
.then(str => new DOMParser().parseFromString(str, 'application/xml'));
288+
substation = doc.querySelector('Substation')!;
289+
290+
element.dispatchEvent(
291+
newIssueEvent({
292+
validatorId: '/src/validators/CompasValidateSchema.js',
293+
title: 'CoMPAS Run',
294+
element: substation
295+
})
296+
);
297+
});
298+
299+
it('in parallel saves the issues of the CoMPAS validator', () => {
300+
expect(element.diagnoses.get('/src/validators/CompasValidateSchema.js')).to
301+
.exist;
302+
expect(
303+
element.diagnoses.get('/src/validators/CompasValidateSchema.js')!.length
304+
).to.equal(1);
305+
const issue = element.diagnoses.get(
306+
'/src/validators/CompasValidateSchema.js'
307+
)![0];
308+
expect(issue.title).to.equal('CoMPAS Run');
309+
expect(issue.element).to.equal(substation);
310+
});
311+
312+
it('diagnostic dialog looks like the latest snapshot', async () => {
313+
await element.issueUI.querySelector('mwc-button')!.click();
314+
await element.updateComplete;
315+
316+
await expect(element.diagnosticUI).to.equalSnapshot();
317+
});
318+
});
281319
});
282320
});
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/* @web/test-runner snapshot v1 */
2+
export const snapshots = {};
3+
4+
snapshots["LoggingElement with an issue incoming with a CoMPAS issue coming in - CoMPAS validator diagnostic dialog looks like the latest snapshot"] =
5+
`<mwc-dialog
6+
heading="[diag.name]"
7+
id="diagnostic"
8+
open=""
9+
>
10+
<filtered-list
11+
id="content"
12+
wrapfocus=""
13+
>
14+
<mwc-list-item
15+
aria-disabled="false"
16+
noninteractive=""
17+
tabindex="-1"
18+
>
19+
/src/validators/ValidateSchema.js
20+
</mwc-list-item>
21+
<li
22+
divider=""
23+
padded=""
24+
role="separator"
25+
>
26+
</li>
27+
<abbr title="test run 1
28+
undefined">
29+
<mwc-list-item
30+
aria-disabled="false"
31+
mwc-list-item=""
32+
tabindex="-1"
33+
>
34+
<span>
35+
test run 1
36+
</span>
37+
<span slot="secondary">
38+
</span>
39+
</mwc-list-item>
40+
</abbr>
41+
<mwc-list-item
42+
aria-disabled="false"
43+
noninteractive=""
44+
tabindex="-1"
45+
>
46+
/src/validators/CompasValidateSchema.js
47+
</mwc-list-item>
48+
<li
49+
divider=""
50+
padded=""
51+
role="separator"
52+
>
53+
</li>
54+
<abbr title="CoMPAS Run
55+
undefined">
56+
<mwc-list-item
57+
aria-disabled="false"
58+
hasmeta=""
59+
mwc-list-item=""
60+
tabindex="-1"
61+
>
62+
<span>
63+
CoMPAS Run
64+
</span>
65+
<span slot="secondary">
66+
</span>
67+
<span slot="meta">
68+
<mwc-icon-button icon="edit">
69+
</mwc-icon-button>
70+
</span>
71+
</mwc-list-item>
72+
</abbr>
73+
</filtered-list>
74+
<mwc-button
75+
dialogaction="close"
76+
slot="primaryAction"
77+
>
78+
[close]
79+
</mwc-button>
80+
</mwc-dialog>
81+
`;
82+
/* end snapshot LoggingElement with an issue incoming with a CoMPAS issue coming in - CoMPAS validator diagnostic dialog looks like the latest snapshot */
83+

0 commit comments

Comments
 (0)