Skip to content

Commit a3aecf7

Browse files
feat(menu/UpdateDescritionABB): update ABB ExtRef with internal signal description (#374)
* fix(foundation): extRefIdentity definition * feat(menu/UpdateDescriptionABB): add with unit tests * test(menu/UpdateDescriptionABB): add integration tests * refactor(menu/UpdateDescriptionABB): add translations * test(menu/UpdateDescriptionABB): improve unit tests * refactor(menu/updatedescABB): minor improvement triggered by review * refactor(menu/updatedescABB): minor improvement triggered by review
1 parent b4c69c5 commit a3aecf7

File tree

10 files changed

+461
-4
lines changed

10 files changed

+461
-4
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# `Update method for desc attributes in ABB IEDs`
2+
3+
## `working on SCL files without manufacturer ABB`
4+
5+
#### `creates an empty wizard indicating not found desc updates`
6+
7+
```html
8+
<mwc-dialog
9+
defaultaction="close"
10+
heading="[wizard.title.add]"
11+
open=""
12+
>
13+
<div id="wizard-content">
14+
<filtered-list multi="">
15+
</filtered-list>
16+
</div>
17+
<mwc-button
18+
dialogaction="close"
19+
label="[cancel]"
20+
slot="secondaryAction"
21+
style="--mdc-theme-primary: var(--mdc-theme-error)"
22+
>
23+
</mwc-button>
24+
<mwc-button
25+
dialoginitialfocus=""
26+
icon="save"
27+
label="[save]"
28+
slot="primaryAction"
29+
trailingicon=""
30+
>
31+
</mwc-button>
32+
</mwc-dialog>
33+
34+
```
35+
36+
## `working on SCL files containing manufacturer ABB`
37+
38+
#### `creates a wizard with all valid desc update possibilities`
39+
40+
```html
41+
<mwc-dialog
42+
defaultaction="close"
43+
heading="[wizard.title.add]"
44+
open=""
45+
>
46+
<div id="wizard-content">
47+
<filtered-list multi="">
48+
<mwc-check-list-item
49+
aria-disabled="false"
50+
graphic="control"
51+
mwc-list-item=""
52+
selected=""
53+
tabindex="0"
54+
twoline=""
55+
value="ExtRef | IED1>>Disconnectors>DC CSWI 1>IED2 CBSW/ XSWI 2 Pos stVal@01-0C-CD-01-00-01,0001,5,GOOSERCV_BIN.3.I1,400,0,GOOSERCV_BIN,Dynamic"
56+
>
57+
<span>
58+
GOOSERCV_BIN.3.I1
59+
</span>
60+
<span slot="secondary">
61+
ExtRef | IED1>>Disconnectors>DC CSWI 1>IED2 CBSW/ XSWI 2 Pos stVal@01-0C-CD-01-00-01,0001,5,GOOSERCV_BIN.3.I1,400,0,GOOSERCV_BIN,Dynamic
62+
</span>
63+
</mwc-check-list-item>
64+
<mwc-check-list-item
65+
aria-disabled="false"
66+
graphic="control"
67+
mwc-list-item=""
68+
selected=""
69+
tabindex="-1"
70+
twoline=""
71+
value="ExtRef | IED1>>Disconnectors>DC CSWI 1>IED2 CBSW/ XSWI 2 Pos q@01-0C-CD-01-00-01,0001,5,GOOSERCV_BIN.3.I2,400,0,GOOSERCV_BIN,Dynamic"
72+
>
73+
<span>
74+
some desc-GOOSERCV_BIN.3.I2
75+
</span>
76+
<span slot="secondary">
77+
ExtRef | IED1>>Disconnectors>DC CSWI 1>IED2 CBSW/ XSWI 2 Pos q@01-0C-CD-01-00-01,0001,5,GOOSERCV_BIN.3.I2,400,0,GOOSERCV_BIN,Dynamic
78+
</span>
79+
</mwc-check-list-item>
80+
</filtered-list>
81+
</div>
82+
<mwc-button
83+
dialogaction="close"
84+
label="[cancel]"
85+
slot="secondaryAction"
86+
style="--mdc-theme-primary: var(--mdc-theme-error)"
87+
>
88+
</mwc-button>
89+
<mwc-button
90+
dialoginitialfocus=""
91+
icon="save"
92+
label="[save]"
93+
slot="primaryAction"
94+
trailingicon=""
95+
>
96+
</mwc-button>
97+
</mwc-dialog>
98+
99+
```
100+

__snapshots__/open-scd.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,21 @@
754754
</mwc-icon>
755755
Subscriber Update
756756
</mwc-check-list-item>
757+
<mwc-check-list-item
758+
aria-disabled="false"
759+
class="official"
760+
graphic="control"
761+
hasmeta=""
762+
left=""
763+
mwc-list-item=""
764+
tabindex="-1"
765+
value="/src/menu/UpdateDescriptionABB.js"
766+
>
767+
<mwc-icon slot="meta">
768+
play_circle
769+
</mwc-icon>
770+
Update desc (ABB)
771+
</mwc-check-list-item>
757772
<mwc-check-list-item
758773
aria-disabled="false"
759774
class="official"

public/js/plugins.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,14 @@ export const officialPlugins = [
7979
requireDoc: true,
8080
position: 'middle'
8181
},
82+
{
83+
name: 'Update desc (ABB)',
84+
src: '/src/menu/UpdateDescriptionABB.js',
85+
default: false,
86+
kind: 'menu',
87+
requireDoc: true,
88+
position: 'middle'
89+
},
8290
{
8391
name: 'Merge Project',
8492
src: '/src/menu/Merge.js',

src/foundation.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -678,14 +678,14 @@ function extRefIdentity(e: Element): string | number {
678678
].map(name => e.getAttribute(name));
679679

680680
const cbPath = srcCBName
681-
? `${serviceType}:${srcCBName} ${srcLDInst ?? ''}/${
682-
srcPrefix ?? ''
683-
} ${srcLNClass} ${srcLNInst ?? ''}`
681+
? `${serviceType}:${srcCBName} ${srcLDInst ?? ''}/${srcPrefix ?? ''} ${
682+
srcLNClass ?? ''
683+
} ${srcLNInst ?? ''}`
684684
: '';
685685
const dataPath = `${iedName} ${ldInst}/${prefix ?? ''} ${lnClass} ${
686686
lnInst ?? ''
687687
} ${doName} ${daName ? daName : ''}`;
688-
return `${parentIdentity}>${cbPath} ${dataPath}${
688+
return `${parentIdentity}>${cbPath ? cbPath + ' ' : ''}${dataPath}${
689689
intAddr ? '@' + `${intAddr}` : ''
690690
}`;
691691
}

src/menu/UpdateDescriptionABB.ts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { html, LitElement } from 'lit-element';
2+
import { get } from 'lit-translate';
3+
4+
import {
5+
cloneElement,
6+
identity,
7+
isPublic,
8+
newWizardEvent,
9+
SCLTag,
10+
selector,
11+
Wizard,
12+
WizardAction,
13+
WizardActor,
14+
WizardInput,
15+
} from '../foundation.js';
16+
17+
import { List } from '@material/mwc-list';
18+
import { ListItemBase } from '@material/mwc-list/mwc-list-item-base';
19+
20+
interface addDescItem {
21+
desc: string;
22+
tag: SCLTag;
23+
identity: string | number;
24+
}
25+
26+
function addDescriptionAction(doc: XMLDocument): WizardActor {
27+
return (
28+
_: WizardInput[],
29+
wizard: Element,
30+
list: List | null | undefined
31+
): WizardAction[] => {
32+
const selectedItems = <ListItemBase[]>list!.selected;
33+
34+
const actions = selectedItems.map(item => {
35+
const desc = (<Element>item.querySelector('span')).textContent;
36+
const [tag, identity] = item.value.split(' | ');
37+
38+
const oldElement = doc.querySelector(selector(tag, identity))!;
39+
const newElement = cloneElement(oldElement, { desc });
40+
return { old: { element: oldElement }, new: { element: newElement } };
41+
});
42+
43+
return [
44+
{
45+
title: get('updatedesc.abb'),
46+
actions,
47+
},
48+
];
49+
};
50+
}
51+
52+
function createLogWizard(doc: XMLDocument, items: addDescItem[]): Wizard {
53+
return [
54+
{
55+
title: get('wizard.title.add', { tagName: 'desc' }),
56+
primary: {
57+
label: get('save'),
58+
icon: 'save',
59+
action: addDescriptionAction(doc),
60+
},
61+
content: [
62+
html`<filtered-list multi
63+
>${Array.from(
64+
items.map(
65+
item =>
66+
html`<mwc-check-list-item
67+
twoline
68+
selected
69+
value="${item.tag + ' | ' + item.identity}"
70+
><span>${item.desc}</span
71+
><span slot="secondary"
72+
>${item.tag + ' | ' + item.identity}</span
73+
></mwc-check-list-item
74+
>`
75+
)
76+
)}</filtered-list
77+
>`,
78+
],
79+
},
80+
];
81+
}
82+
83+
function addDescriptionToABB(ied: Element): addDescItem[] {
84+
return Array.from(ied.getElementsByTagName('ExtRef'))
85+
.filter(element => isPublic(element))
86+
.filter(extRef => extRef.getAttribute('intAddr'))
87+
.map(extRef => {
88+
const intAddr = extRef.getAttribute('intAddr')!;
89+
const internalMapping = intAddr.split(',')[3]; //this might change as is vendor dependant!!
90+
const oldDesc = extRef.getAttribute('desc');
91+
const newDesc = oldDesc
92+
? oldDesc + '-' + internalMapping
93+
: internalMapping;
94+
95+
return {
96+
desc: newDesc,
97+
tag: <SCLTag>'ExtRef',
98+
identity: identity(extRef),
99+
};
100+
});
101+
}
102+
103+
/** Plug-in that enriched ExtRefs desc attribute based on intAddr attribute (ABB)*/
104+
export default class UpdateDescriptionAbb extends LitElement {
105+
/** The document being edited as provided to plugins by [[`OpenSCD`]]. */
106+
doc!: XMLDocument;
107+
108+
/** Entry point for this plug-in */
109+
async run(): Promise<void> {
110+
const items = Array.from(this.doc.querySelectorAll(':root > IED')).flatMap(
111+
ied => addDescriptionToABB(ied)
112+
);
113+
114+
this.dispatchEvent(newWizardEvent(createLogWizard(this.doc, items)));
115+
}
116+
}

src/translations/de.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,9 @@ export const de: Translations = {
366366
code: {
367367
log: 'Element im XML Editor angepasst: {{id}}',
368368
},
369+
updatedesc: {
370+
abb: 'Signalbeschreibungen zu ABB IEDs hinzugefügt',
371+
},
369372
add: 'Hinzufügen',
370373
new: 'Neu',
371374
remove: 'Entfernen',

src/translations/en.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,9 @@ export const en = {
363363
code: {
364364
log: 'Changed element in XML editor: {{id}}',
365365
},
366+
updatedesc: {
367+
abb: 'Added signal descriptions to ABB IEDs',
368+
},
366369
add: 'Add',
367370
new: 'New',
368371
remove: 'Remove',
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { expect, fixture, html } from '@open-wc/testing';
2+
3+
import '../../mock-wizard-editor.js';
4+
import UpdateDescriptionAbb from '../../../src/menu/UpdateDescriptionABB.js';
5+
import { MockWizardEditor } from '../../mock-wizard-editor.js';
6+
7+
describe('Update method for desc attributes in ABB IEDs', () => {
8+
if (customElements.get('update-description-abb') === undefined)
9+
customElements.define('update-description-abb', UpdateDescriptionAbb);
10+
11+
let parent: MockWizardEditor;
12+
let element: UpdateDescriptionAbb;
13+
let doc: XMLDocument;
14+
15+
beforeEach(async () => {
16+
doc = await fetch('/base/test/testfiles/updatedesc/updatedescABB.scd')
17+
.then(response => response.text())
18+
.then(str => new DOMParser().parseFromString(str, 'application/xml'));
19+
20+
parent = await fixture(html`
21+
<mock-wizard-editor
22+
><update-description-abb></update-description-abb
23+
></mock-wizard-editor>
24+
`);
25+
26+
element = <UpdateDescriptionAbb>(
27+
parent.querySelector('update-description-abb')!
28+
);
29+
30+
element.doc = doc;
31+
element.run();
32+
await parent.requestUpdate();
33+
});
34+
35+
it('creates desc attributes for ExtRef elements with existing intAddr attribute', async () => {
36+
expect(
37+
doc.querySelector(
38+
'IED[name="IED1"] LN[lnClass="CSWI"][inst="1"] ExtRef:not([desc])'
39+
)
40+
).to.exist;
41+
parent.wizardUI?.dialog
42+
?.querySelector<HTMLElement>('mwc-button[slot="primaryAction"]')!
43+
.click();
44+
await parent.updateComplete;
45+
expect(
46+
doc.querySelector(
47+
'IED[name="IED1"] LN[lnClass="CSWI"][inst="1"] ExtRef[desc="GOOSERCV_BIN.3.I1"]'
48+
)
49+
).to.exist;
50+
});
51+
52+
it('does not create desc attributes for ExtRef elements with missing intAddr attribute', async () => {
53+
expect(
54+
doc.querySelector(
55+
'IED[name="IED1"] LN[lnClass="CSWI"][inst="2"] ExtRef:not([desc])'
56+
)
57+
).to.exist;
58+
parent.wizardUI?.dialog
59+
?.querySelector<HTMLElement>('mwc-button[slot="primaryAction"]')!
60+
.click();
61+
await parent.updateComplete;
62+
expect(
63+
doc.querySelector(
64+
'IED[name="IED1"] LN[lnClass="CSWI"][inst="2"] ExtRef:not([desc])'
65+
)
66+
).to.exist;
67+
});
68+
});

0 commit comments

Comments
 (0)