Skip to content

Commit 57c184c

Browse files
authored
Merge pull request #133 from com-pas/load_nsdoc_backend
Automatically Load NSDoc Files from SCL Validator Service
2 parents bb69fdc + 0c98f4b commit 57c184c

File tree

17 files changed

+1532
-144
lines changed

17 files changed

+1532
-144
lines changed

src/Logging.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,8 +245,8 @@ export function Logging<TBase extends LitElementConstructor>(Base: TBase) {
245245

246246
this.undo = this.undo.bind(this);
247247
this.redo = this.redo.bind(this);
248-
249248
this.onLog = this.onLog.bind(this);
249+
250250
this.addEventListener('log', this.onLog);
251251
this.addEventListener('issue', this.onIssue);
252252
this.addEventListener('open-doc', this.onLoadHistoryFromDoc);

src/Setting.ts

Lines changed: 47 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,23 @@ type NsdVersions = {
5454
'IEC 61850-8-1': NsdVersion;
5555
}
5656

57+
/** Represents a document to be opened. */
58+
export interface LoadNsdocDetail {
59+
nsdoc: string;
60+
filename: string;
61+
}
62+
export type LoadNsdocEvent = CustomEvent<LoadNsdocDetail>;
63+
export function newLoadNsdocEvent(
64+
nsdoc: string,
65+
filename: string
66+
): LoadNsdocEvent {
67+
return new CustomEvent<LoadNsdocDetail>('load-nsdoc', {
68+
bubbles: true,
69+
composed: true,
70+
detail: { nsdoc, filename },
71+
});
72+
}
73+
5774
/** Mixin that saves [[`Settings`]] to `localStorage`, reflecting them in the
5875
* `settings` property, setting them through `setSetting(setting, value)`. */
5976
export type SettingElement = Mixin<typeof Setting>;
@@ -165,64 +182,44 @@ export function Setting<TBase extends LitElementConstructor>(Base: TBase) {
165182
if (changedProperties.has('settings')) use(this.settings.language);
166183
}
167184

168-
private renderFileSelect(): TemplateResult {
169-
return html `
170-
<input id="nsdoc-file" accept=".nsdoc" type="file" hidden required multiple
171-
@change=${(evt: Event) => this.loadNsdocFile(evt)}}>
172-
<mwc-button label="${translate('settings.selectFileButton')}"
173-
id="selectFileButton"
174-
@click=${() => {
175-
const input = <HTMLInputElement | null>this.shadowRoot!.querySelector("#nsdoc-file");
176-
input?.click();
177-
}}>
178-
</mwc-button>
179-
`;
180-
}
185+
private async onLoadNsdoc(event: LoadNsdocEvent) {
186+
const nsdocElement = this.parseToXmlObject(event.detail.nsdoc).querySelector('NSDoc');
181187

182-
private async loadNsdocFile(evt: Event): Promise<void> {
183-
const nsdVersions = await this.nsdVersions();
184-
const files = Array.from(
185-
(<HTMLInputElement | null>evt.target)?.files ?? []
186-
);
187-
188-
if (files.length == 0) return;
189-
files.forEach(async file => {
190-
const text = await file.text();
191-
const nsdocElement = this.parseToXmlObject(text).querySelector('NSDoc');
192-
const id = nsdocElement?.getAttribute('id');
193-
if (!id) {
194-
document
188+
const id = nsdocElement?.getAttribute('id');
189+
if (!id) {
190+
document
195191
.querySelector('open-scd')!
196192
.dispatchEvent(
197-
newLogEvent({ kind: 'error', title: get('settings.invalidFileNoIdFound') })
198-
);
199-
return;
200-
}
201-
const nsdVersion = nsdVersions[id as keyof NsdVersions];
202-
const nsdocVersion = {
203-
version: nsdocElement!.getAttribute('version') ?? '',
204-
revision: nsdocElement!.getAttribute('revision') ?? '',
205-
release: nsdocElement!.getAttribute('release') ?? ''
206-
}
193+
newLogEvent({ kind: 'error', title: get('settings.invalidFileNoIdFound', {
194+
filename: event.detail.filename
195+
}) })
196+
);
197+
return;
198+
}
207199

208-
if (!this.isEqual(nsdVersion, nsdocVersion)) {
209-
document
200+
const nsdVersions = await this.nsdVersions();
201+
const nsdVersion = nsdVersions[id as keyof NsdVersions];
202+
const nsdocVersion = {
203+
version: nsdocElement!.getAttribute('version') ?? '',
204+
revision: nsdocElement!.getAttribute('revision') ?? '',
205+
release: nsdocElement!.getAttribute('release') ?? ''
206+
}
207+
208+
if (!this.isEqual(nsdVersion, nsdocVersion)) {
209+
document
210210
.querySelector('open-scd')!
211211
.dispatchEvent(
212-
newLogEvent({ kind: 'error', title: get('settings.invalidNsdocVersion', {
212+
newLogEvent({ kind: 'error', title: get('settings.invalidNsdocVersion', {
213213
id: id,
214+
filename: event.detail.filename,
214215
nsdVersion: `${nsdVersion.version}${nsdVersion.revision}${nsdVersion.release}`,
215216
nsdocVersion: `${nsdocVersion.version}${nsdocVersion.revision}${nsdocVersion.release}`
216217
}) })
217-
);
218-
return;
219-
}
220-
221-
this.setSetting(id as keyof Settings, text);
222-
})
218+
);
219+
return;
220+
}
223221

224-
this.nsdocFileUI.value = '';
225-
this.requestUpdate();
222+
this.setSetting(id as keyof Settings, event.detail.nsdoc);
226223
}
227224

228225
/**
@@ -245,7 +242,7 @@ export function Setting<TBase extends LitElementConstructor>(Base: TBase) {
245242
let nsdVersion: string | undefined | null;
246243
let nsdRevision: string | undefined | null;
247244
let nsdRelease: string | undefined | null;
248-
245+
249246
if (nsdSetting) {
250247
const nsdoc = this.parseToXmlObject(nsdSetting)!.querySelector('NSDoc');
251248
nsdVersion = nsdoc?.getAttribute('version');
@@ -273,6 +270,8 @@ export function Setting<TBase extends LitElementConstructor>(Base: TBase) {
273270

274271
registerTranslateConfig({ loader, empty: key => key });
275272
use(this.settings.language);
273+
274+
(<any>this).addEventListener('load-nsdoc', this.onLoadNsdoc);
276275
}
277276

278277
render(): TemplateResult {
@@ -321,7 +320,6 @@ export function Setting<TBase extends LitElementConstructor>(Base: TBase) {
321320
<wizard-divider></wizard-divider>
322321
<section>
323322
<h3>${translate('settings.loadNsdTranslations')}</h3>
324-
${this.renderFileSelect()}
325323
</section>
326324
<mwc-list id="nsdocList">
327325
${this.renderNsdocItem('IEC 61850-7-2')}

src/compas-services/CompasValidatorService.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ export function CompasSclValidatorService() {
1010

1111
return {
1212
validateSCL(type: string, doc: Document): Promise<Document> {
13-
const saaUrl = getCompasSettings().sclValidatorServiceUrl + '/validate/v1/' + type;
14-
return fetch(saaUrl, {
13+
const svsUrl = getCompasSettings().sclValidatorServiceUrl + '/validate/v1/' + type;
14+
return fetch(svsUrl, {
1515
method: 'POST',
1616
headers: {
1717
'Content-Type': 'application/xml'
@@ -24,5 +24,18 @@ export function CompasSclValidatorService() {
2424
.then(handleResponse)
2525
.then(parseXml);
2626
},
27+
28+
listNsdocFiles(): Promise<Document> {
29+
const svsUrl = getCompasSettings().sclValidatorServiceUrl + '/nsdoc/v1';
30+
return fetch(svsUrl).catch(handleError)
31+
.then(handleResponse)
32+
.then(parseXml);
33+
},
34+
35+
getNsdocFile(id: string): Promise<string> {
36+
const svsUrl = getCompasSettings().sclValidatorServiceUrl + '/nsdoc/v1/' + id;
37+
return fetch(svsUrl).catch(handleError)
38+
.then(handleResponse);
39+
},
2740
}
2841
}

src/compas/CompasNsdoc.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import {newLoadNsdocEvent} from "../Setting.js";
2+
import {dispatchEventOnOpenScd} from "./foundation.js";
3+
4+
import {createLogEvent} from "../compas-services/foundation.js";
5+
import {CompasSclValidatorService} from "../compas-services/CompasValidatorService.js";
6+
7+
/**
8+
* Load a single entry. Use the nsdocId to look in the Local Storage, if already loaded,
9+
* and if the checksum is the same.
10+
* If one of them isn't the case use the ID to retrieve the content of the NSDoc File and
11+
* fire the #newLoadNsdocEvent to add the content to the Local Storage.
12+
*
13+
* @param id - A unique id use to retrieve the content of NSDoc File from the SCL Validator Service.
14+
* @param nsdocId - The NSDoc ID to be used in the Local Storage.
15+
* @param filename - The name of the file, just for logging.
16+
* @param checksum - The checksum of the NSDoc File in the SCL Validator Service.
17+
*/
18+
async function processNsdocFile(id: string, nsdocId: string, filename: string, checksum: string): Promise<void> {
19+
const checksumKey = nsdocId + '.checksum';
20+
const checksumStored = localStorage.getItem(checksumKey);
21+
if (localStorage.getItem(nsdocId) === null || checksumStored === null || checksumStored !== checksum) {
22+
console.info(`Loading NSDoc File '${nsdocId}' with ID '${id}'.`);
23+
await CompasSclValidatorService().getNsdocFile(id)
24+
.then(nsdocContent => {
25+
dispatchEventOnOpenScd(newLoadNsdocEvent(nsdocContent, filename));
26+
localStorage.setItem(checksumKey, checksum);
27+
})
28+
.catch(reason => {
29+
createLogEvent(reason);
30+
});
31+
} else {
32+
console.debug(`Loading NSDoc File '${nsdocId}' skipped, already loaded.`);
33+
}
34+
}
35+
36+
/**
37+
* Call backend to get the list of available NSDoc Files on the SCL Validator Service.
38+
* Load each item found using the function #processNsdocFile.
39+
*/
40+
export async function loadNsdocFiles(): Promise<void> {
41+
await CompasSclValidatorService().listNsdocFiles()
42+
.then(response => {
43+
Array.from(response.querySelectorAll("NsdocFile") ?? [])
44+
.forEach(element => {
45+
const id = element.querySelector('Id')!.textContent ?? '';
46+
const nsdocId = element.querySelector('NsdocId')!.textContent ?? '';
47+
const filename = element.querySelector('Filename')!.textContent ?? '';
48+
const checksum = element.querySelector('Checksum')!.textContent ?? '';
49+
processNsdocFile(id, nsdocId, filename, checksum);
50+
});
51+
})
52+
.catch(reason => {
53+
createLogEvent(reason);
54+
});
55+
}

src/menu/CompasSettings.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {newWizardEvent, Wizard, WizardInput} from '../foundation.js';
55

66
import {CompasSettingsElement} from "../compas/CompasSettings.js";
77
import {retrieveUserInfo} from "../compas/CompasSession.js";
8+
import {loadNsdocFiles} from "../compas/CompasNsdoc.js";
89

910
import "../compas/CompasSettings.js";
1011

@@ -42,4 +43,6 @@ export function compasSettingWizard(): Wizard {
4243

4344
// When the plugin is loaded we will also start retrieving the User Information and prepare the Timeout Panels.
4445
retrieveUserInfo();
46+
// And we will start loading the Nsdoc Files from the Compas Backend Service.
47+
loadNsdocFiles();
4548

src/translations/de.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,9 @@ export const de: Translations = {
7070
showieds: 'Zeige IEDs im Substation-Editor',
7171
selectFileButton: 'Datei auswählen',
7272
loadNsdTranslations: 'NSDoc-Dateien hochladen',
73-
invalidFileNoIdFound:
74-
"Ungültiges NSDoc; kein 'id'-Attribut in der Datei gefunden",
73+
invalidFileNoIdFound: "Ungültiges NSDoc ({{ filename }}); kein 'id'-Attribut in der Datei gefunden",
7574
invalidNsdocVersion:
76-
'Die Version {{ id }} NSD ({{ nsdVersion }}) passt nicht zu der geladenen NSDoc ({{ nsdocVersion }})',
75+
'Die Version {{ id }} NSD ({{ nsdVersion }}) passt nicht zu der geladenen NSDoc ({{ filename }}, {{ nsdocVersion }})',
7776
},
7877
menu: {
7978
new: 'Neues projekt',

src/translations/en.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,10 @@ export const en = {
6767
mode: 'Pro mode',
6868
showieds: 'Show IEDs in substation editor',
6969
selectFileButton: 'Select file',
70-
loadNsdTranslations: 'Uploading NSDoc files',
71-
invalidFileNoIdFound: "Invalid NSDoc; no 'id' attribute found in file",
70+
loadNsdTranslations: 'Uploaded NSDoc files',
71+
invalidFileNoIdFound: "Invalid NSDoc ({{ filename }}); no 'id' attribute found in file",
7272
invalidNsdocVersion:
73-
'The version of {{ id }} NSD ({{ nsdVersion }}) does not correlate with the version of the corresponding NSDoc ({{ nsdocVersion }})',
73+
'The version of {{ id }} NSD ({{ nsdVersion }}) does not correlate with the version of the corresponding NSDoc ({{ filename }}, {{ nsdocVersion }})',
7474
},
7575
menu: {
7676
new: 'New project',

test/integration/Setting.test.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { html, fixture, expect } from '@open-wc/testing';
2+
3+
import '../../src/open-scd.js';
4+
5+
import { OpenSCD } from '../../src/open-scd.js';
6+
import { newLoadNsdocEvent } from "../../src/Setting.js";
7+
8+
describe('Setting', () => {
9+
let element: OpenSCD;
10+
11+
beforeEach(async () => {
12+
localStorage.clear();
13+
14+
element = await fixture(html`
15+
<open-scd></open-scd>
16+
17+
<link href="public/google/fonts/roboto-v27.css" rel="stylesheet" />
18+
<link href="public/google/fonts/roboto-mono-v13.css" rel="stylesheet" />
19+
<link href="public/google/icons/material-icons-outlined.css" rel="stylesheet" />
20+
`);
21+
});
22+
23+
it('opens the log on log menu entry click', async () => {
24+
await (<HTMLElement>(
25+
element.shadowRoot!.querySelector('mwc-list-item[iconid="history"]')!
26+
)).click();
27+
expect(element.logUI).to.have.property('open', true);
28+
});
29+
30+
it('upload .nsdoc file using event and looks like latest snapshot', async () => {
31+
element.settingsUI.show();
32+
await element.settingsUI.updateComplete;
33+
34+
const nsdocFile = await fetch('/test/testfiles/nsdoc/IEC_61850-7-2.nsdoc')
35+
.then(response => response.text())
36+
37+
element.dispatchEvent(
38+
newLoadNsdocEvent(nsdocFile, 'IEC_61850-7-2.nsdoc')
39+
);
40+
41+
await element.requestUpdate();
42+
await element.updateComplete;
43+
44+
expect(localStorage.getItem('IEC 61850-7-2')).to.eql(nsdocFile);
45+
expect(element).shadowDom.to.equalSnapshot();
46+
});
47+
48+
it('upload invalid .nsdoc file using event and log event fired', async () => {
49+
element.settingsUI.show();
50+
await element.settingsUI.updateComplete;
51+
52+
const nsdocFile = await fetch('/test/testfiles/nsdoc/invalid.nsdoc')
53+
.then(response => response.text())
54+
55+
element.dispatchEvent(
56+
newLoadNsdocEvent(nsdocFile, 'invalid.nsdoc')
57+
);
58+
59+
await element.requestUpdate();
60+
await element.updateComplete;
61+
62+
expect(element.history.length).to.be.equal(1);
63+
expect(element.history[0].title).to.be.equal('Invalid NSDoc (invalid.nsdoc); no \'id\' attribute found in file');
64+
});
65+
66+
it('upload .nsdoc file with wrong version using event and log event fired', async () => {
67+
element.settingsUI.show();
68+
await element.settingsUI.updateComplete;
69+
70+
const nsdocFile = await fetch('/test/testfiles/nsdoc/wrong-version.nsdoc')
71+
.then(response => response.text())
72+
73+
element.dispatchEvent(
74+
newLoadNsdocEvent(nsdocFile, 'wrong-version.nsdoc')
75+
);
76+
77+
await element.requestUpdate();
78+
await element.updateComplete;
79+
80+
expect(element.history.length).to.be.equal(1);
81+
expect(element.history[0].title).to.be.equal('The version of IEC 61850-7-3 NSD (2007B3) does not correlate ' +
82+
'with the version of the corresponding NSDoc (wrong-version.nsdoc, 2007B4)');
83+
});
84+
}).timeout(4000);

0 commit comments

Comments
 (0)