From 8bdd990a1d0c77b50743281d71b61489709e433a Mon Sep 17 00:00:00 2001 From: danyill Date: Fri, 14 Jul 2023 08:55:50 +1200 Subject: [PATCH] fix(menu/importIEDs): Allow importing multiple IEDs from multiple SCD files (#1222) * Initial refactoring to functions * Allow consistent import of IEDs (closes #1106) * Tidy up * Tidying * More tidying * Remove nullish coalescing where not required --- src/menu/ImportIEDs.ts | 91 +++++++++++-------- .../triggered/ImportIedsPlugin.test.ts | 83 +++++------------ .../{dublicate.iid => duplicate.iid} | 0 3 files changed, 75 insertions(+), 99 deletions(-) rename test/testfiles/importieds/{dublicate.iid => duplicate.iid} (100%) diff --git a/src/menu/ImportIEDs.ts b/src/menu/ImportIEDs.ts index 439cdd89cb..bb7ef95a16 100644 --- a/src/menu/ImportIEDs.ts +++ b/src/menu/ImportIEDs.ts @@ -23,7 +23,6 @@ import { isPublic, newActionEvent, newLogEvent, - newPendingStateEvent, selector, SimpleAction, } from '../foundation.js'; @@ -368,12 +367,6 @@ function isIedNameUnique(ied: Element, doc: Document): boolean { return true; } -function resetSelection(dialog: Dialog): void { - ( - (dialog.querySelector('filtered-list') as List).selected as ListItemBase[] - ).forEach(item => (item.selected = false)); -} - export default class ImportingIedPlugin extends LitElement { @property({ attribute: false }) doc!: XMLDocument; @@ -381,12 +374,13 @@ export default class ImportingIedPlugin extends LitElement { editCount = -1; @state() - importDoc?: XMLDocument; + iedSelection: TemplateResult[] = []; @query('#importied-plugin-input') pluginFileUI!: HTMLInputElement; @query('mwc-dialog') dialog!: Dialog; async run(): Promise { + this.iedSelection = []; this.pluginFileUI.click(); } @@ -423,6 +417,7 @@ export default class ImportingIedPlugin extends LitElement { // This doesn't provide redo/undo capability as it is not using the Editing // action API. To use it would require us to cache the full SCL file in // OpenSCD as it is now which could use significant memory. + // TODO: In open-scd core update this to allow including in undo/redo. updateNamespaces( this.doc.documentElement, @@ -447,19 +442,25 @@ export default class ImportingIedPlugin extends LitElement { ); } - private async importIEDs(): Promise { + private async importIEDs( + importDoc: XMLDocument, + fileName: string + ): Promise { + const documentDialog: Dialog = this.shadowRoot!.querySelector( + `mwc-dialog[data-file="${fileName}"]` + )!; + const selectedItems = ( - (this.dialog.querySelector('filtered-list')).selected + (documentDialog.querySelector('filtered-list')).selected ); const ieds = selectedItems .map(item => { - return this.importDoc!.querySelector(selector('IED', item.value)); + return importDoc!.querySelector(selector('IED', item.value)); }) .filter(ied => ied) as Element[]; - resetSelection(this.dialog); - this.dialog.close(); + documentDialog.close(); for (const ied of ieds) { this.importIED(ied); @@ -467,8 +468,8 @@ export default class ImportingIedPlugin extends LitElement { } } - public prepareImport(): void { - if (!this.importDoc) { + async prepareImport(importDoc: XMLDocument, fileName: string): Promise { + if (!importDoc) { this.dispatchEvent( newLogEvent({ kind: 'error', @@ -478,7 +479,7 @@ export default class ImportingIedPlugin extends LitElement { return; } - if (this.importDoc.querySelector('parsererror')) { + if (importDoc.querySelector('parsererror')) { this.dispatchEvent( newLogEvent({ kind: 'error', @@ -488,7 +489,7 @@ export default class ImportingIedPlugin extends LitElement { return; } - const ieds = Array.from(this.importDoc.querySelectorAll(':root > IED')); + const ieds = Array.from(importDoc.querySelectorAll(':root > IED')); if (ieds.length === 0) { this.dispatchEvent( newLogEvent({ @@ -501,10 +502,23 @@ export default class ImportingIedPlugin extends LitElement { if (ieds.length === 1) { this.importIED(ieds[0]); - return; + return await this.docUpdate(); } - this.dialog.show(); + this.buildIedSelection(importDoc, fileName); + await this.requestUpdate(); + const dialog = ( + this.shadowRoot!.querySelector(`mwc-dialog[data-file="${fileName}"]`) + ); + dialog.show(); + + // await closing of dialog + await new Promise(resolve => { + dialog.addEventListener('closed', function onClosed(evt) { + evt.target?.removeEventListener('closed', onClosed); + resolve(); + }); + }); } /** Loads the file `event.target.files[0]` into [[`src`]] as a `blob:...`. */ @@ -513,23 +527,20 @@ export default class ImportingIedPlugin extends LitElement { (event.target)?.files ?? [] ); - const promises = files.map(async file => { - this.importDoc = new DOMParser().parseFromString( - await file.text(), - 'application/xml' - ); - - return this.prepareImport(); + const promises = files.map(file => { + return { + text: file + .text() + .then(text => + new DOMParser().parseFromString(text, 'application/xml') + ), + name: file.name, + }; }); - const mergedPromise = new Promise((resolve, reject) => - Promise.allSettled(promises).then( - () => resolve(), - () => reject() - ) - ); - - this.dispatchEvent(newPendingStateEvent(mergedPromise)); + for await (const file of promises) { + await this.prepareImport(await file.text, file.name); + } } protected renderInput(): TemplateResult { @@ -539,10 +550,10 @@ export default class ImportingIedPlugin extends LitElement { }} id="importied-plugin-input" accept=".sed,.scd,.ssd,.iid,.cid,.icd" type="file">`; } - protected renderIedSelection(): TemplateResult { - return html` + protected buildIedSelection(importDoc: XMLDocument, fileName: string): void { + this.iedSelection.push(html` - ${Array.from(this.importDoc?.querySelectorAll(':root > IED') ?? []).map( + ${Array.from(importDoc?.querySelectorAll(':root > IED') ?? []).map( ied => html`${ied.getAttribute('name')} this.importIEDs(importDoc, fileName)} > - `; + `); } render(): TemplateResult { - return html`${this.renderIedSelection()}${this.renderInput()}`; + return html`${this.iedSelection}${this.renderInput()}`; } static styles = css` diff --git a/test/integration/editors/triggered/ImportIedsPlugin.test.ts b/test/integration/editors/triggered/ImportIedsPlugin.test.ts index f3219099d2..7143a722df 100644 --- a/test/integration/editors/triggered/ImportIedsPlugin.test.ts +++ b/test/integration/editors/triggered/ImportIedsPlugin.test.ts @@ -41,7 +41,6 @@ describe('ImportIedsPlugin', () => { importDoc = await fetch('/test/testfiles/importieds/valid.iid') .then(response => response.text()) .then(str => new DOMParser().parseFromString(str, 'application/xml')); - element.importDoc = importDoc; await element.updateComplete; }); @@ -49,7 +48,7 @@ describe('ImportIedsPlugin', () => { expect(element.doc?.querySelector(':root > IED[name="TestImportIED"]')).to .not.exist; - element.prepareImport(); + element.prepareImport(importDoc, 'valid.iid'); await parent.updateComplete; expect(element.doc?.querySelector(':root > IED[name="TestImportIED"]')).to @@ -57,7 +56,7 @@ describe('ImportIedsPlugin', () => { }); it('adds the connectedap of the imported ied', async () => { - element.prepareImport(); + element.prepareImport(importDoc, 'valid.iid'); await parent.updateComplete; expect( @@ -71,7 +70,7 @@ describe('ImportIedsPlugin', () => { expect(element.doc.querySelector('SubNetwork[name="NewSubNetwork"]')).to .not.exist; - element.prepareImport(); + element.prepareImport(importDoc, 'valid.iid'); await parent.updateComplete; expect(element.doc.querySelector('SubNetwork[name="NewSubNetwork"]')).to @@ -87,10 +86,9 @@ describe('ImportIedsPlugin', () => { ied.setAttribute('manufacturer', 'Fancy-Vendy'); ied.setAttribute('type', 'Z#Mega$Y'); - element.importDoc = importDoc; await element.updateComplete; - element.prepareImport(); + element.prepareImport(importDoc, 'template.icd'); await parent.updateComplete; console.log( @@ -108,11 +106,7 @@ describe('ImportIedsPlugin', () => { ) .then(response => response.text()) .then(str => new DOMParser().parseFromString(str, 'application/xml')); - - element.importDoc = templateIED1; - await element.updateComplete; - - element.prepareImport(); + element.prepareImport(templateIED1, 'template.icd'); const templateIED2 = await fetch( '/test/testfiles/importieds/template.icd' @@ -120,10 +114,9 @@ describe('ImportIedsPlugin', () => { .then(response => response.text()) .then(str => new DOMParser().parseFromString(str, 'application/xml')); - element.importDoc = templateIED2; await element.updateComplete; - element.prepareImport(); + element.prepareImport(templateIED2, 'template.icd'); await parent.updateComplete; expect(element.doc.querySelector('IED[name="FancyVendy_ZMegaY_001"]')).to @@ -138,10 +131,7 @@ describe('ImportIedsPlugin', () => { ) .then(response => response.text()) .then(str => new DOMParser().parseFromString(str, 'application/xml')); - element.importDoc = templateIED1; - await element.updateComplete; - - element.prepareImport(); + element.prepareImport(templateIED1, 'template.icd'); await parent.updateComplete; expect( @@ -157,7 +147,7 @@ describe('ImportIedsPlugin', () => { .length ).to.equal(0); - element.prepareImport(); + element.prepareImport(importDoc, 'template.icd'); await parent.updateComplete; expect( @@ -189,7 +179,6 @@ describe('ImportIedsPlugin', () => { .then(response => response.text()) .then(str => new DOMParser().parseFromString(str, 'application/xml')); - element.importDoc = importDoc; await element.updateComplete; }); @@ -197,7 +186,7 @@ describe('ImportIedsPlugin', () => { expect(element.doc?.querySelector(':root > IED[name="TestImportIED"]')).to .not.exist; - element.prepareImport(); + element.prepareImport(importDoc, 'valid.iid'); await parent.updateComplete; expect(element.doc?.querySelector(':root > IED[name="TestImportIED"]')).to @@ -210,7 +199,7 @@ describe('ImportIedsPlugin', () => { .length ).to.equal(11); - element.prepareImport(); + element.prepareImport(importDoc, 'valid.iid'); await parent.updateComplete; expect( @@ -225,7 +214,7 @@ describe('ImportIedsPlugin', () => { .length ).to.equal(16); - element.prepareImport(); + element.prepareImport(importDoc, 'valid.iid'); await parent.updateComplete; expect( @@ -240,7 +229,7 @@ describe('ImportIedsPlugin', () => { .length ).to.equal(7); - element.prepareImport(); + element.prepareImport(importDoc, 'valid.iid'); await parent.updateComplete; expect( @@ -255,7 +244,7 @@ describe('ImportIedsPlugin', () => { .length ).to.equal(4); - element.prepareImport(); + element.prepareImport(importDoc, 'valid.iid'); await parent.updateComplete; expect( @@ -268,7 +257,7 @@ describe('ImportIedsPlugin', () => { expect(element.doc.querySelector('ConnectedAP[iedName="TestImportIED"]')) .to.not.exist; - element.prepareImport(); + element.prepareImport(importDoc, 'valid.iid'); await parent.updateComplete; expect(element.doc.querySelector('ConnectedAP[iedName="TestImportIED"]')) @@ -283,7 +272,7 @@ describe('ImportIedsPlugin', () => { expect(element.doc.querySelector('SubNetwork[name="NewSubNetwork"]')).to .not.exist; - element.prepareImport(); + element.prepareImport(importDoc, 'valid.iid'); await parent.updateComplete; expect(element.doc.querySelector('SubNetwork[name="NewSubNetwork"]')).to @@ -291,7 +280,7 @@ describe('ImportIedsPlugin', () => { }); it('correctly transfers document element namespaces', async () => { - element.prepareImport(); + element.prepareImport(importDoc, 'valid.iid'); await parent.updateComplete; expect( @@ -328,22 +317,14 @@ describe('ImportIedsPlugin', () => { ) .then(response => response.text()) .then(str => new DOMParser().parseFromString(str, 'application/xml')); - - element.importDoc = templateIED1; - await element.updateComplete; - - element.prepareImport(); + element.prepareImport(templateIED1, 'template.icd'); const templateIED2 = await fetch( '/test/testfiles/importieds/template.icd' ) .then(response => response.text()) .then(str => new DOMParser().parseFromString(str, 'application/xml')); - - element.importDoc = templateIED2; - await element.updateComplete; - - element.prepareImport(); + element.prepareImport(templateIED2, 'template.icd'); await parent.updateComplete; expect(element.doc.querySelector('IED[name="FancyVendy_ZMegaY_001"]')).to @@ -359,10 +340,7 @@ describe('ImportIedsPlugin', () => { .then(response => response.text()) .then(str => new DOMParser().parseFromString(str, 'application/xml')); - element.importDoc = multipleIedDoc; - await element.updateComplete; - - element.prepareImport(); + element.prepareImport(multipleIedDoc, 'multipleied.scd'); await element.updateComplete; expect(element.dialog).to.exist; @@ -379,10 +357,7 @@ describe('ImportIedsPlugin', () => { .then(response => response.text()) .then(str => new DOMParser().parseFromString(str, 'application/xml')); - element.importDoc = multipleIedDoc; - await element.updateComplete; - - element.prepareImport(); + element.prepareImport(multipleIedDoc, 'multipleied.scd'); await element.updateComplete; (( @@ -436,25 +411,17 @@ describe('ImportIedsPlugin', () => { importDoc = await fetch('/test/testfiles/importieds/invalid.iid') .then(response => response.text()) .then(str => new DOMParser().parseFromString(str, 'application/xml')); - - element.importDoc = importDoc; - await element.updateComplete; - - element.prepareImport(); + element.prepareImport(importDoc, 'invalid.iid'); expect(parent.history[0].kind).to.equal('error'); expect(parent.history[0].title).to.equal('[import.log.missingied]'); }); it('throws duplicate ied name error', async () => { - importDoc = await fetch('/test/testfiles/importieds/dublicate.iid') + importDoc = await fetch('/test/testfiles/importieds/duplicate.iid') .then(response => response.text()) .then(str => new DOMParser().parseFromString(str, 'application/xml')); - - element.importDoc = importDoc; - await element.updateComplete; - - element.prepareImport(); + element.prepareImport(importDoc, 'duplicate.iid'); expect(parent.history[0].kind).to.equal('error'); expect(parent.history[0].title).to.equal('[import.log.nouniqueied]'); @@ -464,11 +431,9 @@ describe('ImportIedsPlugin', () => { importDoc = await fetch('/test/testfiles/importieds/parsererror.iid') .then(response => response.text()) .then(str => new DOMParser().parseFromString(str, 'application/xml')); - - element.importDoc = importDoc; await element.updateComplete; - element.prepareImport(); + element.prepareImport(importDoc, 'parsererror.iid'); expect(parent.history[0].kind).to.equal('error'); expect(parent.history[0].title).to.equal('[import.log.parsererror]'); diff --git a/test/testfiles/importieds/dublicate.iid b/test/testfiles/importieds/duplicate.iid similarity index 100% rename from test/testfiles/importieds/dublicate.iid rename to test/testfiles/importieds/duplicate.iid