From 3ad17c053f86efc3885ec995995309207acd1b0b Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Thu, 8 Aug 2019 16:50:53 -0700 Subject: [PATCH 01/27] SHIBUI-1361 Added script to verify display date format are parseable valid dates by core JavaScript. Possible regex testing to come. Also includes minor script updates to 1334, 1335 scripts. --- .../admin/ui/SeleniumSIDETest.groovy | 1 + .../integration/resources/SHIBUI-1334-2.side | 24 +- .../integration/resources/SHIBUI-1335-1.side | 7 + .../integration/resources/SHIBUI-1361.side | 1184 +++++++++++++++++ 4 files changed, 1204 insertions(+), 12 deletions(-) create mode 100644 backend/src/integration/resources/SHIBUI-1361.side diff --git a/backend/src/integration/groovy/edu/internet2/tier/shibboleth/admin/ui/SeleniumSIDETest.groovy b/backend/src/integration/groovy/edu/internet2/tier/shibboleth/admin/ui/SeleniumSIDETest.groovy index e61c9dc26..a579a0543 100644 --- a/backend/src/integration/groovy/edu/internet2/tier/shibboleth/admin/ui/SeleniumSIDETest.groovy +++ b/backend/src/integration/groovy/edu/internet2/tier/shibboleth/admin/ui/SeleniumSIDETest.groovy @@ -119,6 +119,7 @@ class SeleniumSIDETest extends Specification { 'SHIBUI-1335: Verify Local Dynamic Metadata Provider Filters' | '/SHIBUI-1335-3.side' 'SHIBUI-1335: Verify Dynamic HTTP Metadata Provider Filters' | '/SHIBUI-1335-4.side' 'SHIBUI-1392: Verify provider with script filter is persistable' | '/SHIBUI-1392.side' + 'SHIBUI-1361: Verify dates display in proper format' | '/SHIBUI-1361.side' } } diff --git a/backend/src/integration/resources/SHIBUI-1334-2.side b/backend/src/integration/resources/SHIBUI-1334-2.side index db9a5f632..e20c9ba3d 100644 --- a/backend/src/integration/resources/SHIBUI-1334-2.side +++ b/backend/src/integration/resources/SHIBUI-1334-2.side @@ -693,10 +693,10 @@ "id": "68103f0e-e3f1-419b-903d-5d8c30bc6700", "comment": "", "command": "verifyText", - "target": "css=.d-block > primitive-property:nth-child(2) .d-block:nth-child(2)", + "target": "css=.d-block > primitive-property:nth-child(4) .d-block:nth-child(2)", "targets": [ - ["css=.d-block > primitive-property:nth-child(2) .d-block:nth-child(2)", "css:finder"], - ["xpath=//primitive-property[2]/div/span[2]", "xpath:position"], + ["css=.d-block > primitive-property:nth-child(4) .d-block:nth-child(2)", "css:finder"], + ["xpath=//primitive-property[4]/div/span[2]", "xpath:position"], ["xpath=//span[contains(.,'123 v2')]", "xpath:innerText"] ], "value": "123 v2" @@ -704,30 +704,30 @@ "id": "a13198b9-7827-47e2-ade3-143b981d34ee", "comment": "", "command": "verifyText", - "target": "css=.d-block > primitive-property:nth-child(2) .d-block:nth-child(3)", + "target": "css=.d-block > primitive-property:nth-child(4) .d-block:nth-child(3)", "targets": [ - ["css=.d-block > primitive-property:nth-child(2) .d-block:nth-child(3)", "css:finder"], - ["xpath=//primitive-property[2]/div/span[3]", "xpath:position"] + ["css=.d-block > primitive-property:nth-child(4) .d-block:nth-child(3)", "css:finder"], + ["xpath=//primitive-property[4]/div/span[3]", "xpath:position"] ], "value": "123" }, { "id": "3d83741c-299e-4fcb-9ce6-4b7291c423a4", "comment": "", "command": "verifyText", - "target": "css=.d-block > primitive-property:nth-child(10) .d-block:nth-child(2)", + "target": "css=.d-block > primitive-property:nth-child(12) .d-block:nth-child(2)", "targets": [ - ["css=.d-block > primitive-property:nth-child(10) .d-block:nth-child(2)", "css:finder"], - ["xpath=//primitive-property[10]/div/span[2]", "xpath:position"] + ["css=.d-block > primitive-property:nth-child(12) .d-block:nth-child(2)", "css:finder"], + ["xpath=//primitive-property[12]/div/span[2]", "xpath:position"] ], "value": "false" }, { "id": "190cc157-d427-4046-aff2-180392395ad3", "comment": "", "command": "verifyText", - "target": "css=.d-block > primitive-property:nth-child(10) .d-block:nth-child(3)", + "target": "css=.d-block > primitive-property:nth-child(12) .d-block:nth-child(3)", "targets": [ - ["css=.d-block > primitive-property:nth-child(10) .d-block:nth-child(3)", "css:finder"], - ["xpath=//primitive-property[10]/div/span[3]", "xpath:position"] + ["css=.d-block > primitive-property:nth-child(12) .d-block:nth-child(3)", "css:finder"], + ["xpath=//primitive-property[12]/div/span[3]", "xpath:position"] ], "value": "true" }, { diff --git a/backend/src/integration/resources/SHIBUI-1335-1.side b/backend/src/integration/resources/SHIBUI-1335-1.side index 1af0bba89..d9a790f3c 100644 --- a/backend/src/integration/resources/SHIBUI-1335-1.side +++ b/backend/src/integration/resources/SHIBUI-1335-1.side @@ -117,6 +117,13 @@ ["xpath=//li[2]/button", "xpath:position"] ], "value": "" + }, { + "id": "7d9109bb-f3b5-4d74-864e-5c647e90c9e4", + "comment": "", + "command": "waitForElementVisible", + "target": "id=/xmlId", + "targets": [], + "value": "10000" }, { "id": "e7462621-f4e4-4512-b200-20afa5685fc6", "comment": "", diff --git a/backend/src/integration/resources/SHIBUI-1361.side b/backend/src/integration/resources/SHIBUI-1361.side new file mode 100644 index 000000000..2248d351a --- /dev/null +++ b/backend/src/integration/resources/SHIBUI-1361.side @@ -0,0 +1,1184 @@ +{ + "id": "6c70c319-3e73-4a13-8ee7-d9da5f802b83", + "version": "2.0", + "name": "SHIBUI-1361", + "url": "http://localhost:10101", + "tests": [{ + "id": "1e7a4ca4-b2e6-4e5a-8e38-823ae8a1e20d", + "name": "SHIBUI-1361", + "commands": [{ + "id": "29bb3186-81c8-4b3e-a9d9-5ff55d225878", + "comment": "", + "command": "open", + "target": "/login", + "targets": [], + "value": "" + }, { + "id": "302b949f-39b8-4060-813b-d827cf62dff4", + "comment": "", + "command": "type", + "target": "name=username", + "targets": [ + ["name=username", "name"], + ["css=tr:nth-child(1) input", "css:finder"], + ["xpath=//input[@name='username']", "xpath:attributes"], + ["xpath=//input", "xpath:position"] + ], + "value": "admin" + }, { + "id": "a7910a51-e40e-4cdb-a72e-5f2cfd1b3656", + "comment": "", + "command": "type", + "target": "name=password", + "targets": [ + ["name=password", "name"], + ["css=tr:nth-child(2) input", "css:finder"], + ["xpath=//input[@name='password']", "xpath:attributes"], + ["xpath=//tr[2]/td[2]/input", "xpath:position"] + ], + "value": "adminpass" + }, { + "id": "6636b5f6-2b97-4be9-9fc7-44d6b48b8abf", + "comment": "", + "command": "sendKeys", + "target": "name=password", + "targets": [ + ["name=password", "name"], + ["css=tr:nth-child(2) input", "css:finder"], + ["xpath=//input[@name='password']", "xpath:attributes"], + ["xpath=//tr[2]/td[2]/input", "xpath:position"] + ], + "value": "${KEY_ENTER}" + }, { + "id": "e7a98419-36c7-4b0f-a99d-087144f6a417", + "comment": "", + "command": "click", + "target": "css=.fa-plus-circle", + "targets": [ + ["css=.fa-plus-circle", "css:finder"], + ["xpath=//button[@id='addNewDropdown']/i", "xpath:idRelative"], + ["xpath=//i", "xpath:position"] + ], + "value": "" + }, { + "id": "74fef022-3979-4bed-9226-b25114514976", + "comment": "", + "command": "click", + "target": "linkText=Metadata Provider", + "targets": [ + ["linkText=Metadata Provider", "linkText"], + ["css=.nav-link:nth-child(2)", "css:finder"], + ["xpath=//div[@id='navbar']/ul/li/div/a[2]", "xpath:idRelative"], + ["xpath=(//a[contains(@href, '')])[3]", "xpath:href"], + ["xpath=//a[2]", "xpath:position"] + ], + "value": "" + }, { + "id": "b49d6ed7-75ea-4be8-9a79-c7fd83287aa0", + "comment": "", + "command": "click", + "target": "id=/name", + "targets": [ + ["id=/name", "id"], + ["name=field1", "name"], + ["css=#\\/name", "css:finder"], + ["xpath=//input[@id='/name']", "xpath:attributes"], + ["xpath=//input", "xpath:position"] + ], + "value": "" + }, { + "id": "54dc29f7-bbe0-4c2b-a76e-e67a61f57a96", + "comment": "", + "command": "type", + "target": "id=/name", + "targets": [ + ["id=/name", "id"], + ["name=field1", "name"], + ["css=#\\/name", "css:finder"], + ["xpath=//input[@id='/name']", "xpath:attributes"], + ["xpath=//input", "xpath:position"] + ], + "value": "Test Metadata Provider" + }, { + "id": "add874d5-e3b2-4412-8039-2b9d2619ed19", + "comment": "", + "command": "select", + "target": "id=/type", + "targets": [ + ["css=select-component > .widget", "css:finder"], + ["xpath=//select-component/div", "xpath:position"] + ], + "value": "label=FileBackedHttpMetadataProvider" + }, { + "id": "1cc352f4-0d71-4a3e-9399-1d9932816efc", + "comment": "", + "command": "click", + "target": "css=.next", + "targets": [ + ["css=.next", "css:finder"], + ["xpath=//li[2]/button", "xpath:position"] + ], + "value": "" + }, { + "id": "79f8f28b-8bcf-464a-850f-eb10552a9390", + "comment": "", + "command": "waitForElementVisible", + "target": "id=/xmlId", + "targets": [], + "value": "10000" + }, { + "id": "86262eda-4a44-41b0-b7aa-fa46406e2601", + "comment": "", + "command": "type", + "target": "id=/xmlId", + "targets": [ + ["id=field7", "id"], + ["name=field7", "name"], + ["css=#field7", "css:finder"], + ["xpath=//input[@id='field7']", "xpath:attributes"], + ["xpath=//input", "xpath:position"] + ], + "value": "123" + }, { + "id": "26cc765d-98c1-414c-97d8-4c7e5c6ab7e2", + "comment": "", + "command": "type", + "target": "id=/metadataURL", + "targets": [ + ["id=field8", "id"], + ["name=field8", "name"], + ["css=#field8", "css:finder"], + ["xpath=//input[@id='field8']", "xpath:attributes"], + ["xpath=//div[2]/sf-form-element/div/sf-widget-chooser/custom-string/div/input", "xpath:position"] + ], + "value": "https://idp.unicon.net/idp/shibboleth" + }, { + "id": "f0459917-b2a1-4e3c-8800-25273965129f", + "comment": "", + "command": "click", + "target": "id=/initializeFromBackupFile.false", + "targets": [ + ["id=/initializeFromBackupFile.false", "id"], + ["css=#\\/initializeFromBackupFile\\.false", "css:finder"], + ["xpath=//input[@id='/initializeFromBackupFile.false']", "xpath:attributes"], + ["xpath=//div[2]/label/input", "xpath:position"] + ], + "value": "" + }, { + "id": "5b37cf08-77b9-4091-94f9-c0d539c9010d", + "comment": "", + "command": "type", + "target": "id=/backingFile", + "targets": [ + ["id=/backingFile", "id"], + ["name=field10", "name"], + ["css=#\\/backingFile", "css:finder"], + ["xpath=//input[@id='/backingFile']", "xpath:attributes"], + ["xpath=//div[4]/sf-form-element/div/sf-widget-chooser/custom-string/div/input", "xpath:position"] + ], + "value": "%{idp.home}/foo.txt" + }, { + "id": "203d0d3c-d866-422a-8b48-b50181db00e9", + "comment": "", + "command": "click", + "target": "css=.fa-caret-down", + "targets": [ + ["css=.fa-caret-down", "css:finder"], + ["xpath=//div[@id='/backupFileInitNextRefreshDelay-container']/div/div/button/i", "xpath:idRelative"], + ["xpath=//div/button/i", "xpath:position"] + ], + "value": "" + }, { + "id": "5fc6a348-0533-4d94-bb76-ebb891a0db8f", + "comment": "", + "command": "click", + "target": "id=/backupFileInitNextRefreshDelay__option--1", + "targets": [ + ["id=/backupFileInitNextRefreshDelay__option--1", "id"], + ["css=#\\/backupFileInitNextRefreshDelay__option--1", "css:finder"], + ["xpath=//li[@id='/backupFileInitNextRefreshDelay__option--1']", "xpath:attributes"], + ["xpath=//ul[@id='/backupFileInitNextRefreshDelay__listbox']/li[2]", "xpath:idRelative"], + ["xpath=//auto-complete/div/ul/li[2]", "xpath:position"], + ["xpath=//li[contains(.,'PT30S')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "bf041d41-5d58-4f69-8fcc-49ec79e5547d", + "comment": "", + "command": "click", + "target": "id=/requireValidMetadata.false", + "targets": [ + ["id=/requireValidMetadata.false", "id"], + ["css=#\\/requireValidMetadata\\.false", "css:finder"], + ["xpath=//input[@id='/requireValidMetadata.false']", "xpath:attributes"], + ["xpath=//div[6]/sf-form-element/div/sf-widget-chooser/boolean-radio/div/div[2]/label/input", "xpath:position"] + ], + "value": "" + }, { + "id": "fba85c75-e218-4deb-b5e1-888ff75b6a4e", + "comment": "", + "command": "click", + "target": "id=/failFastInitialization.false", + "targets": [ + ["id=/failFastInitialization.false", "id"], + ["css=#\\/failFastInitialization\\.false", "css:finder"], + ["xpath=//input[@id='/failFastInitialization.false']", "xpath:attributes"], + ["xpath=//div[7]/sf-form-element/div/sf-widget-chooser/boolean-radio/div/div[2]/label/input", "xpath:position"] + ], + "value": "" + }, { + "id": "375b8d6e-179b-4f14-8f00-d8a8cad29d7e", + "comment": "", + "command": "click", + "target": "id=/useDefaultPredicateRegistry.false", + "targets": [ + ["id=/useDefaultPredicateRegistry.false", "id"], + ["css=#\\/useDefaultPredicateRegistry\\.false", "css:finder"], + ["xpath=//input[@id='/useDefaultPredicateRegistry.false']", "xpath:attributes"], + ["xpath=//div[8]/sf-form-element/div/sf-widget-chooser/boolean-radio/div/div[2]/label/input", "xpath:position"] + ], + "value": "" + }, { + "id": "8b4bf341-62c0-491f-b775-d2faf55477bc", + "comment": "", + "command": "click", + "target": "id=/satisfyAnyPredicates.true", + "targets": [ + ["id=/satisfyAnyPredicates.true", "id"], + ["css=#\\/satisfyAnyPredicates\\.true", "css:finder"], + ["xpath=//input[@id='/satisfyAnyPredicates.true']", "xpath:attributes"], + ["xpath=//div[9]/sf-form-element/div/sf-widget-chooser/boolean-radio/div/div/label/input", "xpath:position"] + ], + "value": "" + }, { + "id": "57ca153c-4121-4531-9ebe-3dfba20fa299", + "comment": "", + "command": "click", + "target": "css=.next", + "targets": [ + ["css=.next", "css:finder"], + ["xpath=//li[3]/button", "xpath:position"] + ], + "value": "" + }, { + "id": "a188bc3e-fd98-4d49-ae35-a0b78a17607c", + "comment": "", + "command": "click", + "target": "css=#\\/reloadableMetadataResolverAttributes\\/minRefreshDelay-container .btn", + "targets": [ + ["css=#\\/reloadableMetadataResolverAttributes\\/minRefreshDelay-container .btn", "css:finder"], + ["xpath=(//button[@type='button'])[2]", "xpath:attributes"], + ["xpath=//div[@id='/reloadableMetadataResolverAttributes/minRefreshDelay-container']/div/div/button", "xpath:idRelative"], + ["xpath=//div/button", "xpath:position"], + ["xpath=//button[contains(.,'Toggle Dropdown')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "a159e056-c870-4e02-a7e9-4b3a60d83718", + "comment": "", + "command": "click", + "target": "id=/reloadableMetadataResolverAttributes/minRefreshDelay__option--1", + "targets": [ + ["id=/reloadableMetadataResolverAttributes/minRefreshDelay__option--1", "id"], + ["css=#\\/reloadableMetadataResolverAttributes\\/minRefreshDelay__option--1", "css:finder"], + ["xpath=//li[@id='/reloadableMetadataResolverAttributes/minRefreshDelay__option--1']", "xpath:attributes"], + ["xpath=//ul[@id='/reloadableMetadataResolverAttributes/minRefreshDelay__listbox']/li[2]", "xpath:idRelative"], + ["xpath=//auto-complete/div/ul/li[2]", "xpath:position"], + ["xpath=//li[contains(.,'PT30S')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "7a72cf6a-c97a-49f6-83d0-cbe42f0fdb7d", + "comment": "", + "command": "click", + "target": "css=#\\/reloadableMetadataResolverAttributes\\/maxRefreshDelay-container .fa", + "targets": [ + ["css=#\\/reloadableMetadataResolverAttributes\\/maxRefreshDelay-container .fa", "css:finder"], + ["xpath=//div[@id='/reloadableMetadataResolverAttributes/maxRefreshDelay-container']/div/div/button/i", "xpath:idRelative"], + ["xpath=//div[2]/sf-form-element/div/sf-widget-chooser/datalist-component/div/auto-complete/div/div/div/button/i", "xpath:position"] + ], + "value": "" + }, { + "id": "820397f5-aabb-46f5-a65a-56747ce29091", + "comment": "", + "command": "click", + "target": "id=/reloadableMetadataResolverAttributes/maxRefreshDelay__option--3", + "targets": [ + ["id=/reloadableMetadataResolverAttributes/maxRefreshDelay__option--3", "id"], + ["css=#\\/reloadableMetadataResolverAttributes\\/maxRefreshDelay__option--3", "css:finder"], + ["xpath=//li[@id='/reloadableMetadataResolverAttributes/maxRefreshDelay__option--3']", "xpath:attributes"], + ["xpath=//ul[@id='/reloadableMetadataResolverAttributes/maxRefreshDelay__listbox']/li[4]", "xpath:idRelative"], + ["xpath=//div[2]/sf-form-element/div/sf-widget-chooser/datalist-component/div/auto-complete/div/ul/li[4]", "xpath:position"] + ], + "value": "" + }, { + "id": "b036a851-4ac9-43cb-8686-8e78ed940d72", + "comment": "", + "command": "type", + "target": "id=/reloadableMetadataResolverAttributes/refreshDelayFactor", + "targets": [ + ["id=/reloadableMetadataResolverAttributes/refreshDelayFactor", "id"], + ["name=field20", "name"], + ["css=#\\/reloadableMetadataResolverAttributes\\/refreshDelayFactor", "css:finder"], + ["xpath=//input[@id='/reloadableMetadataResolverAttributes/refreshDelayFactor']", "xpath:attributes"], + ["xpath=//custom-string/div/input", "xpath:position"] + ], + "value": "0.5" + }, { + "id": "b48d48d8-2fec-4877-85ae-2f94f15e63eb", + "comment": "", + "command": "click", + "target": "css=.next", + "targets": [ + ["css=.next", "css:finder"], + ["xpath=//li[3]/button", "xpath:position"] + ], + "value": "" + }, { + "id": "232da257-962f-4a4b-8213-038ef90c96c3", + "comment": "", + "command": "click", + "target": "css=.btn-outline-secondary", + "targets": [ + ["css=.btn-outline-secondary", "css:finder"], + ["xpath=(//button[@type='button'])[2]", "xpath:attributes"], + ["xpath=//div[@id='/metadataFilters/RequiredValidUntil/maxValidityInterval-container']/div/div/button", "xpath:idRelative"], + ["xpath=//div/button", "xpath:position"], + ["xpath=//button[contains(.,'Toggle Dropdown')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "b8d999f1-0c59-4a9a-9991-91d222f52b8e", + "comment": "", + "command": "click", + "target": "id=/metadataFilters/RequiredValidUntil/maxValidityInterval__option--1", + "targets": [ + ["id=/metadataFilters/RequiredValidUntil/maxValidityInterval__option--1", "id"], + ["css=#\\/metadataFilters\\/RequiredValidUntil\\/maxValidityInterval__option--1", "css:finder"], + ["xpath=//li[@id='/metadataFilters/RequiredValidUntil/maxValidityInterval__option--1']", "xpath:attributes"], + ["xpath=//ul[@id='/metadataFilters/RequiredValidUntil/maxValidityInterval__listbox']/li[2]", "xpath:idRelative"], + ["xpath=//auto-complete/div/ul/li[2]", "xpath:position"], + ["xpath=//li[contains(.,'P14D')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "8af1abb5-2c7a-4a4c-a8f7-0eb287100dde", + "comment": "", + "command": "click", + "target": "css=div:nth-child(1) > sf-form-element > .has-success .custom-control-label", + "targets": [ + ["css=div:nth-child(1) > sf-form-element > .has-success .custom-control-label", "css:finder"], + ["xpath=//div/div/label", "xpath:position"], + ["xpath=//label[contains(.,'Require Signed Root')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "323a2a02-9111-4064-8bb0-a3da34330e54", + "comment": "", + "command": "type", + "target": "id=/metadataFilters/SignatureValidation/certificateFile", + "targets": [], + "value": "%{idp.home}/foo.txt" + }, { + "id": "a125e601-f306-441d-9c8c-e97b95817b46", + "comment": "", + "command": "click", + "target": "css=.btn-success", + "targets": [ + ["css=.btn-success", "css:finder"], + ["xpath=//array-component/div/div/button", "xpath:position"], + ["xpath=//button[contains(.,'Add   ')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "74d696db-3f3b-4ff3-a180-34baceb469a2", + "comment": "", + "command": "select", + "target": "id=/metadataFilters/EntityRoleWhiteList/retainedRoles/0", + "targets": [], + "value": "label=SPSSODescriptor" + }, { + "id": "532bf2e5-13fc-48d7-b01b-ff16207a554d", + "comment": "", + "command": "click", + "target": "css=option:nth-child(2)", + "targets": [ + ["css=option:nth-child(2)", "css:finder"], + ["xpath=//option[@value='1: md:SPSSODescriptor']", "xpath:attributes"], + ["xpath=//select[@id='/metadataFilters/EntityRoleWhiteList/retainedRoles/0']/option[2]", "xpath:idRelative"], + ["xpath=//option[2]", "xpath:position"], + ["xpath=//option[contains(.,'SPSSODescriptor')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "12cf80c9-594c-4c4d-bf26-aa36ba636220", + "comment": "", + "command": "click", + "target": "css=.fa-plus", + "targets": [ + ["css=.fa-plus", "css:finder"], + ["xpath=//array-component/div/div/button/i", "xpath:position"] + ], + "value": "" + }, { + "id": "6cb9854b-293b-482d-8c07-4b7c638d79cb", + "comment": "", + "command": "click", + "target": "css=#\\/metadataFilters\\/EntityRoleWhiteList\\/retainedRoles\\/1 > option:nth-child(1)", + "targets": [ + ["css=#\\/metadataFilters\\/EntityRoleWhiteList\\/retainedRoles\\/1 > option:nth-child(1)", "css:finder"], + ["xpath=(//option[@value=''])[2]", "xpath:attributes"], + ["xpath=//select[@id='/metadataFilters/EntityRoleWhiteList/retainedRoles/1']/option", "xpath:idRelative"], + ["xpath=//li[2]/div/sf-form-element/div/sf-widget-chooser/select-component/div/select/option", "xpath:position"] + ], + "value": "" + }, { + "id": "f366c86c-1420-4fb7-86c2-82c49d81208d", + "comment": "", + "command": "select", + "target": "id=/metadataFilters/EntityRoleWhiteList/retainedRoles/1", + "targets": [], + "value": "label=AttributeAuthorityDescriptor" + }, { + "id": "5dfac033-ecfd-42e8-aa6f-931d5acd4d89", + "comment": "", + "command": "click", + "target": "css=div:nth-child(2) > sf-form-element > .has-success > sf-widget-chooser > checkbox-component .custom-control-label", + "targets": [ + ["css=div:nth-child(2) > sf-form-element > .has-success > sf-widget-chooser > checkbox-component .custom-control-label", "css:finder"], + ["xpath=//div[2]/sf-form-element/div/sf-widget-chooser/checkbox-component/div/div/div/label", "xpath:position"], + ["xpath=//label[contains(.,'Remove Roleless Entity Descriptors?')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "9d5934f0-fe10-4539-9d71-80b454fc8612", + "comment": "", + "command": "click", + "target": "css=div:nth-child(3) > sf-form-element > .has-success > sf-widget-chooser > checkbox-component .custom-control-label", + "targets": [ + ["css=div:nth-child(3) > sf-form-element > .has-success > sf-widget-chooser > checkbox-component .custom-control-label", "css:finder"], + ["xpath=//div[3]/sf-form-element/div/sf-widget-chooser/checkbox-component/div/div/div/label", "xpath:position"], + ["xpath=//label[contains(.,'Remove Empty Entities Descriptors?')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "530234cd-ec70-48f2-9cc6-9e0d848c4b82", + "comment": "", + "command": "click", + "target": "css=.next", + "targets": [ + ["css=.next", "css:finder"], + ["xpath=//li[3]/button", "xpath:position"] + ], + "value": "" + }, { + "id": "79b96f40-2677-438a-9564-62df0ea2c116", + "comment": "", + "command": "click", + "target": "css=.custom-control-label", + "targets": [], + "value": "" + }, { + "id": "52f5680c-c63d-411e-8332-52901f12ea3b", + "comment": "", + "command": "verifyText", + "target": "css=.px-3:nth-child(1) > summary-property:nth-child(2) > .mb-3 > .d-block:nth-child(2)", + "targets": [ + ["css=.px-3:nth-child(1) > summary-property:nth-child(2) > .mb-3 > .d-block:nth-child(2)", "css:finder"], + ["xpath=//summary-property/div/span", "xpath:position"], + ["xpath=//span[contains(.,'Test Metadata Provider')]", "xpath:innerText"] + ], + "value": "Test Metadata Provider" + }, { + "id": "67ff44aa-1efd-4b13-a0e9-3648a09911dd", + "comment": "", + "command": "verifyText", + "target": "css=.px-3:nth-child(2) > summary-property:nth-child(2) > .mb-3 > .d-block:nth-child(2)", + "targets": [ + ["css=.px-3:nth-child(2) > summary-property:nth-child(2) > .mb-3 > .d-block:nth-child(2)", "css:finder"], + ["xpath=//section[2]/summary-property/div/span", "xpath:position"], + ["xpath=//span[contains(.,'123')]", "xpath:innerText"] + ], + "value": "123" + }, { + "id": "f5197d46-41a7-4ef2-ac40-19f80c953929", + "comment": "", + "command": "click", + "target": "css=.save", + "targets": [ + ["css=.save", "css:finder"], + ["xpath=//li[3]/button", "xpath:position"] + ], + "value": "" + }, { + "id": "1067b0b4-8aff-4972-b6f5-e4479eca9150", + "comment": "", + "command": "waitForElementPresent", + "target": "css=.badge-success", + "targets": [ + ["css=.badge > span", "css:finder"], + ["xpath=//span/span", "xpath:position"] + ], + "value": "10000" + }, { + "id": "f48dd1b5-607a-485b-b1d5-03301f99b9ce", + "comment": "", + "command": "verifyText", + "target": "linkText=Test Metadata Provider", + "targets": [ + ["linkText=Test Metadata Provider", "linkText"], + ["css=td > a", "css:finder"], + ["xpath=//a[contains(text(),'Test Metadata Provider')]", "xpath:link"], + ["xpath=//a[contains(@href, '/metadata/provider/4cc9924f-fd9e-4ef3-bf57-3b24eec9f27f/configuration/options')]", "xpath:href"], + ["xpath=//td[2]/a", "xpath:position"], + ["xpath=//a[contains(.,'Test Metadata Provider')]", "xpath:innerText"] + ], + "value": "Test Metadata Provider" + }, { + "id": "5eccd4e9-a451-4ec8-a9e1-1e1f8e771677", + "comment": "", + "command": "verifyText", + "target": "css=td:nth-child(3)", + "targets": [ + ["css=td:nth-child(3)", "css:finder"], + ["xpath=//td[3]", "xpath:position"], + ["xpath=//td[contains(.,'FileBackedHttpMetadataResolver')]", "xpath:innerText"] + ], + "value": "FileBackedHttpMetadataResolver" + }, { + "id": "c768c2c1-09d4-46fe-8007-42fb4b3f4aaa", + "comment": "", + "command": "verifyText", + "target": "css=td:nth-child(4)", + "targets": [ + ["css=td:nth-child(4)", "css:finder"], + ["xpath=//td[4]", "xpath:position"], + ["xpath=//td[contains(.,'admin')]", "xpath:innerText"] + ], + "value": "admin" + }, { + "id": "8283ffc5-d46c-4a07-b95e-1534dfd34c02", + "comment": "", + "command": "click", + "target": "linkText=Test Metadata Provider", + "targets": [ + ["linkText=Test Metadata Provider", "linkText"], + ["css=td > a", "css:finder"], + ["xpath=//a[contains(text(),'Test Metadata Provider')]", "xpath:link"], + ["xpath=//a[contains(@href, '/metadata/provider/ae086029-5871-4951-bcf0-8903d7a1a1f7/configuration/options')]", "xpath:href"], + ["xpath=//td[2]/a", "xpath:position"], + ["xpath=//a[contains(.,'Test Metadata Provider')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "7a479ab7-f57d-426d-88ce-f11654733f45", + "comment": "", + "command": "waitForElementVisible", + "target": "css=.save-date", + "targets": [], + "value": "10000" + }, { + "id": "fe1e9381-048e-4948-8a9a-f58d8afba258", + "comment": "", + "command": "storeText", + "target": "css=.save-date", + "targets": [ + ["css=.save-date", "css:finder"], + ["xpath=//div[@id='header']/metadata-header/div/div/h5/span", "xpath:idRelative"], + ["xpath=//h5/span", "xpath:position"], + ["xpath=//span[contains(.,'Aug 08, 2019 15:50:04')]", "xpath:innerText"] + ], + "value": "saveDate" + }, { + "id": "a7b73d1f-87bb-4364-944a-5097f981b8c9", + "comment": "", + "command": "executeScript", + "target": "return (new Date(${saveDate}) !== \"Invalid Date\" && !isNaN(new Date(${saveDate})))", + "targets": [], + "value": "isDate" + }, { + "id": "d54fd23e-e330-48d3-a1f4-31786b087425", + "comment": "", + "command": "verify", + "target": "isDate", + "targets": [], + "value": "true" + }, { + "id": "67578648-0800-4716-8c0c-27741770249a", + "comment": "", + "command": "click", + "target": "css=.mb-4:nth-child(1) .actions span", + "targets": [ + ["css=.mb-4:nth-child(1) .actions span", "css:finder"], + ["xpath=//metadata-configuration[@id='configuration']/div/section/div/div/div/button/span", "xpath:idRelative"], + ["xpath=//div/button/span", "xpath:position"], + ["xpath=//span[contains(.,'Edit')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "6a917493-6dfa-4126-ba9b-207f9c670867", + "comment": "", + "command": "waitForElementVisible", + "target": "id=/xmlId", + "targets": [], + "value": "10000" + }, { + "id": "7d641e58-47ce-4d20-908c-e0002ff73cfa", + "comment": "", + "command": "type", + "target": "id=/xmlId", + "targets": [ + ["id=/xmlId", "id"], + ["name=field38", "name"], + ["css=#\\/xmlId", "css:finder"], + ["xpath=//input[@id='/xmlId']", "xpath:attributes"], + ["xpath=//custom-string/div/input", "xpath:position"] + ], + "value": "123 v2" + }, { + "id": "247b04c1-e690-42bc-ae15-dfbedb44d2e9", + "comment": "", + "command": "click", + "target": "id=/satisfyAnyPredicates.false", + "targets": [ + ["id=/satisfyAnyPredicates.false", "id"], + ["css=#\\/satisfyAnyPredicates\\.false", "css:finder"], + ["xpath=//input[@id='/satisfyAnyPredicates.false']", "xpath:attributes"], + ["xpath=//div[10]/sf-form-element/div/sf-widget-chooser/boolean-radio/div/div[2]/label/input", "xpath:position"] + ], + "value": "" + }, { + "id": "5aacc6b8-b509-4405-8755-c60fa3619e67", + "comment": "", + "command": "click", + "target": "css=.btn-info", + "targets": [ + ["css=.btn-info", "css:finder"], + ["xpath=//div[2]/button", "xpath:position"], + ["xpath=//button[contains(.,'Save')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "cf005a45-74ae-4f7a-a53c-ed59d63f7114", + "comment": "", + "command": "click", + "target": "linkText=Test Metadata Provider", + "targets": [ + ["linkText=Test Metadata Provider", "linkText"], + ["css=td > a", "css:finder"], + ["xpath=//a[contains(text(),'Test Metadata Provider')]", "xpath:link"], + ["xpath=//a[contains(@href, '/metadata/provider/ae086029-5871-4951-bcf0-8903d7a1a1f7/configuration/options')]", "xpath:href"], + ["xpath=//td[2]/a", "xpath:position"], + ["xpath=//a[contains(.,'Test Metadata Provider')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "ea880e6a-498a-427a-bc08-8058137d62e0", + "comment": "", + "command": "click", + "target": "css=.btn-link:nth-child(1)", + "targets": [ + ["css=.btn-link:nth-child(1) > translate-i18n", "css:finder"], + ["xpath=//div[@id='navigation']/div/a/translate-i18n", "xpath:idRelative"], + ["xpath=//div/a/translate-i18n", "xpath:position"], + ["xpath=//translate-i18n[contains(.,'Version History')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "c223e329-733c-41eb-9e85-c6f69a840179", + "comment": "", + "command": "click", + "target": "css=tr:nth-child(1) .custom-control-label", + "targets": [ + ["css=tr:nth-child(1) .custom-control-label", "css:finder"], + ["xpath=//label", "xpath:position"], + ["xpath=//label[contains(.,'Check to select')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "9b764399-76cc-44e7-8d72-5a3383387a5f", + "comment": "", + "command": "click", + "target": "css=tr:nth-child(2) .custom-control-label", + "targets": [ + ["css=tr:nth-child(2) .custom-control-label", "css:finder"], + ["xpath=//tr[2]/td/div/label", "xpath:position"] + ], + "value": "" + }, { + "id": "32ef784f-ccf0-4fd7-afff-40f0ba877dad", + "comment": "", + "command": "click", + "target": "css=.btn-primary", + "targets": [ + ["css=.btn-primary > translate-i18n", "css:finder"], + ["xpath=//history-list/button/translate-i18n", "xpath:position"], + ["xpath=//translate-i18n[contains(.,'Compare Selected')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "ce328b6a-f6ee-45e9-975c-5a5608f373dc", + "comment": "", + "command": "waitForElementVisible", + "target": "css=.d-block > primitive-property:nth-child(2) .d-block:nth-child(2)", + "targets": [], + "value": "10000" + }, { + "id": "68103f0e-e3f1-419b-903d-5d8c30bc6700", + "comment": "", + "command": "verifyText", + "target": "css=.d-block > primitive-property:nth-child(4) .d-block:nth-child(2)", + "targets": [ + ["css=.d-block > primitive-property:nth-child(4) .d-block:nth-child(2)", "css:finder"], + ["xpath=//primitive-property[4]/div/span[2]", "xpath:position"], + ["xpath=//span[contains(.,'123 v2')]", "xpath:innerText"] + ], + "value": "123 v2" + }, { + "id": "a13198b9-7827-47e2-ade3-143b981d34ee", + "comment": "", + "command": "verifyText", + "target": "css=.d-block > primitive-property:nth-child(4) .d-block:nth-child(3)", + "targets": [ + ["css=.d-block > primitive-property:nth-child(4) .d-block:nth-child(3)", "css:finder"], + ["xpath=//primitive-property[4]/div/span[3]", "xpath:position"] + ], + "value": "123" + }, { + "id": "3d83741c-299e-4fcb-9ce6-4b7291c423a4", + "comment": "", + "command": "verifyText", + "target": "css=.d-block > primitive-property:nth-child(12) .d-block:nth-child(2)", + "targets": [ + ["css=.d-block > primitive-property:nth-child(12) .d-block:nth-child(2)", "css:finder"], + ["xpath=//primitive-property[12]/div/span[2]", "xpath:position"] + ], + "value": "false" + }, { + "id": "190cc157-d427-4046-aff2-180392395ad3", + "comment": "", + "command": "verifyText", + "target": "css=.d-block > primitive-property:nth-child(12) .d-block:nth-child(3)", + "targets": [ + ["css=.d-block > primitive-property:nth-child(12) .d-block:nth-child(3)", "css:finder"], + ["xpath=//primitive-property[12]/div/span[3]", "xpath:position"] + ], + "value": "true" + }, { + "id": "f6897c4f-763f-4360-95a2-1d144455fa17", + "comment": "", + "command": "click", + "target": "css=.breadcrumb-item > a", + "targets": [ + ["css=.breadcrumb-item > a", "css:finder"], + ["xpath=//a[contains(text(),'Dashboard')]", "xpath:link"], + ["xpath=(//a[contains(@href, '/dashboard')])[2]", "xpath:href"], + ["xpath=//ol/li/a", "xpath:position"] + ], + "value": "" + }, { + "id": "41c1d29e-9eaa-41d3-979b-46601fd7a178", + "comment": "", + "command": "click", + "target": "linkText=Metadata Providers", + "targets": [ + ["linkText=Metadata Providers", "linkText"], + ["css=.nav > .nav-item:nth-child(2) > .nav-link", "css:finder"], + ["xpath=//a[contains(text(),'Metadata Providers')]", "xpath:link"], + ["xpath=//a[contains(@href, '/dashboard/metadata/manager/providers')]", "xpath:href"], + ["xpath=//dashboard-page/div/ul/li[2]/a", "xpath:position"], + ["xpath=//a[contains(.,'Metadata Providers')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "5dd3952e-d716-4d63-8a08-23be4983da23", + "comment": "", + "command": "click", + "target": "linkText=Test Metadata Provider", + "targets": [ + ["linkText=Test Metadata Provider", "linkText"], + ["css=td > a", "css:finder"], + ["xpath=//a[contains(text(),'Test Metadata Provider')]", "xpath:link"], + ["xpath=//a[contains(@href, '/metadata/provider/eb0ee617-33e2-4a31-83a5-7c42515c3b4f/configuration/options')]", "xpath:href"], + ["xpath=//td[2]/a", "xpath:position"], + ["xpath=//a[contains(.,'Test Metadata Provider')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "c9341f6f-4d13-4378-bb5c-0079e7535008", + "comment": "", + "command": "click", + "target": "css=.mb-4:nth-child(4) .actions span", + "targets": [ + ["css=.mb-4:nth-child(4) .actions span", "css:finder"], + ["xpath=//metadata-configuration[@id='configuration']/div/section[4]/div/div/div/button/span", "xpath:idRelative"], + ["xpath=//section[4]/div/div/div/button/span", "xpath:position"] + ], + "value": "" + }, { + "id": "78967fb5-8f61-46ce-9c14-9b6ceb12b03a", + "comment": "", + "command": "click", + "target": "css=#\\/httpMetadataResolverAttributes\\/connectionRequestTimeout-container .btn", + "targets": [ + ["css=#\\/httpMetadataResolverAttributes\\/connectionRequestTimeout-container .btn", "css:finder"], + ["xpath=(//button[@type='button'])[2]", "xpath:attributes"], + ["xpath=//div[@id='/httpMetadataResolverAttributes/connectionRequestTimeout-container']/div/div/button", "xpath:idRelative"], + ["xpath=//auto-complete/div/div/div/button", "xpath:position"], + ["xpath=//button[contains(.,'Toggle Dropdown')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "1cde012c-d521-4fd5-a7d5-7a680bba0e7b", + "comment": "", + "command": "click", + "target": "css=.slider", + "targets": [ + ["css=.slider", "css:finder"], + ["xpath=//toggle-switch[@id='toggle']/label/span", "xpath:idRelative"], + ["xpath=//label/span", "xpath:position"] + ], + "value": "" + }, { + "id": "d00ecf98-7425-467c-acbb-3b39918e3462", + "comment": "", + "command": "click", + "target": "css=#\\/httpMetadataResolverAttributes\\/connectionRequestTimeout-container .btn", + "targets": [ + ["css=#\\/httpMetadataResolverAttributes\\/connectionRequestTimeout-container .btn", "css:finder"], + ["xpath=(//button[@type='button'])[2]", "xpath:attributes"], + ["xpath=//div[@id='/httpMetadataResolverAttributes/connectionRequestTimeout-container']/div/div/button", "xpath:idRelative"], + ["xpath=//auto-complete/div/div/div/button", "xpath:position"], + ["xpath=//button[contains(.,'Toggle Dropdown')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "8ba62572-354d-47f4-bb72-f9309c1cb201", + "comment": "", + "command": "click", + "target": "id=/httpMetadataResolverAttributes/connectionRequestTimeout__option--1", + "targets": [ + ["id=/httpMetadataResolverAttributes/connectionRequestTimeout__option--1", "id"], + ["css=#\\/httpMetadataResolverAttributes\\/connectionRequestTimeout__option--1", "css:finder"], + ["xpath=//li[@id='/httpMetadataResolverAttributes/connectionRequestTimeout__option--1']", "xpath:attributes"], + ["xpath=//ul[@id='/httpMetadataResolverAttributes/connectionRequestTimeout__listbox']/li[2]", "xpath:idRelative"], + ["xpath=//auto-complete/div/ul/li[2]", "xpath:position"], + ["xpath=//li[contains(.,'PT30S')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "d7406190-0bb0-4df8-9c0b-7e393952b6a2", + "comment": "", + "command": "click", + "target": "css=#\\/httpMetadataResolverAttributes\\/connectionTimeout-container .btn", + "targets": [ + ["css=#\\/httpMetadataResolverAttributes\\/connectionTimeout-container .btn", "css:finder"], + ["xpath=(//button[@type='button'])[3]", "xpath:attributes"], + ["xpath=//div[@id='/httpMetadataResolverAttributes/connectionTimeout-container']/div/div/button", "xpath:idRelative"], + ["xpath=//div[2]/sf-form-element/div/sf-widget-chooser/datalist-component/div/auto-complete/div/div/div/button", "xpath:position"] + ], + "value": "" + }, { + "id": "337ac160-7990-425f-83da-2788d8a0985e", + "comment": "", + "command": "click", + "target": "id=/httpMetadataResolverAttributes/connectionTimeout__option--2", + "targets": [ + ["id=/httpMetadataResolverAttributes/connectionTimeout__option--2", "id"], + ["css=#\\/httpMetadataResolverAttributes\\/connectionTimeout__option--2", "css:finder"], + ["xpath=//li[@id='/httpMetadataResolverAttributes/connectionTimeout__option--2']", "xpath:attributes"], + ["xpath=//ul[@id='/httpMetadataResolverAttributes/connectionTimeout__listbox']/li[3]", "xpath:idRelative"], + ["xpath=//div[2]/sf-form-element/div/sf-widget-chooser/datalist-component/div/auto-complete/div/ul/li[3]", "xpath:position"] + ], + "value": "" + }, { + "id": "19182110-0f86-4601-ae12-0b90967ef68a", + "comment": "", + "command": "click", + "target": "css=#\\/httpMetadataResolverAttributes\\/socketTimeout-container .btn", + "targets": [ + ["css=#\\/httpMetadataResolverAttributes\\/socketTimeout-container .btn", "css:finder"], + ["xpath=(//button[@type='button'])[4]", "xpath:attributes"], + ["xpath=//div[@id='/httpMetadataResolverAttributes/socketTimeout-container']/div/div/button", "xpath:idRelative"], + ["xpath=//div[3]/sf-form-element/div/sf-widget-chooser/datalist-component/div/auto-complete/div/div/div/button", "xpath:position"] + ], + "value": "" + }, { + "id": "34693225-4754-4b27-92ee-3e37304a8cd7", + "comment": "", + "command": "click", + "target": "id=/httpMetadataResolverAttributes/socketTimeout__option--3", + "targets": [ + ["id=/httpMetadataResolverAttributes/socketTimeout__option--3", "id"], + ["css=#\\/httpMetadataResolverAttributes\\/socketTimeout__option--3", "css:finder"], + ["xpath=//li[@id='/httpMetadataResolverAttributes/socketTimeout__option--3']", "xpath:attributes"], + ["xpath=//ul[@id='/httpMetadataResolverAttributes/socketTimeout__listbox']/li[4]", "xpath:idRelative"], + ["xpath=//div[3]/sf-form-element/div/sf-widget-chooser/datalist-component/div/auto-complete/div/ul/li[4]", "xpath:position"] + ], + "value": "" + }, { + "id": "d9a9dcb4-bac1-4f5a-a822-047bd2941df0", + "comment": "", + "command": "click", + "target": "css=.btn-info", + "targets": [ + ["css=.btn-info", "css:finder"], + ["xpath=//div[2]/button", "xpath:position"], + ["xpath=//button[contains(.,'Save')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "8868c054-2e6a-403e-ba2b-499a56730d71", + "comment": "", + "command": "click", + "target": "linkText=Test Metadata Provider", + "targets": [ + ["linkText=Test Metadata Provider", "linkText"], + ["css=td > a", "css:finder"], + ["xpath=//a[contains(text(),'Test Metadata Provider')]", "xpath:link"], + ["xpath=//a[contains(@href, '/metadata/provider/eb0ee617-33e2-4a31-83a5-7c42515c3b4f/configuration/options')]", "xpath:href"], + ["xpath=//td[2]/a", "xpath:position"], + ["xpath=//a[contains(.,'Test Metadata Provider')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "0092cd5c-a885-4a48-a233-b3af447b02bc", + "comment": "", + "command": "click", + "target": "css=.btn-link:nth-child(1)", + "targets": [ + ["css=.btn-link:nth-child(1) > translate-i18n", "css:finder"], + ["xpath=//div[@id='navigation']/div/a/translate-i18n", "xpath:idRelative"], + ["xpath=//div/a/translate-i18n", "xpath:position"], + ["xpath=//translate-i18n[contains(.,'Version History')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "ca6537f7-73d1-4f02-ba06-c24f66c569e3", + "comment": "", + "command": "waitForElementVisible", + "target": "css=tr:nth-child(1) > td:nth-child(3)", + "targets": [ + ["css=tr:nth-child(1) > td:nth-child(3)", "css:finder"], + ["xpath=//td[3]", "xpath:position"], + ["xpath=//td[contains(.,'Aug 08, 2019 16:10:01')]", "xpath:innerText"] + ], + "value": "10000" + }, { + "id": "6620688a-039a-4969-92be-b1fcfc8a4161", + "comment": "", + "command": "storeText", + "target": "css=tr:nth-child(1) > td:nth-child(3)", + "targets": [ + ["css=tr:nth-child(1) > td:nth-child(3)", "css:finder"], + ["xpath=//td[3]", "xpath:position"], + ["xpath=//td[contains(.,'Aug 08, 2019 16:10:01')]", "xpath:innerText"] + ], + "value": "saveDate" + }, { + "id": "910d8bf9-4875-4946-9ab8-0d3a9b13f020", + "comment": "", + "command": "executeScript", + "target": "return (new Date(${saveDate}) !== \"Invalid Date\" && !isNaN(new Date(${saveDate})))", + "targets": [], + "value": "isDate" + }, { + "id": "18458e4b-e414-4125-aa4d-fd27848d3c40", + "comment": "", + "command": "verify", + "target": "isDate", + "targets": [], + "value": "true" + }, { + "id": "48adfc53-ed95-4e59-aa57-45eb57128154", + "comment": "", + "command": "storeText", + "target": "css=tr:nth-child(2) > td:nth-child(3)", + "targets": [ + ["css=tr:nth-child(2) > td:nth-child(3)", "css:finder"], + ["xpath=//tr[2]/td[3]", "xpath:position"], + ["xpath=//td[contains(.,'Aug 08, 2019 16:09:55')]", "xpath:innerText"] + ], + "value": "saveDate" + }, { + "id": "a370624e-a6d6-43d7-a74a-eeb8c577fc06", + "comment": "", + "command": "executeScript", + "target": "return (new Date(${saveDate}) !== \"Invalid Date\" && !isNaN(new Date(${saveDate})))", + "targets": [], + "value": "isDate" + }, { + "id": "21e88ab8-7f11-42ae-8db8-ccc21116c303", + "comment": "", + "command": "verify", + "target": "isDate", + "targets": [], + "value": "true" + }, { + "id": "2a299cc4-7b22-42e6-89cd-4ae0aef8bb27", + "comment": "", + "command": "storeText", + "target": "css=tr:nth-child(3) > td:nth-child(3)", + "targets": [ + ["css=tr:nth-child(3) > td:nth-child(3)", "css:finder"], + ["xpath=//tr[3]/td[3]", "xpath:position"], + ["xpath=//td[contains(.,'Aug 08, 2019 16:09:52')]", "xpath:innerText"] + ], + "value": "saveDate" + }, { + "id": "4b305857-0369-4fad-821c-c944e9801395", + "comment": "", + "command": "executeScript", + "target": "return (new Date(${saveDate}) !== \"Invalid Date\" && !isNaN(new Date(${saveDate})))", + "targets": [], + "value": "isDate" + }, { + "id": "3fd48668-3df2-4264-85c9-01dac26acaee", + "comment": "", + "command": "verify", + "target": "isDate", + "targets": [], + "value": "true" + }, { + "id": "2cfe222d-a506-4f2b-8840-4b4a76cf2bfb", + "comment": "", + "command": "click", + "target": "css=tr:nth-child(1) .custom-control-label", + "targets": [ + ["css=tr:nth-child(1) .custom-control-label", "css:finder"], + ["xpath=//label", "xpath:position"], + ["xpath=//label[contains(.,'Check to select')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "a10e3e37-28f1-403f-b572-8b4020de7904", + "comment": "", + "command": "click", + "target": "css=tr:nth-child(2) .custom-control-label", + "targets": [ + ["css=tr:nth-child(2) .custom-control-label", "css:finder"], + ["xpath=//tr[2]/td/div/label", "xpath:position"] + ], + "value": "" + }, { + "id": "d487ae82-707d-4991-8914-89cf181b656e", + "comment": "", + "command": "click", + "target": "css=.btn-primary", + "targets": [ + ["css=.btn-primary", "css:finder"], + ["xpath=//history-list/button", "xpath:position"], + ["xpath=//button[contains(.,'Compare Selected(2)')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "9f1380c3-2b99-4b7d-90b7-f20d4fe431d3", + "comment": "", + "command": "waitForElementVisible", + "target": "css=.mb-4:nth-child(4) primitive-property:nth-child(2) .d-block:nth-child(2)", + "targets": [], + "value": "10000" + }, { + "id": "8a2c6435-d77b-4c87-8e0e-e1ae6d4629b9", + "comment": "", + "command": "verifyText", + "target": "css=.mb-4:nth-child(4) primitive-property:nth-child(2) .d-block:nth-child(2)", + "targets": [ + ["css=.mb-4:nth-child(4) primitive-property:nth-child(2) .d-block:nth-child(2)", "css:finder"], + ["xpath=//section[4]/div/div[2]/object-property/object-property/primitive-property[2]/div/span[2]", "xpath:position"] + ], + "value": "PT30S" + }, { + "id": "0687fe3f-c1af-4a5a-9708-09b7b92734bc", + "comment": "", + "command": "verifyText", + "target": "css=.mb-4:nth-child(4) primitive-property:nth-child(3) .d-block:nth-child(2)", + "targets": [ + ["css=.mb-4:nth-child(4) primitive-property:nth-child(3) .d-block:nth-child(2)", "css:finder"], + ["xpath=//section[4]/div/div[2]/object-property/object-property/primitive-property[3]/div/span[2]", "xpath:position"], + ["xpath=//span[contains(.,'PT1M')]", "xpath:innerText"] + ], + "value": "PT1M" + }, { + "id": "a007794f-ab91-47b8-b470-e4294e47a2a7", + "comment": "", + "command": "verifyText", + "target": "css=object-property:nth-child(1) > primitive-property:nth-child(4) .d-block:nth-child(2)", + "targets": [ + ["css=object-property:nth-child(1) > primitive-property:nth-child(4) .d-block:nth-child(2)", "css:finder"], + ["xpath=//object-property/object-property/primitive-property[4]/div/span[2]", "xpath:position"] + ], + "value": "PT10M" + }, { + "id": "5279bf64-46c1-4376-a527-e19e217b42fa", + "comment": "", + "command": "verifyText", + "target": "css=object-property:nth-child(1) > primitive-property:nth-child(5) .d-block:nth-child(2)", + "targets": [ + ["css=object-property:nth-child(1) > primitive-property:nth-child(5) .d-block:nth-child(2)", "css:finder"], + ["xpath=//object-property/object-property/primitive-property[5]/div/span[2]", "xpath:position"] + ], + "value": "false" + }, { + "id": "cc2f6596-d135-48ef-abab-8f2c4bf44a2c", + "comment": "", + "command": "storeText", + "target": "css=.mb-4:nth-child(1) strong:nth-child(2) > span", + "targets": [ + ["css=.mb-4:nth-child(1) strong:nth-child(2) > span", "css:finder"], + ["xpath=//strong[2]/span", "xpath:position"], + ["xpath=//span[contains(.,'Aug 08, 2019 16:10:01')]", "xpath:innerText"] + ], + "value": "leftDate" + }, { + "id": "c1fda79e-9fa9-4969-92f5-83f9f54b9d3d", + "comment": "", + "command": "executeScript", + "target": "return (new Date(${leftDate}) !== \"Invalid Date\" && !isNaN(new Date(${leftDate})))", + "targets": [], + "value": "leftDateIsDate" + }, { + "id": "30e8b9fb-254d-462e-9039-6b378ff4d245", + "comment": "", + "command": "verify", + "target": "leftDateIsDate", + "targets": [], + "value": "true" + }, { + "id": "a55513b6-d1dd-4036-9b99-4d03e845d65a", + "comment": "", + "command": "storeText", + "target": "css=.mb-4:nth-child(1) strong:nth-child(3) > span", + "targets": [ + ["css=.mb-4:nth-child(1) strong:nth-child(3) > span", "css:finder"], + ["xpath=//strong[3]/span", "xpath:position"], + ["xpath=//span[contains(.,'Aug 08, 2019 16:09:55')]", "xpath:innerText"] + ], + "value": "rightDate" + }, { + "id": "dd3a52a4-de8b-4c25-a6e7-81b7ee9f8149", + "comment": "", + "command": "executeScript", + "target": "return (new Date(${rightDate}) !== \"Invalid Date\" && !isNaN(new Date(${rightDate})))", + "targets": [], + "value": "rightDateIsDate" + }, { + "id": "d973102f-964b-4021-93f4-44adf165b33e", + "comment": "", + "command": "verify", + "target": "rightDateIsDate", + "targets": [], + "value": "true" + }] + }], + "suites": [{ + "id": "894bbaf7-9978-4d30-b4e3-3c4263e084aa", + "name": "Default Suite", + "persistSession": false, + "parallel": false, + "timeout": 300, + "tests": ["1e7a4ca4-b2e6-4e5a-8e38-823ae8a1e20d"] + }], + "urls": ["http://localhost:10101/"], + "plugins": [] +} \ No newline at end of file From 3a2cee53a09fa306f2399cef7f83874cc92dffc1 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Fri, 9 Aug 2019 11:05:10 -0700 Subject: [PATCH 02/27] SHIBUI-1382 implementation for restore page --- .../configuration/action/restore.action.ts | 31 ++++++++++++++++- .../container/configuration.component.ts | 2 +- .../container/metadata-history.component.ts | 1 + .../container/restore.component.html | 10 +++--- .../container/restore.component.ts | 33 ++++++++++++++----- .../effect/configuration.effect.ts | 8 ++--- .../configuration/effect/restore.effect.ts | 17 ++++++++-- .../metadata/configuration/reducer/index.ts | 1 + 8 files changed, 82 insertions(+), 21 deletions(-) diff --git a/ui/src/app/metadata/configuration/action/restore.action.ts b/ui/src/app/metadata/configuration/action/restore.action.ts index 48861c35e..b05594fab 100644 --- a/ui/src/app/metadata/configuration/action/restore.action.ts +++ b/ui/src/app/metadata/configuration/action/restore.action.ts @@ -5,13 +5,39 @@ export enum RestoreActionTypes { SELECT_VERSION_SUCCESS = '[Restore Version] Select Version Success', SELECT_VERSION_ERROR = '[Restore Version] Select Version Error', SELECT_VERSION_REQUEST = '[Restore Version] Select Version Request', + + RESTORE_VERSION_REQUEST = '[Restore Version] Restore Version Request', + RESTORE_VERSION_SUCCESS = '[Restore Version] Restore Version Request', + RESTORE_VERSION_ERROR = '[Restore Version] Restore Version Request', + CLEAR_VERSION = '[Restore Version] Clear Versions' } +export interface VersionRequest { + type: string; + id: string; + version: string; +} + +export class RestoreVersionRequest implements Action { + readonly type = RestoreActionTypes.RESTORE_VERSION_REQUEST; + constructor(public payload: VersionRequest) { } +} + +export class RestoreVersionSuccess implements Action { + readonly type = RestoreActionTypes.RESTORE_VERSION_SUCCESS; + constructor(public payload: Metadata) { } +} + +export class RestoreVersionError implements Action { + readonly type = RestoreActionTypes.RESTORE_VERSION_ERROR; + constructor(public payload: any) { } +} + export class SelectVersionRestoreRequest implements Action { readonly type = RestoreActionTypes.SELECT_VERSION_REQUEST; - constructor(public payload: { type: string, id: string, version: string }) { } + constructor(public payload: VersionRequest) { } } export class SelectVersionRestoreSuccess implements Action { @@ -33,4 +59,7 @@ export type RestoreActionsUnion = | SelectVersionRestoreRequest | SelectVersionRestoreError | SelectVersionRestoreSuccess + | RestoreVersionRequest + | RestoreVersionSuccess + | RestoreVersionError | ClearVersionRestore; diff --git a/ui/src/app/metadata/configuration/container/configuration.component.ts b/ui/src/app/metadata/configuration/container/configuration.component.ts index d8c0234c6..7e8d6f443 100644 --- a/ui/src/app/metadata/configuration/container/configuration.component.ts +++ b/ui/src/app/metadata/configuration/container/configuration.component.ts @@ -37,7 +37,7 @@ export class ConfigurationComponent implements OnDestroy { type, version })) - ).subscribe(store); + ).subscribe(this.store); this.routerState.params.pipe( takeUntil(this.ngUnsubscribe), diff --git a/ui/src/app/metadata/configuration/container/metadata-history.component.ts b/ui/src/app/metadata/configuration/container/metadata-history.component.ts index 17f2c3caa..7d817b94f 100644 --- a/ui/src/app/metadata/configuration/container/metadata-history.component.ts +++ b/ui/src/app/metadata/configuration/container/metadata-history.component.ts @@ -49,6 +49,7 @@ export class MetadataHistoryComponent { this.router.navigate( [ '../', 'restore' ], { + queryParams: { version: version.id }, relativeTo: this.route } ); diff --git a/ui/src/app/metadata/configuration/container/restore.component.html b/ui/src/app/metadata/configuration/container/restore.component.html index c842aa47d..0b5a2f1ac 100644 --- a/ui/src/app/metadata/configuration/container/restore.component.html +++ b/ui/src/app/metadata/configuration/container/restore.component.html @@ -1,12 +1,14 @@

- Restore Version + Restore Version ({{ date$ | async | date:DATE_FORMAT }})

-

Create new version from {{ date | date:DATE_FORMAT }} settings

-

Restoring this version will copy the configuration from the selected version and create a new version from these settings. You can then edit the configuration before saving the new version.

- Cancel Restore +

Create New Version from Version ({{ date$ | async | date:DATE_FORMAT }}) Settings

+

Restoring this version will copy the Version ({{ date$ | async | date:DATE_FORMAT }}) configuration and create a new Version from the + selected version settings. You can then edit the configuration before saving the new version.

+ Cancel  +
diff --git a/ui/src/app/metadata/configuration/container/restore.component.ts b/ui/src/app/metadata/configuration/container/restore.component.ts index 53f3573ec..83d3cb98f 100644 --- a/ui/src/app/metadata/configuration/container/restore.component.ts +++ b/ui/src/app/metadata/configuration/container/restore.component.ts @@ -5,6 +5,8 @@ import { Subject } from 'rxjs'; import * as fromConfiguration from '../reducer'; import { CONFIG_DATE_FORMAT } from '../configuration.values'; +import { RestoreVersionRequest } from '../action/restore.action'; +import { withLatestFrom, map, takeUntil } from 'rxjs/operators'; @Component({ selector: 'restore-component', @@ -13,19 +15,32 @@ import { CONFIG_DATE_FORMAT } from '../configuration.values'; styleUrls: [] }) export class RestoreComponent implements OnDestroy { - private ngUnsubscribe: Subject = new Subject(); - DATE_FORMAT = CONFIG_DATE_FORMAT; + private subj = new Subject(); + restore$ = this.subj.asObservable(); + + date$ = this.store.select(fromConfiguration.getConfigurationVersionDate); - date = new Date(); + DATE_FORMAT = CONFIG_DATE_FORMAT; constructor( - private store: Store, - private routerState: ActivatedRoute - ) {} + private store: Store + ) { + this.restore$.pipe( + withLatestFrom( + this.store.select(fromConfiguration.getSelectedVersionId), + this.store.select(fromConfiguration.getConfigurationModelType), + this.store.select(fromConfiguration.getConfigurationModelId) + ), + map(([restore, version, type, id]) => new RestoreVersionRequest({ id, type, version })) + ).subscribe(this.store); + } + + restore() { + this.subj.next(); + } - ngOnDestroy() { - this.ngUnsubscribe.next(); - this.ngUnsubscribe.complete(); + ngOnDestroy(): void { + this.subj.complete(); } } diff --git a/ui/src/app/metadata/configuration/effect/configuration.effect.ts b/ui/src/app/metadata/configuration/effect/configuration.effect.ts index 924d61919..b9719469d 100644 --- a/ui/src/app/metadata/configuration/effect/configuration.effect.ts +++ b/ui/src/app/metadata/configuration/effect/configuration.effect.ts @@ -44,11 +44,11 @@ export class MetadataConfigurationEffects { map(action => action.payload), switchMap(payload => this.historyService.getVersion(payload.id, payload.type, payload.version).pipe( - map((response: Metadata) => - (payload.type === 'resolver') ? + map((response: Metadata) => { + return (payload.type === 'resolver') ? new SelectResolverSuccess(response as MetadataResolver) : - new SelectProviderSuccess(response as MetadataProvider) - ) + new SelectProviderSuccess(response as MetadataProvider); + }) ) ) ); diff --git a/ui/src/app/metadata/configuration/effect/restore.effect.ts b/ui/src/app/metadata/configuration/effect/restore.effect.ts index b7f928f88..3a7f10002 100644 --- a/ui/src/app/metadata/configuration/effect/restore.effect.ts +++ b/ui/src/app/metadata/configuration/effect/restore.effect.ts @@ -6,7 +6,8 @@ import { RestoreActionTypes, SelectVersionRestoreRequest, SelectVersionRestoreError, - SelectVersionRestoreSuccess + SelectVersionRestoreSuccess, + RestoreVersionRequest } from '../action/restore.action'; import { MetadataHistoryService } from '../service/history.service'; import { of } from 'rxjs'; @@ -16,7 +17,7 @@ import { of } from 'rxjs'; export class RestoreVersionEffects { @Effect() - restoreVersionFromId$ = this.actions$.pipe( + selectVersionFromId$ = this.actions$.pipe( ofType(RestoreActionTypes.SELECT_VERSION_REQUEST), map(action => action.payload), switchMap(({ type, id, version }) => { @@ -27,6 +28,18 @@ export class RestoreVersionEffects { }) ); + @Effect() + restoreVersion$ = this.actions$.pipe( + ofType(RestoreActionTypes.RESTORE_VERSION_REQUEST), + map(action => action.payload), + switchMap(({ id, type, version }) => { + return this.historyService.getVersion(id, version, type).pipe( + map(v => new SelectVersionRestoreSuccess(v)), + catchError(err => of(new SelectVersionRestoreError(err))) + ); + }) + ); + constructor( private historyService: MetadataHistoryService, private actions$: Actions diff --git a/ui/src/app/metadata/configuration/reducer/index.ts b/ui/src/app/metadata/configuration/reducer/index.ts index 76140bfca..469fe740e 100644 --- a/ui/src/app/metadata/configuration/reducer/index.ts +++ b/ui/src/app/metadata/configuration/reducer/index.ts @@ -183,3 +183,4 @@ export const getConfigurationModelType = createSelector(getConfigurationModel, g export const getConfigurationHasXml = createSelector(getConfigurationXml, xml => !!xml); export const getConfigurationFilters = createSelector(getConfigurationModel, model => model.metadataFilters); +export const getConfigurationVersionDate = createSelector(getConfigurationModel, version => version.modifiedDate); From f302e901b73902fb5a1a5a6baa4ba1c2507c56c0 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Mon, 12 Aug 2019 09:40:45 -0700 Subject: [PATCH 03/27] SHIBUI-1382 Implemented restore call --- .../configuration/action/restore.action.ts | 6 +-- .../container/restore.component.ts | 2 +- .../configuration/effect/restore.effect.ts | 46 +++++++++++++++---- .../configuration/service/history.service.ts | 15 +++++- .../model/wizards/metadata-source-base.ts | 1 + 5 files changed, 57 insertions(+), 13 deletions(-) diff --git a/ui/src/app/metadata/configuration/action/restore.action.ts b/ui/src/app/metadata/configuration/action/restore.action.ts index b05594fab..290367054 100644 --- a/ui/src/app/metadata/configuration/action/restore.action.ts +++ b/ui/src/app/metadata/configuration/action/restore.action.ts @@ -7,8 +7,8 @@ export enum RestoreActionTypes { SELECT_VERSION_REQUEST = '[Restore Version] Select Version Request', RESTORE_VERSION_REQUEST = '[Restore Version] Restore Version Request', - RESTORE_VERSION_SUCCESS = '[Restore Version] Restore Version Request', - RESTORE_VERSION_ERROR = '[Restore Version] Restore Version Request', + RESTORE_VERSION_SUCCESS = '[Restore Version] Restore Version Success', + RESTORE_VERSION_ERROR = '[Restore Version] Restore Version Error', CLEAR_VERSION = '[Restore Version] Clear Versions' } @@ -26,7 +26,7 @@ export class RestoreVersionRequest implements Action { export class RestoreVersionSuccess implements Action { readonly type = RestoreActionTypes.RESTORE_VERSION_SUCCESS; - constructor(public payload: Metadata) { } + constructor(public payload: { id: string, type: string, model: Metadata }) { } } export class RestoreVersionError implements Action { diff --git a/ui/src/app/metadata/configuration/container/restore.component.ts b/ui/src/app/metadata/configuration/container/restore.component.ts index 83d3cb98f..feffeb71b 100644 --- a/ui/src/app/metadata/configuration/container/restore.component.ts +++ b/ui/src/app/metadata/configuration/container/restore.component.ts @@ -29,7 +29,7 @@ export class RestoreComponent implements OnDestroy { this.restore$.pipe( withLatestFrom( this.store.select(fromConfiguration.getSelectedVersionId), - this.store.select(fromConfiguration.getConfigurationModelType), + this.store.select(fromConfiguration.getConfigurationModelKind), this.store.select(fromConfiguration.getConfigurationModelId) ), map(([restore, version, type, id]) => new RestoreVersionRequest({ id, type, version })) diff --git a/ui/src/app/metadata/configuration/effect/restore.effect.ts b/ui/src/app/metadata/configuration/effect/restore.effect.ts index 3a7f10002..d91682c61 100644 --- a/ui/src/app/metadata/configuration/effect/restore.effect.ts +++ b/ui/src/app/metadata/configuration/effect/restore.effect.ts @@ -7,10 +7,16 @@ import { SelectVersionRestoreRequest, SelectVersionRestoreError, SelectVersionRestoreSuccess, - RestoreVersionRequest + RestoreVersionRequest, + RestoreVersionSuccess, + RestoreVersionError } from '../action/restore.action'; import { MetadataHistoryService } from '../service/history.service'; import { of } from 'rxjs'; +import { Router, ActivatedRoute } from '@angular/router'; + +import { AddNotification } from '../../../notification/action/notification.action'; +import { Notification, NotificationType } from '../../../notification/model/notification'; @Injectable() @@ -32,16 +38,40 @@ export class RestoreVersionEffects { restoreVersion$ = this.actions$.pipe( ofType(RestoreActionTypes.RESTORE_VERSION_REQUEST), map(action => action.payload), - switchMap(({ id, type, version }) => { - return this.historyService.getVersion(id, version, type).pipe( - map(v => new SelectVersionRestoreSuccess(v)), - catchError(err => of(new SelectVersionRestoreError(err))) - ); - }) + switchMap(({ id, type, version }) => + this.historyService.restoreVersion(id, type, version).pipe( + map(v => new RestoreVersionSuccess({ id, type, model: v })), + catchError(err => of(new RestoreVersionError(err))) + ) + ) + ); + + @Effect() + restoreVersionSuccessNotification$ = this.actions$.pipe( + ofType(RestoreActionTypes.RESTORE_VERSION_SUCCESS), + map(action => action.payload), + map((data) => + new AddNotification(new Notification( + NotificationType.Success, + `Version Restored!`, + 5000 + )) + ) + ); + + @Effect({dispatch: false}) + restoreVersionSuccessRedirect$ = this.actions$.pipe( + ofType(RestoreActionTypes.RESTORE_VERSION_SUCCESS), + map(action => action.payload), + switchMap((data) => + this.router.navigate(['/metadata', data.type, data.id, 'configuration', 'options']) + ) ); constructor( private historyService: MetadataHistoryService, - private actions$: Actions + private actions$: Actions, + private router: Router, + private route: ActivatedRoute ) { } } diff --git a/ui/src/app/metadata/configuration/service/history.service.ts b/ui/src/app/metadata/configuration/service/history.service.ts index 700e88e6a..02529cde5 100644 --- a/ui/src/app/metadata/configuration/service/history.service.ts +++ b/ui/src/app/metadata/configuration/service/history.service.ts @@ -5,8 +5,9 @@ import { MetadataHistory } from '../model/history'; import { PATHS } from '../../configuration/configuration.values'; import { MetadataVersion } from '../model/version'; -import { map } from 'rxjs/operators'; +import { map, catchError, switchMap } from 'rxjs/operators'; import { Metadata } from '../../domain/domain.type'; +import { withLatestFrom } from 'rxjs-compat/operator/withLatestFrom'; @Injectable() export class MetadataHistoryService { @@ -39,4 +40,16 @@ export class MetadataHistoryService { `/${this.base}/${PATHS[type]}/${resourceId}`; return this.http.get(api); } + + updateVersion(resourceId: string, type: string, model: Metadata): Observable { + return this.http.put(`/${this.base}/${PATHS[type]}/${resourceId}`, model); + } + + restoreVersion(resourceId: string, type: string, versionId: string): Observable { + return this.getVersions(resourceId, [null, versionId], type).pipe( + switchMap(([current, toRestore]) => + this.updateVersion(resourceId, type, { ...toRestore, version: current.version }) + ) + ); + } } diff --git a/ui/src/app/metadata/domain/model/wizards/metadata-source-base.ts b/ui/src/app/metadata/domain/model/wizards/metadata-source-base.ts index aab844d5e..02b1a2625 100644 --- a/ui/src/app/metadata/domain/model/wizards/metadata-source-base.ts +++ b/ui/src/app/metadata/domain/model/wizards/metadata-source-base.ts @@ -86,6 +86,7 @@ export class MetadataSourceBase implements Wizard { const checkOrg = (value, property, form) => { const org = property.parent; const orgValue = org.value || {}; + console.log(orgValue); const err = Object.keys(orgValue) && !value ? { code: 'ORG_INCOMPLETE', path: `#${property.path}`, From 0a601273d998c076a789dbc06abd5b7c46845f69 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Tue, 13 Aug 2019 08:02:03 -0700 Subject: [PATCH 04/27] Implemented unit tests --- .../container/configuration.component.spec.ts | 8 ++ .../container/configuration.component.ts | 8 +- .../container/metadata-xml.component.spec.ts | 7 ++ .../container/restore.component.spec.ts | 72 ++++++++++++++++++ .../container/restore.component.ts | 2 +- .../service/history.service.spec.ts | 74 ++++++++++++++++++- 6 files changed, 165 insertions(+), 6 deletions(-) create mode 100644 ui/src/app/metadata/configuration/container/restore.component.spec.ts diff --git a/ui/src/app/metadata/configuration/container/configuration.component.spec.ts b/ui/src/app/metadata/configuration/container/configuration.component.spec.ts index 71bb05ab0..0d2fca0d3 100644 --- a/ui/src/app/metadata/configuration/container/configuration.component.spec.ts +++ b/ui/src/app/metadata/configuration/container/configuration.component.spec.ts @@ -59,4 +59,12 @@ describe('Metadata Configuration Page Component', () => { it('should load metadata objects', async(() => { expect(app).toBeTruthy(); })); + + describe('hasVersion function', () => { + it('should determine if a version is defined', () => { + expect(app.hasVersion([[{id: 'foo'}], { version: 'foo' }])).toBe('foo'); + expect(app.hasVersion([[{ id: 'foo' }], {}])).toBe('foo'); + expect(app.hasVersion([[], {}])).toBeNull(); + }); + }); }); diff --git a/ui/src/app/metadata/configuration/container/configuration.component.ts b/ui/src/app/metadata/configuration/container/configuration.component.ts index 7e8d6f443..295f90257 100644 --- a/ui/src/app/metadata/configuration/container/configuration.component.ts +++ b/ui/src/app/metadata/configuration/container/configuration.component.ts @@ -22,6 +22,8 @@ export class ConfigurationComponent implements OnDestroy { name$: Observable; type$: Observable; + hasVersion = ([collection, params]) => params.version || collection && collection.length > 0 ? collection[0].id : null; + constructor( private store: Store, private routerState: ActivatedRoute @@ -50,10 +52,8 @@ export class ConfigurationComponent implements OnDestroy { withLatestFrom( this.routerState.queryParams ), - map(([collection, params]) => params.version || collection && collection.length ? collection[0].id : null) - ).subscribe(version => { - this.store.dispatch(new SelectVersion(version)); - }); + map(this.hasVersion) + ).subscribe(version => this.store.dispatch(new SelectVersion(version))); this.name$ = this.store.select(fromReducer.getConfigurationModelName); this.type$ = this.store.select(fromReducer.getConfigurationModelType); diff --git a/ui/src/app/metadata/configuration/container/metadata-xml.component.spec.ts b/ui/src/app/metadata/configuration/container/metadata-xml.component.spec.ts index c9129dfaa..5e5815c06 100644 --- a/ui/src/app/metadata/configuration/container/metadata-xml.component.spec.ts +++ b/ui/src/app/metadata/configuration/container/metadata-xml.component.spec.ts @@ -56,4 +56,11 @@ describe('Metadata Xml Page Component', () => { expect(app).toBeTruthy(); expect(store.select).toHaveBeenCalledTimes(3); })); + + describe('preview method', () => { + it('should dispatch an action', () => { + app.preview(); + expect(store.dispatch).toHaveBeenCalled(); + }); + }); }); diff --git a/ui/src/app/metadata/configuration/container/restore.component.spec.ts b/ui/src/app/metadata/configuration/container/restore.component.spec.ts new file mode 100644 index 000000000..433a11ebd --- /dev/null +++ b/ui/src/app/metadata/configuration/container/restore.component.spec.ts @@ -0,0 +1,72 @@ +import { Component, ViewChild, Input } from '@angular/core'; +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { StoreModule, combineReducers, Store } from '@ngrx/store'; +import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; + +import * as fromConfiguration from '../reducer'; +import * as fromProviders from '../../provider/reducer'; +import * as fromResolvers from '../../resolver/reducer'; +import { MockI18nModule } from '../../../../testing/i18n.stub'; +import { RestoreComponent } from './restore.component'; +import { of } from 'rxjs'; + +@Component({ + template: ` + + ` +}) +class TestHostComponent { + @ViewChild(RestoreComponent) + public componentUnderTest: RestoreComponent; +} + +describe('Metadata Restore Page Component', () => { + + let fixture: ComponentFixture; + let instance: TestHostComponent; + let app: RestoreComponent; + let store: Store; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + NgbDropdownModule, + StoreModule.forRoot({ + 'metadata-configuration': combineReducers(fromConfiguration.reducers), + 'provider': combineReducers(fromProviders.reducers), + 'resolver': combineReducers(fromResolvers.reducers) + }), + MockI18nModule, + RouterTestingModule + ], + declarations: [ + RestoreComponent, + TestHostComponent + ], + }).compileComponents(); + + store = TestBed.get(Store); + spyOn(store, 'dispatch'); + spyOn(store, 'select').and.callFake(() => of(new Date().toDateString())); + + fixture = TestBed.createComponent(TestHostComponent); + instance = fixture.componentInstance; + app = instance.componentUnderTest; + fixture.detectChanges(); + })); + + it('should load metadata objects', async(() => { + expect(app).toBeTruthy(); + expect(store.select).toHaveBeenCalledTimes(4); + expect(store.dispatch).not.toHaveBeenCalled(); + })); + + describe('restore method', () => { + it('should emit a value from the restore subject', () => { + spyOn(app.subj, 'next').and.callThrough(); + app.restore(); + expect(app.subj.next).toHaveBeenCalled(); + }); + }); +}); diff --git a/ui/src/app/metadata/configuration/container/restore.component.ts b/ui/src/app/metadata/configuration/container/restore.component.ts index feffeb71b..69c6a6f18 100644 --- a/ui/src/app/metadata/configuration/container/restore.component.ts +++ b/ui/src/app/metadata/configuration/container/restore.component.ts @@ -16,7 +16,7 @@ import { withLatestFrom, map, takeUntil } from 'rxjs/operators'; }) export class RestoreComponent implements OnDestroy { - private subj = new Subject(); + readonly subj = new Subject(); restore$ = this.subj.asObservable(); date$ = this.store.select(fromConfiguration.getConfigurationVersionDate); diff --git a/ui/src/app/metadata/configuration/service/history.service.spec.ts b/ui/src/app/metadata/configuration/service/history.service.spec.ts index 0e8bc77a4..0927f8e70 100644 --- a/ui/src/app/metadata/configuration/service/history.service.spec.ts +++ b/ui/src/app/metadata/configuration/service/history.service.spec.ts @@ -1,7 +1,10 @@ import { TestBed, async, inject } from '@angular/core/testing'; -import { HttpClientModule } from '@angular/common/http'; +import { HttpClientModule, HttpRequest } from '@angular/common/http'; import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing'; import { MetadataHistoryService } from './history.service'; +import { of } from 'rxjs'; +import { PATHS } from '../configuration.values'; +import { Metadata } from '../../domain/domain.type'; describe(`Attributes Service`, () => { beforeEach(() => { @@ -25,4 +28,73 @@ describe(`Attributes Service`, () => { } ))); }); + + describe('getVersions method', () => { + it(`should join a list of observables`, async(inject([MetadataHistoryService, HttpTestingController], + (service: MetadataHistoryService) => { + spyOn(service, 'getVersion').and.returnValue(of()); + service.getVersions('foo', ['abc', 'def'], 'resolver').subscribe(history => { + expect(service.getVersion).toHaveBeenCalledTimes(2); + }); + } + ))); + }); + + describe('getVersion method', () => { + it(`should get the primary version of the resource`, async(inject([MetadataHistoryService, HttpTestingController], + (service: MetadataHistoryService, backend: HttpTestingController) => { + const resourceId = 'foo'; + const type = 'resource'; + service.getVersion(resourceId, type).subscribe(); + backend.expectOne((req: HttpRequest) => { + return req.url === `/${service.base}/${PATHS[type]}/${resourceId}` + && req.method === 'GET'; + }, `GET schema by path`); + } + ))); + it(`should get the provided version of the resource`, async(inject([MetadataHistoryService, HttpTestingController], + (service: MetadataHistoryService, backend: HttpTestingController) => { + const resourceId = 'foo'; + const type = 'resource'; + const versionId = '1'; + service.getVersion(resourceId, type, versionId).subscribe(); + backend.expectOne((req: HttpRequest) => { + return req.url === `/${service.base}/${PATHS[type]}/${resourceId}/${service.path}/${versionId}` + && req.method === 'GET'; + }, `GET schema by path`); + } + ))); + }); + + describe('updateVersion method', () => { + it(`should send a put request`, async(inject([MetadataHistoryService, HttpTestingController], + (service: MetadataHistoryService, backend: HttpTestingController) => { + const resourceId = 'foo'; + const type = 'resource'; + const versionId = '1'; + service.updateVersion(resourceId, type, {} as Metadata).subscribe(); + backend.expectOne((req: HttpRequest) => { + return req.url === `/${service.base}/${PATHS[type]}/${resourceId}` + && req.method === 'PUT'; + }, `PUT schema by path`); + } + ))); + }); + + describe('restoreVersion method', () => { + it(`should send a put request`, async(inject([MetadataHistoryService, HttpTestingController], + (service: MetadataHistoryService, backend: HttpTestingController) => { + const resourceId = 'foo'; + const type = 'resource'; + const versionId = '1'; + const response = {version: 'bar'} as Metadata; + spyOn(service, 'getVersions').and.returnValue(of([response, { ...response, version: 'foo' }])); + spyOn(service, 'updateVersion').and.returnValue(of(response)); + service.restoreVersion(resourceId, type, versionId).subscribe(updated => { + expect(service.getVersions).toHaveBeenCalled(); + expect(service.updateVersion).toHaveBeenCalled(); + }); + } + ))); + }); }); From 76dcaa6d8dbbfb16803ea3b0b04d4c4ac8e08df3 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Tue, 13 Aug 2019 08:27:44 -0700 Subject: [PATCH 05/27] Fixed unit test --- .../service/configuration.service.spec.ts | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/ui/src/app/metadata/configuration/service/configuration.service.spec.ts b/ui/src/app/metadata/configuration/service/configuration.service.spec.ts index 40bfb39ac..43fd99c05 100644 --- a/ui/src/app/metadata/configuration/service/configuration.service.spec.ts +++ b/ui/src/app/metadata/configuration/service/configuration.service.spec.ts @@ -7,10 +7,14 @@ import { MetadataSourceEditor } from '../../domain/model/wizards/metadata-source import { ResolverService } from '../../domain/service/resolver.service'; import { of } from 'rxjs'; import { MetadataProviderService } from '../../domain/service/provider.service'; +import { Metadata } from '../../domain/domain.type'; +import { SCHEMA } from '../../../../testing/form-schema.stub'; +import { getConfigurationSectionsFn } from '../reducer'; describe(`Configuration Service`, () => { let resolverService: any; + let providerService: any; let mockService = { find: () => of([]) @@ -36,11 +40,12 @@ describe(`Configuration Service`, () => { }); resolverService = TestBed.get(ResolverService); + providerService = TestBed.get(MetadataProviderService); }); describe('find method', () => { - it(`should send an expected GET request`, async(inject([MetadataConfigurationService, HttpTestingController], + it(`should call the resolver service when type is resolver`, async(inject([MetadataConfigurationService, HttpTestingController], (service: MetadataConfigurationService, backend: HttpTestingController) => { spyOn(resolverService, 'find').and.callThrough(); const type = 'resolver'; @@ -49,6 +54,25 @@ describe(`Configuration Service`, () => { expect(resolverService.find).toHaveBeenCalledWith(id); } ))); + it(`should call the provider service when type is resolver`, async(inject([MetadataConfigurationService, HttpTestingController], + (service: MetadataConfigurationService, backend: HttpTestingController) => { + spyOn(providerService, 'find').and.callThrough(); + const type = 'provider'; + const id = 'foo'; + service.find(id, type).subscribe(); + expect(providerService.find).toHaveBeenCalledWith(id); + } + ))); + it(`should throw an error when a type is not found`, async(inject([MetadataConfigurationService, HttpTestingController], + (service: MetadataConfigurationService, backend: HttpTestingController) => { + spyOn(providerService, 'find').and.callThrough(); + const type = 'bar'; + const id = 'foo'; + service.find(id, type).subscribe(null, (err) => { + expect(err).toEqual(new Error('Type not supported')); + }); + } + ))); }); describe('loadSchema method', () => { @@ -79,4 +103,15 @@ describe(`Configuration Service`, () => { } ))); }); + + describe('getMetadataConfiguration method', () => { + it('should return the parsed configuration', async(inject([MetadataConfigurationService], + (service: MetadataConfigurationService) => { + const model = {} as Metadata; + const definition = {steps: []}; + const expected = getConfigurationSectionsFn([model], definition, SCHEMA); + expect(service.getMetadataConfiguration(model, definition, SCHEMA)).toEqual(expected); + } + ))); + }); }); From be608918c13217ef5f73ff7209f77d9d0e669ca2 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Wed, 14 Aug 2019 13:28:57 -0700 Subject: [PATCH 06/27] Updated i18n tags --- .../main/resources/i18n/messages.properties | 5 +++++ .../component/history-list.component.html | 2 +- .../configuration/configuration.module.ts | 6 ++++-- .../container/restore.component.html | 20 +++++++++++++------ .../container/restore.component.ts | 11 +++++++--- 5 files changed, 32 insertions(+), 12 deletions(-) diff --git a/backend/src/main/resources/i18n/messages.properties b/backend/src/main/resources/i18n/messages.properties index f82153ce6..182a2491e 100644 --- a/backend/src/main/resources/i18n/messages.properties +++ b/backend/src/main/resources/i18n/messages.properties @@ -55,6 +55,7 @@ action.xml=XML action.manage=Manage action.close=Close action.back-to-top=Back to Top +action.restore=Restore value.enabled=Enabled value.disabled=Disabled @@ -416,6 +417,7 @@ label.check-to-select=Check to select label.current=Current label.restore=Restore label.compare-selected=Compare Selected +label.restore-version=Restore Version ({ date }) label.saved=Saved label.by=By @@ -471,6 +473,9 @@ message.database-constraint=There was a database constraint problem processing t message.no-filters=No Filters message.no-filters-added=No filters have been added to this Metadata Provider +message.create-new-version-from-version=Create New Version from Version ({ date }) Settings +message.restoring-this-version-will-copy=Restoring this version will copy the Version ({ date }) configuration and create a new Version from the selected version settings. You can then edit the configuration before saving the new version. + tooltip.entity-id=Entity ID tooltip.service-provider-name=Service Provider Name (Dashboard Display Only) tooltip.force-authn=Disallows use (or reuse) of authentication results and login flows that don\u0027t provide a real-time proof of user presence in the login process diff --git a/ui/src/app/metadata/configuration/component/history-list.component.html b/ui/src/app/metadata/configuration/component/history-list.component.html index ff4599b91..168d0cf19 100644 --- a/ui/src/app/metadata/configuration/component/history-list.component.html +++ b/ui/src/app/metadata/configuration/component/history-list.component.html @@ -34,7 +34,7 @@ diff --git a/ui/src/app/metadata/configuration/configuration.module.ts b/ui/src/app/metadata/configuration/configuration.module.ts index c55e24051..906bed24d 100644 --- a/ui/src/app/metadata/configuration/configuration.module.ts +++ b/ui/src/app/metadata/configuration/configuration.module.ts @@ -1,5 +1,5 @@ import { NgModule, ModuleWithProviders } from '@angular/core'; -import { CommonModule } from '@angular/common'; +import { CommonModule, DatePipe } from '@angular/common'; import { StoreModule } from '@ngrx/store'; import { EffectsModule } from '@ngrx/effects'; import { RouterModule } from '@angular/router'; @@ -63,7 +63,9 @@ import { RestoreVersionEffects } from './effect/restore.effect'; SharedModule ], exports: [], - providers: [] + providers: [ + DatePipe + ] }) export class MetadataConfigurationModule { static forRoot(): ModuleWithProviders { diff --git a/ui/src/app/metadata/configuration/container/restore.component.html b/ui/src/app/metadata/configuration/container/restore.component.html index 0b5a2f1ac..66f994d1d 100644 --- a/ui/src/app/metadata/configuration/container/restore.component.html +++ b/ui/src/app/metadata/configuration/container/restore.component.html @@ -1,14 +1,22 @@

- Restore Version ({{ date$ | async | date:DATE_FORMAT }}) + + Restore Version ( date ) +

-

Create New Version from Version ({{ date$ | async | date:DATE_FORMAT }}) Settings

-

Restoring this version will copy the Version ({{ date$ | async | date:DATE_FORMAT }}) configuration and create a new Version from the - selected version settings. You can then edit the configuration before saving the new version.

- Cancel  - +

+ Create New Version from Version ( date ) Settings +

+

+ Restoring this version will copy the Version ( date ) configuration and create a new Version from the selected version settings. You can then edit the configuration before saving the new version. +

+ Cancel  +
diff --git a/ui/src/app/metadata/configuration/container/restore.component.ts b/ui/src/app/metadata/configuration/container/restore.component.ts index 69c6a6f18..388c5f8d8 100644 --- a/ui/src/app/metadata/configuration/container/restore.component.ts +++ b/ui/src/app/metadata/configuration/container/restore.component.ts @@ -7,6 +7,7 @@ import * as fromConfiguration from '../reducer'; import { CONFIG_DATE_FORMAT } from '../configuration.values'; import { RestoreVersionRequest } from '../action/restore.action'; import { withLatestFrom, map, takeUntil } from 'rxjs/operators'; +import { DatePipe } from '@angular/common'; @Component({ selector: 'restore-component', @@ -20,11 +21,11 @@ export class RestoreComponent implements OnDestroy { restore$ = this.subj.asObservable(); date$ = this.store.select(fromConfiguration.getConfigurationVersionDate); - - DATE_FORMAT = CONFIG_DATE_FORMAT; + date: string; constructor( - private store: Store + private store: Store, + private datePipe: DatePipe ) { this.restore$.pipe( withLatestFrom( @@ -34,6 +35,10 @@ export class RestoreComponent implements OnDestroy { ), map(([restore, version, type, id]) => new RestoreVersionRequest({ id, type, version })) ).subscribe(this.store); + + this.date$.pipe(takeUntil(this.subj)).subscribe( + (date) => this.date = this.datePipe.transform(date, CONFIG_DATE_FORMAT) + ); } restore() { From c4370aa4b75153d09edea7b26829a27160c8ff18 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Wed, 14 Aug 2019 22:15:32 -0700 Subject: [PATCH 07/27] SHIBUI-1361 Updated tests to check date against a regex. And they finally work, this time. --- .../integration/resources/SHIBUI-1361.side | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/backend/src/integration/resources/SHIBUI-1361.side b/backend/src/integration/resources/SHIBUI-1361.side index 2248d351a..c236ea816 100644 --- a/backend/src/integration/resources/SHIBUI-1361.side +++ b/backend/src/integration/resources/SHIBUI-1361.side @@ -7,6 +7,13 @@ "id": "1e7a4ca4-b2e6-4e5a-8e38-823ae8a1e20d", "name": "SHIBUI-1361", "commands": [{ + "id": "58401038-5239-4643-b370-d7012a6f9486", + "comment": "", + "command": "store", + "target": "^[A-Z][a-z]{2}\\\\\\s\\\\\\d\\\\\\d?,\\\\\\s\\\\\\d{4}\\\\\\s\\\\\\d{2}:\\\\\\d{2}:\\\\\\d{2}$", + "targets": [], + "value": "dateRegex" + }, { "id": "29bb3186-81c8-4b3e-a9d9-5ff55d225878", "comment": "", "command": "open", @@ -582,18 +589,13 @@ "comment": "", "command": "storeText", "target": "css=.save-date", - "targets": [ - ["css=.save-date", "css:finder"], - ["xpath=//div[@id='header']/metadata-header/div/div/h5/span", "xpath:idRelative"], - ["xpath=//h5/span", "xpath:position"], - ["xpath=//span[contains(.,'Aug 08, 2019 15:50:04')]", "xpath:innerText"] - ], + "targets": [], "value": "saveDate" }, { "id": "a7b73d1f-87bb-4364-944a-5097f981b8c9", "comment": "", "command": "executeScript", - "target": "return (new Date(${saveDate}) !== \"Invalid Date\" && !isNaN(new Date(${saveDate})))", + "target": "return RegExp(${dateRegex}).test(${saveDate});", "targets": [], "value": "isDate" }, { @@ -979,7 +981,7 @@ "id": "910d8bf9-4875-4946-9ab8-0d3a9b13f020", "comment": "", "command": "executeScript", - "target": "return (new Date(${saveDate}) !== \"Invalid Date\" && !isNaN(new Date(${saveDate})))", + "target": "return RegExp(${dateRegex}).test(${saveDate});", "targets": [], "value": "isDate" }, { @@ -1004,7 +1006,7 @@ "id": "a370624e-a6d6-43d7-a74a-eeb8c577fc06", "comment": "", "command": "executeScript", - "target": "return (new Date(${saveDate}) !== \"Invalid Date\" && !isNaN(new Date(${saveDate})))", + "target": "return RegExp(${dateRegex}).test(${saveDate});", "targets": [], "value": "isDate" }, { @@ -1029,7 +1031,7 @@ "id": "4b305857-0369-4fad-821c-c944e9801395", "comment": "", "command": "executeScript", - "target": "return (new Date(${saveDate}) !== \"Invalid Date\" && !isNaN(new Date(${saveDate})))", + "target": "return RegExp(${dateRegex}).test(${saveDate});", "targets": [], "value": "isDate" }, { @@ -1134,7 +1136,7 @@ "id": "c1fda79e-9fa9-4969-92f5-83f9f54b9d3d", "comment": "", "command": "executeScript", - "target": "return (new Date(${leftDate}) !== \"Invalid Date\" && !isNaN(new Date(${leftDate})))", + "target": "return RegExp(${dateRegex}).test(${leftDate});", "targets": [], "value": "leftDateIsDate" }, { @@ -1159,7 +1161,7 @@ "id": "dd3a52a4-de8b-4c25-a6e7-81b7ee9f8149", "comment": "", "command": "executeScript", - "target": "return (new Date(${rightDate}) !== \"Invalid Date\" && !isNaN(new Date(${rightDate})))", + "target": "return RegExp(${dateRegex}).test(${rightDate});", "targets": [], "value": "rightDateIsDate" }, { From 83157c9b89abaec950442c352feb11ad17e8acc9 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Thu, 15 Aug 2019 00:38:11 -0700 Subject: [PATCH 08/27] SHIBUI-1361 More attempts at getting versions to persist and dates to compare. --- .../integration/resources/SHIBUI-1361.side | 186 +++++++++--------- 1 file changed, 91 insertions(+), 95 deletions(-) diff --git a/backend/src/integration/resources/SHIBUI-1361.side b/backend/src/integration/resources/SHIBUI-1361.side index c236ea816..8185b484e 100644 --- a/backend/src/integration/resources/SHIBUI-1361.side +++ b/backend/src/integration/resources/SHIBUI-1361.side @@ -10,7 +10,7 @@ "id": "58401038-5239-4643-b370-d7012a6f9486", "comment": "", "command": "store", - "target": "^[A-Z][a-z]{2}\\\\\\s\\\\\\d\\\\\\d?,\\\\\\s\\\\\\d{4}\\\\\\s\\\\\\d{2}:\\\\\\d{2}:\\\\\\d{2}$", + "target": "^[A-Z][a-z]{2} [0-9][0-9]?, [0-9]{4} [0-9]{2}:[0-9]{2}:[0-9]{2}(.*)?$", "targets": [], "value": "dateRegex" }, { @@ -488,24 +488,16 @@ }, { "id": "52f5680c-c63d-411e-8332-52901f12ea3b", "comment": "", - "command": "verifyText", + "command": "assertText", "target": "css=.px-3:nth-child(1) > summary-property:nth-child(2) > .mb-3 > .d-block:nth-child(2)", - "targets": [ - ["css=.px-3:nth-child(1) > summary-property:nth-child(2) > .mb-3 > .d-block:nth-child(2)", "css:finder"], - ["xpath=//summary-property/div/span", "xpath:position"], - ["xpath=//span[contains(.,'Test Metadata Provider')]", "xpath:innerText"] - ], + "targets": [], "value": "Test Metadata Provider" }, { "id": "67ff44aa-1efd-4b13-a0e9-3648a09911dd", "comment": "", - "command": "verifyText", + "command": "assertText", "target": "css=.px-3:nth-child(2) > summary-property:nth-child(2) > .mb-3 > .d-block:nth-child(2)", - "targets": [ - ["css=.px-3:nth-child(2) > summary-property:nth-child(2) > .mb-3 > .d-block:nth-child(2)", "css:finder"], - ["xpath=//section[2]/summary-property/div/span", "xpath:position"], - ["xpath=//span[contains(.,'123')]", "xpath:innerText"] - ], + "targets": [], "value": "123" }, { "id": "f5197d46-41a7-4ef2-ac40-19f80c953929", @@ -530,38 +522,23 @@ }, { "id": "f48dd1b5-607a-485b-b1d5-03301f99b9ce", "comment": "", - "command": "verifyText", + "command": "assertText", "target": "linkText=Test Metadata Provider", - "targets": [ - ["linkText=Test Metadata Provider", "linkText"], - ["css=td > a", "css:finder"], - ["xpath=//a[contains(text(),'Test Metadata Provider')]", "xpath:link"], - ["xpath=//a[contains(@href, '/metadata/provider/4cc9924f-fd9e-4ef3-bf57-3b24eec9f27f/configuration/options')]", "xpath:href"], - ["xpath=//td[2]/a", "xpath:position"], - ["xpath=//a[contains(.,'Test Metadata Provider')]", "xpath:innerText"] - ], + "targets": [], "value": "Test Metadata Provider" }, { "id": "5eccd4e9-a451-4ec8-a9e1-1e1f8e771677", "comment": "", - "command": "verifyText", + "command": "assertText", "target": "css=td:nth-child(3)", - "targets": [ - ["css=td:nth-child(3)", "css:finder"], - ["xpath=//td[3]", "xpath:position"], - ["xpath=//td[contains(.,'FileBackedHttpMetadataResolver')]", "xpath:innerText"] - ], + "targets": [], "value": "FileBackedHttpMetadataResolver" }, { "id": "c768c2c1-09d4-46fe-8007-42fb4b3f4aaa", "comment": "", - "command": "verifyText", + "command": "assertText", "target": "css=td:nth-child(4)", - "targets": [ - ["css=td:nth-child(4)", "css:finder"], - ["xpath=//td[4]", "xpath:position"], - ["xpath=//td[contains(.,'admin')]", "xpath:innerText"] - ], + "targets": [], "value": "admin" }, { "id": "8283ffc5-d46c-4a07-b95e-1534dfd34c02", @@ -601,7 +578,7 @@ }, { "id": "d54fd23e-e330-48d3-a1f4-31786b087425", "comment": "", - "command": "verify", + "command": "assert", "target": "isDate", "targets": [], "value": "true" @@ -637,6 +614,13 @@ ["xpath=//custom-string/div/input", "xpath:position"] ], "value": "123 v2" + }, { + "id": "ebbac6ca-c3d4-486c-82f6-680842c286eb", + "comment": "", + "command": "pause", + "target": "2000", + "targets": [], + "value": "" }, { "id": "247b04c1-e690-42bc-ae15-dfbedb44d2e9", "comment": "", @@ -649,6 +633,13 @@ ["xpath=//div[10]/sf-form-element/div/sf-widget-chooser/boolean-radio/div/div[2]/label/input", "xpath:position"] ], "value": "" + }, { + "id": "6806c391-11a9-4f40-8a73-d57ab3304c11", + "comment": "", + "command": "pause", + "target": "2000", + "targets": [], + "value": "" }, { "id": "5aacc6b8-b509-4405-8755-c60fa3619e67", "comment": "", @@ -660,6 +651,13 @@ ["xpath=//button[contains(.,'Save')]", "xpath:innerText"] ], "value": "" + }, { + "id": "7e42b8b2-cc31-4fb5-bcf8-77d8068e2e04", + "comment": "", + "command": "pause", + "target": "2000", + "targets": [], + "value": "" }, { "id": "cf005a45-74ae-4f7a-a53c-ed59d63f7114", "comment": "", @@ -674,6 +672,13 @@ ["xpath=//a[contains(.,'Test Metadata Provider')]", "xpath:innerText"] ], "value": "" + }, { + "id": "8e00ca0d-61e3-413d-8422-a09232049bab", + "comment": "", + "command": "pause", + "target": "2000", + "targets": [], + "value": "" }, { "id": "ea880e6a-498a-427a-bc08-8058137d62e0", "comment": "", @@ -686,6 +691,17 @@ ["xpath=//translate-i18n[contains(.,'Version History')]", "xpath:innerText"] ], "value": "" + }, { + "id": "62211421-e36c-426c-80ef-be10ff259d87", + "comment": "", + "command": "waitForElementVisible", + "target": "css=.btn-text", + "targets": [ + ["css=.btn-text", "css:finder"], + ["xpath=//td[4]/button", "xpath:position"], + ["xpath=//button[contains(.,'  Restore')]", "xpath:innerText"] + ], + "value": "10000" }, { "id": "c223e329-733c-41eb-9e85-c6f69a840179", "comment": "", @@ -728,43 +744,30 @@ }, { "id": "68103f0e-e3f1-419b-903d-5d8c30bc6700", "comment": "", - "command": "verifyText", + "command": "assertText", "target": "css=.d-block > primitive-property:nth-child(4) .d-block:nth-child(2)", - "targets": [ - ["css=.d-block > primitive-property:nth-child(4) .d-block:nth-child(2)", "css:finder"], - ["xpath=//primitive-property[4]/div/span[2]", "xpath:position"], - ["xpath=//span[contains(.,'123 v2')]", "xpath:innerText"] - ], + "targets": [], "value": "123 v2" }, { "id": "a13198b9-7827-47e2-ade3-143b981d34ee", "comment": "", - "command": "verifyText", + "command": "assertText", "target": "css=.d-block > primitive-property:nth-child(4) .d-block:nth-child(3)", - "targets": [ - ["css=.d-block > primitive-property:nth-child(4) .d-block:nth-child(3)", "css:finder"], - ["xpath=//primitive-property[4]/div/span[3]", "xpath:position"] - ], + "targets": [], "value": "123" }, { "id": "3d83741c-299e-4fcb-9ce6-4b7291c423a4", "comment": "", - "command": "verifyText", + "command": "assertText", "target": "css=.d-block > primitive-property:nth-child(12) .d-block:nth-child(2)", - "targets": [ - ["css=.d-block > primitive-property:nth-child(12) .d-block:nth-child(2)", "css:finder"], - ["xpath=//primitive-property[12]/div/span[2]", "xpath:position"] - ], + "targets": [], "value": "false" }, { "id": "190cc157-d427-4046-aff2-180392395ad3", "comment": "", - "command": "verifyText", + "command": "assertText", "target": "css=.d-block > primitive-property:nth-child(12) .d-block:nth-child(3)", - "targets": [ - ["css=.d-block > primitive-property:nth-child(12) .d-block:nth-child(3)", "css:finder"], - ["xpath=//primitive-property[12]/div/span[3]", "xpath:position"] - ], + "targets": [], "value": "true" }, { "id": "f6897c4f-763f-4360-95a2-1d144455fa17", @@ -959,22 +962,25 @@ "id": "ca6537f7-73d1-4f02-ba06-c24f66c569e3", "comment": "", "command": "waitForElementVisible", - "target": "css=tr:nth-child(1) > td:nth-child(3)", + "target": "css=tr:nth-child(1) a", "targets": [ - ["css=tr:nth-child(1) > td:nth-child(3)", "css:finder"], - ["xpath=//td[3]", "xpath:position"], - ["xpath=//td[contains(.,'Aug 08, 2019 16:10:01')]", "xpath:innerText"] + ["css=tr:nth-child(1) a", "css:finder"], + ["xpath=//a[contains(@href, '/metadata/provider/a9294f9e-2e19-4e87-9157-11d86a32f2f9/configuration/options?version=64')]", "xpath:href"], + ["xpath=//td[2]/a", "xpath:position"] ], "value": "10000" }, { "id": "6620688a-039a-4969-92be-b1fcfc8a4161", "comment": "", "command": "storeText", - "target": "css=tr:nth-child(1) > td:nth-child(3)", + "target": "css=tr:nth-child(1) a", "targets": [ - ["css=tr:nth-child(1) > td:nth-child(3)", "css:finder"], - ["xpath=//td[3]", "xpath:position"], - ["xpath=//td[contains(.,'Aug 08, 2019 16:10:01')]", "xpath:innerText"] + ["linkText=Aug 14, 2019 22:52:11", "linkText"], + ["css=tr:nth-child(2) a", "css:finder"], + ["xpath=//a[contains(text(),'Aug 14, 2019 22:52:11')]", "xpath:link"], + ["xpath=//a[contains(@href, '/metadata/provider/1552c0d4-29ea-4dd2-9296-9bf0280157fa/configuration/options?version=33')]", "xpath:href"], + ["xpath=//tr[2]/td[2]/a", "xpath:position"], + ["xpath=//a[contains(.,'Aug 14, 2019 22:52:11 ')]", "xpath:innerText"] ], "value": "saveDate" }, { @@ -987,7 +993,7 @@ }, { "id": "18458e4b-e414-4125-aa4d-fd27848d3c40", "comment": "", - "command": "verify", + "command": "assert", "target": "isDate", "targets": [], "value": "true" @@ -995,11 +1001,14 @@ "id": "48adfc53-ed95-4e59-aa57-45eb57128154", "comment": "", "command": "storeText", - "target": "css=tr:nth-child(2) > td:nth-child(3)", + "target": "css=tr:nth-child(2) a", "targets": [ - ["css=tr:nth-child(2) > td:nth-child(3)", "css:finder"], - ["xpath=//tr[2]/td[3]", "xpath:position"], - ["xpath=//td[contains(.,'Aug 08, 2019 16:09:55')]", "xpath:innerText"] + ["linkText=Aug 14, 2019 22:51:54", "linkText"], + ["css=tr:nth-child(3) a", "css:finder"], + ["xpath=//a[contains(text(),'Aug 14, 2019 22:51:54')]", "xpath:link"], + ["xpath=//a[contains(@href, '/metadata/provider/1552c0d4-29ea-4dd2-9296-9bf0280157fa/configuration/options?version=32')]", "xpath:href"], + ["xpath=//tr[3]/td[2]/a", "xpath:position"], + ["xpath=//a[contains(.,'Aug 14, 2019 22:51:54 ')]", "xpath:innerText"] ], "value": "saveDate" }, { @@ -1012,7 +1021,7 @@ }, { "id": "21e88ab8-7f11-42ae-8db8-ccc21116c303", "comment": "", - "command": "verify", + "command": "assert", "target": "isDate", "targets": [], "value": "true" @@ -1020,7 +1029,7 @@ "id": "2a299cc4-7b22-42e6-89cd-4ae0aef8bb27", "comment": "", "command": "storeText", - "target": "css=tr:nth-child(3) > td:nth-child(3)", + "target": "css=tr:nth-child(3) a", "targets": [ ["css=tr:nth-child(3) > td:nth-child(3)", "css:finder"], ["xpath=//tr[3]/td[3]", "xpath:position"], @@ -1037,7 +1046,7 @@ }, { "id": "3fd48668-3df2-4264-85c9-01dac26acaee", "comment": "", - "command": "verify", + "command": "assert", "target": "isDate", "targets": [], "value": "true" @@ -1083,43 +1092,30 @@ }, { "id": "8a2c6435-d77b-4c87-8e0e-e1ae6d4629b9", "comment": "", - "command": "verifyText", + "command": "assertText", "target": "css=.mb-4:nth-child(4) primitive-property:nth-child(2) .d-block:nth-child(2)", - "targets": [ - ["css=.mb-4:nth-child(4) primitive-property:nth-child(2) .d-block:nth-child(2)", "css:finder"], - ["xpath=//section[4]/div/div[2]/object-property/object-property/primitive-property[2]/div/span[2]", "xpath:position"] - ], + "targets": [], "value": "PT30S" }, { "id": "0687fe3f-c1af-4a5a-9708-09b7b92734bc", "comment": "", - "command": "verifyText", + "command": "assertText", "target": "css=.mb-4:nth-child(4) primitive-property:nth-child(3) .d-block:nth-child(2)", - "targets": [ - ["css=.mb-4:nth-child(4) primitive-property:nth-child(3) .d-block:nth-child(2)", "css:finder"], - ["xpath=//section[4]/div/div[2]/object-property/object-property/primitive-property[3]/div/span[2]", "xpath:position"], - ["xpath=//span[contains(.,'PT1M')]", "xpath:innerText"] - ], + "targets": [], "value": "PT1M" }, { "id": "a007794f-ab91-47b8-b470-e4294e47a2a7", "comment": "", - "command": "verifyText", + "command": "assertText", "target": "css=object-property:nth-child(1) > primitive-property:nth-child(4) .d-block:nth-child(2)", - "targets": [ - ["css=object-property:nth-child(1) > primitive-property:nth-child(4) .d-block:nth-child(2)", "css:finder"], - ["xpath=//object-property/object-property/primitive-property[4]/div/span[2]", "xpath:position"] - ], + "targets": [], "value": "PT10M" }, { "id": "5279bf64-46c1-4376-a527-e19e217b42fa", "comment": "", - "command": "verifyText", + "command": "assertText", "target": "css=object-property:nth-child(1) > primitive-property:nth-child(5) .d-block:nth-child(2)", - "targets": [ - ["css=object-property:nth-child(1) > primitive-property:nth-child(5) .d-block:nth-child(2)", "css:finder"], - ["xpath=//object-property/object-property/primitive-property[5]/div/span[2]", "xpath:position"] - ], + "targets": [], "value": "false" }, { "id": "cc2f6596-d135-48ef-abab-8f2c4bf44a2c", @@ -1142,7 +1138,7 @@ }, { "id": "30e8b9fb-254d-462e-9039-6b378ff4d245", "comment": "", - "command": "verify", + "command": "assert", "target": "leftDateIsDate", "targets": [], "value": "true" @@ -1167,7 +1163,7 @@ }, { "id": "d973102f-964b-4021-93f4-44adf165b33e", "comment": "", - "command": "verify", + "command": "assert", "target": "rightDateIsDate", "targets": [], "value": "true" From 2eb54a05657872a327f58ea1d69496bb3b1a77a7 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Thu, 15 Aug 2019 10:07:11 -0700 Subject: [PATCH 09/27] Refactored version display --- .../main/resources/i18n/messages.properties | 1 + .../configuration/action/restore.action.ts | 43 +++--------- .../configuration/action/version.action.ts | 40 +++++++++++ .../filter-configuration-list.component.ts | 6 +- .../component/history-list.component.html | 12 ++-- .../metadata-configuration.component.html | 3 +- .../component/metadata-header.component.html | 5 +- .../component/metadata-header.component.ts | 6 +- .../configuration/configuration.module.ts | 12 +++- .../configuration/configuration.routing.ts | 16 ++++- .../container/configuration.component.ts | 33 ++------- .../metadata-comparison.component.ts | 6 +- .../container/metadata-history.component.ts | 37 +++++++--- .../container/metadata-options.component.html | 5 +- .../container/metadata-options.component.ts | 46 +++--------- .../container/restore.component.html | 8 +-- .../container/restore.component.ts | 37 +++------- .../container/version-options.component.html | 43 ++++++++++++ .../container/version-options.component.ts | 70 +++++++++++++++++++ .../container/version.component.html | 1 + .../container/version.component.ts | 42 +++++++++++ .../configuration/effect/restore.effect.ts | 68 +++++++++++------- .../configuration/effect/version.effect.ts | 32 +++++++++ .../metadata/configuration/model/request.ts | 5 ++ .../metadata/configuration/reducer/index.ts | 33 +++++---- ...educer.spec.ts => version.reducer.spec.ts} | 0 ...{restore.reducer.ts => version.reducer.ts} | 14 ++-- .../configuration/service/history.service.ts | 15 +--- 28 files changed, 421 insertions(+), 218 deletions(-) create mode 100644 ui/src/app/metadata/configuration/action/version.action.ts create mode 100644 ui/src/app/metadata/configuration/container/version-options.component.html create mode 100644 ui/src/app/metadata/configuration/container/version-options.component.ts create mode 100644 ui/src/app/metadata/configuration/container/version.component.html create mode 100644 ui/src/app/metadata/configuration/container/version.component.ts create mode 100644 ui/src/app/metadata/configuration/effect/version.effect.ts create mode 100644 ui/src/app/metadata/configuration/model/request.ts rename ui/src/app/metadata/configuration/reducer/{restore.reducer.spec.ts => version.reducer.spec.ts} (100%) rename ui/src/app/metadata/configuration/reducer/{restore.reducer.ts => version.reducer.ts} (65%) diff --git a/backend/src/main/resources/i18n/messages.properties b/backend/src/main/resources/i18n/messages.properties index 182a2491e..ef48de5a2 100644 --- a/backend/src/main/resources/i18n/messages.properties +++ b/backend/src/main/resources/i18n/messages.properties @@ -60,6 +60,7 @@ action.restore=Restore value.enabled=Enabled value.disabled=Disabled value.current=Current +value.not-current=Not Current value.none=None value.file=File value.memory=Memory diff --git a/ui/src/app/metadata/configuration/action/restore.action.ts b/ui/src/app/metadata/configuration/action/restore.action.ts index 290367054..7febcd2e8 100644 --- a/ui/src/app/metadata/configuration/action/restore.action.ts +++ b/ui/src/app/metadata/configuration/action/restore.action.ts @@ -1,27 +1,19 @@ import { Action } from '@ngrx/store'; import { Metadata } from '../../domain/domain.type'; +import { VersionRequest } from '../model/request'; export enum RestoreActionTypes { - SELECT_VERSION_SUCCESS = '[Restore Version] Select Version Success', - SELECT_VERSION_ERROR = '[Restore Version] Select Version Error', - SELECT_VERSION_REQUEST = '[Restore Version] Select Version Request', - RESTORE_VERSION_REQUEST = '[Restore Version] Restore Version Request', RESTORE_VERSION_SUCCESS = '[Restore Version] Restore Version Success', RESTORE_VERSION_ERROR = '[Restore Version] Restore Version Error', - CLEAR_VERSION = '[Restore Version] Clear Versions' -} - -export interface VersionRequest { - type: string; - id: string; - version: string; + CLEAR_VERSION = '[Restore Version] Clear Versions', + CANCEL_RESTORE = '[Restore Version] Cancel Restore' } export class RestoreVersionRequest implements Action { readonly type = RestoreActionTypes.RESTORE_VERSION_REQUEST; - constructor(public payload: VersionRequest) { } + constructor() { } } export class RestoreVersionSuccess implements Action { @@ -34,32 +26,13 @@ export class RestoreVersionError implements Action { constructor(public payload: any) { } } -export class SelectVersionRestoreRequest implements Action { - readonly type = RestoreActionTypes.SELECT_VERSION_REQUEST; - - constructor(public payload: VersionRequest) { } -} - -export class SelectVersionRestoreSuccess implements Action { - readonly type = RestoreActionTypes.SELECT_VERSION_SUCCESS; - - constructor(public payload: Metadata) { } -} -export class SelectVersionRestoreError implements Action { - readonly type = RestoreActionTypes.SELECT_VERSION_ERROR; - - constructor(public payload: any) { } -} - -export class ClearVersionRestore implements Action { - readonly type = RestoreActionTypes.CLEAR_VERSION; +export class CancelRestore implements Action { + readonly type = RestoreActionTypes.CANCEL_RESTORE; + constructor() { } } export type RestoreActionsUnion = - | SelectVersionRestoreRequest - | SelectVersionRestoreError - | SelectVersionRestoreSuccess | RestoreVersionRequest | RestoreVersionSuccess | RestoreVersionError - | ClearVersionRestore; + | CancelRestore; diff --git a/ui/src/app/metadata/configuration/action/version.action.ts b/ui/src/app/metadata/configuration/action/version.action.ts new file mode 100644 index 000000000..0379a240b --- /dev/null +++ b/ui/src/app/metadata/configuration/action/version.action.ts @@ -0,0 +1,40 @@ +import { Action } from '@ngrx/store'; +import { Metadata } from '../../domain/domain.type'; +import { VersionRequest } from '../model/request'; + +export enum VersionActionTypes { + SELECT_VERSION_SUCCESS = '[Version] Select Version Success', + SELECT_VERSION_ERROR = '[Version] Select Version Error', + SELECT_VERSION_REQUEST = '[Version] Select Version Request', + + CLEAR_VERSION = '[Version] Clear Versions' +} + +export class SelectVersionRequest implements Action { + readonly type = VersionActionTypes.SELECT_VERSION_REQUEST; + + constructor(public payload: VersionRequest) { } +} + +export class SelectVersionSuccess implements Action { + readonly type = VersionActionTypes.SELECT_VERSION_SUCCESS; + + constructor(public payload: Metadata) { } +} +export class SelectVersionError implements Action { + readonly type = VersionActionTypes.SELECT_VERSION_ERROR; + + constructor(public payload: any) { } +} + +export class ClearVersion implements Action { + readonly type = VersionActionTypes.CLEAR_VERSION; + + constructor() { } +} + +export type VersionActionsUnion = + | SelectVersionRequest + | SelectVersionError + | SelectVersionSuccess + | ClearVersion; diff --git a/ui/src/app/metadata/configuration/component/filter-configuration-list.component.ts b/ui/src/app/metadata/configuration/component/filter-configuration-list.component.ts index 5208a7f4c..17f377480 100644 --- a/ui/src/app/metadata/configuration/component/filter-configuration-list.component.ts +++ b/ui/src/app/metadata/configuration/component/filter-configuration-list.component.ts @@ -1,8 +1,10 @@ -import { Component } from '@angular/core'; +import { Component, Input } from '@angular/core'; import { FilterListComponent } from '../../filter/component/filter-list.component'; @Component({ selector: 'filter-configuration-list', templateUrl: './filter-configuration-list.component.html' }) -export class FilterConfigurationListComponent extends FilterListComponent { } +export class FilterConfigurationListComponent extends FilterListComponent { + @Input() editable = true; +} diff --git a/ui/src/app/metadata/configuration/component/history-list.component.html b/ui/src/app/metadata/configuration/component/history-list.component.html index 168d0cf19..668308311 100644 --- a/ui/src/app/metadata/configuration/component/history-list.component.html +++ b/ui/src/app/metadata/configuration/component/history-list.component.html @@ -21,13 +21,11 @@ - - - {{ version.date | date:DATE_FORMAT }}  - - (Current) - - + + {{ version.date | date:DATE_FORMAT }} (Current) + + + {{ version.date | date:DATE_FORMAT }} {{ version.creator }} diff --git a/ui/src/app/metadata/configuration/component/metadata-configuration.component.html b/ui/src/app/metadata/configuration/component/metadata-configuration.component.html index f5ab2814b..f4826cfe6 100644 --- a/ui/src/app/metadata/configuration/component/metadata-configuration.component.html +++ b/ui/src/app/metadata/configuration/component/metadata-configuration.component.html @@ -1,8 +1,7 @@
-
+

diff --git a/ui/src/app/metadata/configuration/component/metadata-header.component.html b/ui/src/app/metadata/configuration/component/metadata-header.component.html index 64a1a2a2c..fee1cbae3 100644 --- a/ui/src/app/metadata/configuration/component/metadata-header.component.html +++ b/ui/src/app/metadata/configuration/component/metadata-header.component.html @@ -2,16 +2,17 @@
Saved:  - {{ version.date | date:'medium' }} + {{ version.modifiedDate | date:'medium' }}
By:  - {{ version.creator }} + {{ version.createdBy }}

Enabled Disabled   Current + Not Current

\ No newline at end of file diff --git a/ui/src/app/metadata/configuration/component/metadata-header.component.ts b/ui/src/app/metadata/configuration/component/metadata-header.component.ts index 8d0dcc3db..558353304 100644 --- a/ui/src/app/metadata/configuration/component/metadata-header.component.ts +++ b/ui/src/app/metadata/configuration/component/metadata-header.component.ts @@ -1,6 +1,5 @@ import { Component, Input } from '@angular/core'; -import { Metadata, MetadataTypes } from '../../domain/domain.type'; -import { MetadataVersion } from '../model/version'; +import { Metadata } from '../../domain/domain.type'; @Component({ selector: 'metadata-header', @@ -10,8 +9,7 @@ import { MetadataVersion } from '../model/version'; export class MetadataHeaderComponent { @Input() isEnabled: boolean; - @Input() version: MetadataVersion; - @Input() versionNumber: number; + @Input() version: Metadata; @Input() isCurrent: boolean; constructor() {} diff --git a/ui/src/app/metadata/configuration/configuration.module.ts b/ui/src/app/metadata/configuration/configuration.module.ts index 906bed24d..7294632dd 100644 --- a/ui/src/app/metadata/configuration/configuration.module.ts +++ b/ui/src/app/metadata/configuration/configuration.module.ts @@ -31,7 +31,10 @@ import { FilterConfigurationListItemComponent } from './component/filter-configu import { SharedModule } from '../../shared/shared.module'; import { FilterTargetPropertyComponent } from './component/filter-target-property.component'; import { RestoreComponent } from './container/restore.component'; -import { RestoreVersionEffects } from './effect/restore.effect'; +import { RestoreEffects } from './effect/restore.effect'; +import { VersionComponent } from './container/version.component'; +import { VersionOptionsComponent } from './container/version-options.component'; +import { VersionEffects } from './effect/version.effect'; @NgModule({ declarations: [ @@ -50,7 +53,9 @@ import { RestoreVersionEffects } from './effect/restore.effect'; FilterConfigurationListComponent, FilterConfigurationListItemComponent, FilterTargetPropertyComponent, - RestoreComponent + RestoreComponent, + VersionComponent, + VersionOptionsComponent ], entryComponents: [], imports: [ @@ -88,7 +93,8 @@ export class MetadataConfigurationModule { MetadataConfigurationEffects, MetadataHistoryEffects, CompareVersionEffects, - RestoreVersionEffects + RestoreEffects, + VersionEffects ]) ], providers: [] diff --git a/ui/src/app/metadata/configuration/configuration.routing.ts b/ui/src/app/metadata/configuration/configuration.routing.ts index dc3f5df99..ddf6c5c3c 100644 --- a/ui/src/app/metadata/configuration/configuration.routing.ts +++ b/ui/src/app/metadata/configuration/configuration.routing.ts @@ -5,6 +5,8 @@ import { MetadataXmlComponent } from './container/metadata-xml.component'; import { MetadataHistoryComponent } from './container/metadata-history.component'; import { MetadataComparisonComponent } from './container/metadata-comparison.component'; import { RestoreComponent } from './container/restore.component'; +import { VersionComponent } from './container/version.component'; +import { VersionOptionsComponent } from './container/version-options.component'; export const ConfigurationRoutes: Routes = [ { @@ -32,8 +34,18 @@ export const ConfigurationRoutes: Routes = [ component: MetadataComparisonComponent }, { - path: 'restore', - component: RestoreComponent + path: 'version/:version', + component: VersionComponent, + children: [ + { + path: 'options', + component: VersionOptionsComponent + }, + { + path: 'restore', + component: RestoreComponent + } + ] } ] } diff --git a/ui/src/app/metadata/configuration/container/configuration.component.ts b/ui/src/app/metadata/configuration/container/configuration.component.ts index 295f90257..c1e2daf35 100644 --- a/ui/src/app/metadata/configuration/container/configuration.component.ts +++ b/ui/src/app/metadata/configuration/container/configuration.component.ts @@ -1,13 +1,12 @@ -import { Component, ChangeDetectionStrategy, OnDestroy, HostListener } from '@angular/core'; -import { ActivatedRoute, Router, Scroll, Event } from '@angular/router'; -import { takeUntil, map, withLatestFrom, filter, timeout, delay } from 'rxjs/operators'; +import { Component, ChangeDetectionStrategy, OnDestroy } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { takeUntil, map } from 'rxjs/operators'; import { Store } from '@ngrx/store'; -import { Observable, Subject, interval, combineLatest } from 'rxjs'; +import { Observable, Subject } from 'rxjs'; import * as fromConfiguration from '../reducer'; import { ClearConfiguration, SetMetadata } from '../action/configuration.action'; -import { LoadHistoryRequest, ClearHistory, SelectVersion } from '../action/history.action'; import * as fromReducer from '../reducer'; @Component({ @@ -22,39 +21,20 @@ export class ConfigurationComponent implements OnDestroy { name$: Observable; type$: Observable; - hasVersion = ([collection, params]) => params.version || collection && collection.length > 0 ? collection[0].id : null; - constructor( private store: Store, private routerState: ActivatedRoute ) { - combineLatest( - this.routerState.params, - this.routerState.queryParams - ).pipe( + this.routerState.params.pipe( takeUntil(this.ngUnsubscribe), - map(([{ id, type }, { version }]) => new SetMetadata({ + map(({ id, type, version }) => new SetMetadata({ id, type, version })) ).subscribe(this.store); - this.routerState.params.pipe( - takeUntil(this.ngUnsubscribe), - map(params => new LoadHistoryRequest({ id: params.id, type: params.type })) - ).subscribe(store); - - this.store.select(fromReducer.getVersionCollection).pipe( - filter(collection => collection && collection.length > 0), - takeUntil(this.ngUnsubscribe), - withLatestFrom( - this.routerState.queryParams - ), - map(this.hasVersion) - ).subscribe(version => this.store.dispatch(new SelectVersion(version))); - this.name$ = this.store.select(fromReducer.getConfigurationModelName); this.type$ = this.store.select(fromReducer.getConfigurationModelType); } @@ -63,6 +43,5 @@ export class ConfigurationComponent implements OnDestroy { this.ngUnsubscribe.next(); this.ngUnsubscribe.complete(); this.store.dispatch(new ClearConfiguration()); - this.store.dispatch(new ClearHistory()); } } diff --git a/ui/src/app/metadata/configuration/container/metadata-comparison.component.ts b/ui/src/app/metadata/configuration/container/metadata-comparison.component.ts index cec29c2a6..dff593acd 100644 --- a/ui/src/app/metadata/configuration/container/metadata-comparison.component.ts +++ b/ui/src/app/metadata/configuration/container/metadata-comparison.component.ts @@ -3,7 +3,7 @@ import { Observable } from 'rxjs'; import { Store } from '@ngrx/store'; import { ActivatedRoute } from '@angular/router'; import { map } from 'rxjs/operators'; -import { ConfigurationState, getVersionConfigurations, getVersionConfigurationCount } from '../reducer'; +import { ConfigurationState, getComparisonConfigurations, getComparisonConfigurationCount } from '../reducer'; import { CompareVersionRequest } from '../action/compare.action'; import { MetadataConfiguration } from '../model/metadata-configuration'; import * as fromReducer from '../reducer'; @@ -29,8 +29,8 @@ export class MetadataComparisonComponent { map(versions => new CompareVersionRequest(versions)) ).subscribe(this.store); - this.versions$ = this.store.select(getVersionConfigurations); - this.numVersions$ = this.store.select(getVersionConfigurationCount); + this.versions$ = this.store.select(getComparisonConfigurations); + this.numVersions$ = this.store.select(getComparisonConfigurationCount); this.type$ = this.store.select(fromReducer.getConfigurationModelType); } } diff --git a/ui/src/app/metadata/configuration/container/metadata-history.component.ts b/ui/src/app/metadata/configuration/container/metadata-history.component.ts index 7d817b94f..ab3df19ee 100644 --- a/ui/src/app/metadata/configuration/container/metadata-history.component.ts +++ b/ui/src/app/metadata/configuration/container/metadata-history.component.ts @@ -1,11 +1,16 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; -import { Observable } from 'rxjs'; +import { Component, ChangeDetectionStrategy, OnDestroy } from '@angular/core'; +import { Observable, combineLatest, Subject } from 'rxjs'; import { Store } from '@ngrx/store'; -import { ConfigurationState, getVersionCollection } from '../reducer'; +import { + ConfigurationState, + getVersionCollection, + getConfigurationModelId, + getConfigurationModelKind +} from '../reducer'; import { MetadataVersion } from '../model/version'; -import { CompareVersionRequest } from '../action/compare.action'; import { Router, ActivatedRoute } from '@angular/router'; -import { map } from 'rxjs/operators'; +import { map, takeUntil } from 'rxjs/operators'; +import { LoadHistoryRequest, ClearHistory } from '../action/history.action'; @Component({ selector: 'metadata-history', @@ -13,7 +18,9 @@ import { map } from 'rxjs/operators'; templateUrl: './metadata-history.component.html', styleUrls: [] }) -export class MetadataHistoryComponent { +export class MetadataHistoryComponent implements OnDestroy { + + private ngUnsubscribe: Subject = new Subject(); history$: Observable; @@ -22,6 +29,15 @@ export class MetadataHistoryComponent { private router: Router, private route: ActivatedRoute ) { + combineLatest( + this.store.select(getConfigurationModelId), + this.store.select(getConfigurationModelKind) + ).pipe( + takeUntil(this.ngUnsubscribe), + map(([id, kind]) => ({ id, type: kind })), + map(request => new LoadHistoryRequest(request)) + ).subscribe(store); + this.history$ = this.store.select(getVersionCollection) .pipe(map(versions => this.sortVersionsByDate(versions))); } @@ -47,11 +63,16 @@ export class MetadataHistoryComponent { restoreVersion(version: MetadataVersion): void { this.router.navigate( - [ '../', 'restore' ], + [ '../', 'version', version.id, 'restore' ], { - queryParams: { version: version.id }, relativeTo: this.route } ); } + + ngOnDestroy() { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); + this.store.dispatch(new ClearHistory()); + } } diff --git a/ui/src/app/metadata/configuration/container/metadata-options.component.html b/ui/src/app/metadata/configuration/container/metadata-options.component.html index ebed36bf3..e201fcb62 100644 --- a/ui/src/app/metadata/configuration/container/metadata-options.component.html +++ b/ui/src/app/metadata/configuration/container/metadata-options.component.html @@ -6,9 +6,8 @@

+ diff --git a/ui/src/app/metadata/configuration/container/restore-edit.component.ts b/ui/src/app/metadata/configuration/container/restore-edit.component.ts new file mode 100644 index 000000000..a50c0a52b --- /dev/null +++ b/ui/src/app/metadata/configuration/container/restore-edit.component.ts @@ -0,0 +1,45 @@ +import { Component } from '@angular/core'; +import { Observable, of } from 'rxjs'; +import { Store } from '@ngrx/store'; +import { + ConfigurationState, getConfigurationModelKind +} from '../../configuration/reducer'; +import { RestoreVersionRequest, CancelRestore } from '../action/restore.action'; +import { NAV_FORMATS } from '../../domain/component/editor-nav.component'; +import { Metadata } from '../../domain/domain.type'; +import { getVersionModel } from '../reducer'; + + +@Component({ + selector: 'restore-edit', + templateUrl: './restore-edit.component.html', + styleUrls: [] +}) + +export class RestoreEditComponent { + + model$: Observable = this.store.select(getVersionModel); + kind$: Observable = this.store.select(getConfigurationModelKind); + + isInvalid$: Observable = of(false); + canFilter$: Observable = of(false); + status$: Observable = of('VALID'); + isSaving$: Observable = of(false); + + validators$: Observable; + + formats = NAV_FORMATS; + + constructor( + private store: Store + ) {} + + save() { + this.store.dispatch(new RestoreVersionRequest()); + } + + cancel() { + this.store.dispatch(new CancelRestore()); + } +} + diff --git a/ui/src/app/metadata/configuration/container/restore.component.ts b/ui/src/app/metadata/configuration/container/restore.component.ts index e48e52c38..d87302ff6 100644 --- a/ui/src/app/metadata/configuration/container/restore.component.ts +++ b/ui/src/app/metadata/configuration/container/restore.component.ts @@ -7,6 +7,7 @@ import { CONFIG_DATE_FORMAT } from '../configuration.values'; import { RestoreVersionRequest, CancelRestore } from '../action/restore.action'; import { map } from 'rxjs/operators'; import { DatePipe } from '@angular/common'; +import { Router, ActivatedRoute } from '@angular/router'; @Component({ selector: 'restore-component', @@ -21,13 +22,15 @@ export class RestoreComponent { constructor( private store: Store, - private datePipe: DatePipe + private datePipe: DatePipe, + private router: Router, + private route: ActivatedRoute ) { this.date$ = this.dateString$.pipe(map((date) => this.datePipe.transform(date, CONFIG_DATE_FORMAT))); } restore() { - this.store.dispatch(new RestoreVersionRequest()); + this.router.navigate(['../', 'edit'], { relativeTo: this.route }); } cancel() { diff --git a/ui/src/app/metadata/configuration/effect/configuration.effect.ts b/ui/src/app/metadata/configuration/effect/configuration.effect.ts index b9719469d..91ce3d37a 100644 --- a/ui/src/app/metadata/configuration/effect/configuration.effect.ts +++ b/ui/src/app/metadata/configuration/effect/configuration.effect.ts @@ -9,10 +9,10 @@ import { MetadataConfigurationService } from '../service/configuration.service'; import { ConfigurationActionTypes, SetMetadata, - SetDefinition, + SetConfigurationDefinition, LoadSchemaRequest, LoadSchemaSuccess, - SetSchema, + SetConfigurationSchema, LoadSchemaError, LoadXmlSuccess, LoadXmlError, @@ -34,6 +34,11 @@ import { import { MetadataHistoryService } from '../service/history.service'; import { Metadata } from '../../domain/domain.type'; import { SelectVersion } from '../action/history.action'; +import { + SetDefinition, + LoadSchemaSuccess as LoadWizardSchemaSuccess, + LoadSchemaRequest as LoadWizardSchemaRequest +} from '../../../wizard/action/wizard.action'; @Injectable() export class MetadataConfigurationEffects { @@ -79,21 +84,33 @@ export class MetadataConfigurationEffects { @Effect() setDefinitionOnResolverDataLoad$ = this.actions$.pipe( ofType(ResolverCollectionActionTypes.SELECT_SUCCESS), - map(action => new SetDefinition(this.configService.getDefinition('resolver'))) + map(action => new SetConfigurationDefinition(this.configService.getDefinition('resolver'))) ); @Effect() setDefinitionOnProviderLoad$ = this.actions$.pipe( ofType(ProviderCollectionActionTypes.SELECT_PROVIDER_SUCCESS), - map(action => new SetDefinition(this.configService.getDefinition(action.payload['@type']))) + map(action => new SetConfigurationDefinition(this.configService.getDefinition(action.payload['@type']))) ); @Effect() loadSchemaOnDefinitionSet$ = this.actions$.pipe( - ofType(ConfigurationActionTypes.SET_DEFINITION), + ofType(ConfigurationActionTypes.SET_DEFINITION), map(action => new LoadSchemaRequest(action.payload.schema)) ); + @Effect() + setWizardDefinition$ = this.actions$.pipe( + ofType(ConfigurationActionTypes.SET_DEFINITION), + map(action => new SetDefinition(action.payload)) + ); + + @Effect() + setWizardSchema$ = this.actions$.pipe( + ofType(ConfigurationActionTypes.LOAD_SCHEMA_SUCCESS), + map(action => new LoadWizardSchemaSuccess(action.payload)) + ); + @Effect() loadSchemaData$ = this.actions$.pipe( ofType(ConfigurationActionTypes.LOAD_SCHEMA_REQUEST), @@ -110,7 +127,7 @@ export class MetadataConfigurationEffects { @Effect() setSchema$ = this.actions$.pipe( ofType(ConfigurationActionTypes.LOAD_SCHEMA_SUCCESS), - map(action => new SetSchema(action.payload)) + map(action => new SetConfigurationSchema(action.payload)) ); @Effect({dispatch: false}) diff --git a/ui/src/app/metadata/configuration/effect/restore.effect.ts b/ui/src/app/metadata/configuration/effect/restore.effect.ts index 4a5e0b491..bacb422f6 100644 --- a/ui/src/app/metadata/configuration/effect/restore.effect.ts +++ b/ui/src/app/metadata/configuration/effect/restore.effect.ts @@ -7,7 +7,9 @@ import { RestoreVersionRequest, RestoreVersionSuccess, RestoreVersionError, - CancelRestore + CancelRestore, + UpdateRestorationChangesRequest, + UpdateRestorationChangesSuccess } from '../action/restore.action'; import { MetadataHistoryService } from '../service/history.service'; import { of } from 'rxjs'; @@ -16,7 +18,14 @@ import { Router } from '@angular/router'; import { AddNotification } from '../../../notification/action/notification.action'; import { Notification, NotificationType } from '../../../notification/model/notification'; import { Store } from '@ngrx/store'; -import { ConfigurationState, getConfigurationModel, getVersionModel, getConfigurationModelId, getConfigurationModelKind } from '../reducer'; +import { + ConfigurationState, + getConfigurationModel, + getVersionModel, + getConfigurationModelId, + getConfigurationModelKind, + getConfigurationDefinition +} from '../reducer'; import { SetMetadata } from '../action/configuration.action'; @@ -86,6 +95,31 @@ export class RestoreEffects { ) ); + @Effect() + updateRestorationChanges$ = this.actions$.pipe( + ofType(RestoreActionTypes.UPDATE_RESTORATION_REQUEST), + map(action => action.payload), + withLatestFrom( + this.store.select(getConfigurationDefinition), + this.store.select(getConfigurationModelKind), + this.store.select(getConfigurationModel) + ), + map(([changes, definition, kind, original]) => { + let parsed = definition.parser(changes); + if (kind === 'provider') { + parsed = { + ...parsed, + metadataFilters: [ + ...original.metadataFilters, + ...(parsed.metadataFilters || []) + ] + }; + } + return (parsed); + }), + map(changes => new UpdateRestorationChangesSuccess(changes)) + ); + constructor( private store: Store, private historyService: MetadataHistoryService, diff --git a/ui/src/app/metadata/configuration/reducer/index.ts b/ui/src/app/metadata/configuration/reducer/index.ts index 43638ba72..7bd043a2c 100644 --- a/ui/src/app/metadata/configuration/reducer/index.ts +++ b/ui/src/app/metadata/configuration/reducer/index.ts @@ -5,6 +5,7 @@ import * as fromConfiguration from './configuration.reducer'; import * as fromHistory from './history.reducer'; import * as fromCompare from './compare.reducer'; import * as fromVersion from './version.reducer'; +import * as fromRestore from './restore.reducer'; import { WizardStep } from '../../../wizard/model'; import * as utils from '../../domain/utility/configuration'; @@ -21,13 +22,15 @@ export interface ConfigurationState { history: fromHistory.HistoryState; compare: fromCompare.State; version: fromVersion.State; + restore: fromRestore.RestoreState; } export const reducers = { configuration: fromConfiguration.reducer, history: fromHistory.reducer, compare: fromCompare.reducer, - version: fromVersion.reducer + version: fromVersion.reducer, + restore: fromRestore.reducer }; export interface State extends fromRoot.State { @@ -40,6 +43,7 @@ export const getConfigurationStateFn = (state: ConfigurationState) => state.conf export const getHistoryStateFn = (state: ConfigurationState) => state.history; export const getCompareStateFn = (state: ConfigurationState) => state.compare; export const getVersionStateFn = (state: ConfigurationState) => state.version; +export const getRestoreStateFn = (state: ConfigurationState) => state.restore; export const getConfigurationState = createSelector(getState, getConfigurationStateFn); export const getConfigurationModelKind = createSelector(getConfigurationState, fromConfiguration.getModelKind); @@ -151,6 +155,9 @@ export const getComparisonConfigurations = createSelector( export const getComparisonConfigurationCount = createSelector(getComparisonConfigurations, (config) => config ? config.dates.length : 0); // Version Restoration + +export const getRestoreState = createSelector(getState, getRestoreStateFn); + export const filterPluginTypes = ['RequiredValidUntil', 'SignatureValidation', 'EntityRoleWhiteList']; export const isAdditionalFilter = (type) => filterPluginTypes.indexOf(type) === -1; @@ -175,6 +182,19 @@ export const getVersionModelFilters = createSelector( getVersionModelFiltersFn ); +export const getRestorationIsValid = createSelector(getRestoreState, fromRestore.isRestorationValid); +export const getRestorationIsSaved = createSelector(getRestoreState, fromRestore.isRestorationSaved); +export const getRestorationChanges = createSelector(getRestoreState, fromRestore.getChanges); +export const getRestorationIsSaving = createSelector(getRestoreState, fromRestore.isRestorationSaving); +export const getRestorationFormStatus = createSelector(getRestoreState, fromRestore.getFormStatus); +export const getInvalidRestorationForms = createSelector(getRestoreState, fromRestore.getInvalidRestorationForms); + +export const getFormattedModel = createSelector( + getVersionModel, + getConfigurationDefinition, + (model, definition) => definition.formatter(model) +); + // Mixed states export const getConfigurationModelFn = (kind, version, provider, resolver) => { diff --git a/ui/src/app/metadata/configuration/reducer/restore.reducer.ts b/ui/src/app/metadata/configuration/reducer/restore.reducer.ts new file mode 100644 index 000000000..4ca626459 --- /dev/null +++ b/ui/src/app/metadata/configuration/reducer/restore.reducer.ts @@ -0,0 +1,40 @@ +import { Metadata } from '../../domain/domain.type'; +import { RestoreActionTypes, RestoreActionsUnion } from '../action/restore.action'; + +export interface RestoreState { + saving: boolean; + status: { [key: string]: string }; + changes: Metadata; +} + +export const initialState: RestoreState = { + saving: false, + status: {}, + changes: {} as Metadata +}; + +export function reducer(state = initialState, action: RestoreActionsUnion): RestoreState { + switch (action.type) { + case RestoreActionTypes.UPDATE_RESTORATION_SUCCESS: + return { + ...state, + changes: { + ...state.changes, + ...action.payload + } + }; + default: { + return state; + } + } +} + +export const isRestorationSaved = (state: RestoreState) => !Object.keys(state.changes).length; +export const getChanges = (state: RestoreState) => state.changes; +export const isRestorationSaving = (state: RestoreState) => state.saving; +export const getFormStatus = (state: RestoreState) => state.status; + +export const isRestorationValid = (state: RestoreState) => + !Object.keys(state.status).some(key => state.status[key] === ('INVALID')); +export const getInvalidRestorationForms = (state: RestoreState) => + Object.keys(state.status).filter(key => state.status[key] === 'INVALID'); diff --git a/ui/src/app/metadata/configuration/service/index-resolver.service.ts b/ui/src/app/metadata/configuration/service/index-resolver.service.ts new file mode 100644 index 000000000..4bbc5e07e --- /dev/null +++ b/ui/src/app/metadata/configuration/service/index-resolver.service.ts @@ -0,0 +1,14 @@ +import { Injectable } from '@angular/core'; +import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; +import { Store } from '@ngrx/store'; +import { WizardState } from '../../../wizard/reducer'; +import { SetIndex } from '../../../wizard/action/wizard.action'; + +@Injectable() +export class IndexResolver implements Resolve { + constructor(private store: Store) { } + + resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { + this.store.dispatch(new SetIndex(route.params.index)); + } +} diff --git a/ui/src/app/metadata/domain/model/wizards/metadata-source-base.ts b/ui/src/app/metadata/domain/model/wizards/metadata-source-base.ts index 02b1a2625..106ebcf1d 100644 --- a/ui/src/app/metadata/domain/model/wizards/metadata-source-base.ts +++ b/ui/src/app/metadata/domain/model/wizards/metadata-source-base.ts @@ -3,6 +3,7 @@ import { MetadataResolver } from '../metadata-resolver'; import { FormProperty } from 'ngx-schema-form/lib/model/formproperty'; import { ArrayProperty } from 'ngx-schema-form/lib/model/arrayproperty'; import { ObjectProperty } from 'ngx-schema-form/lib/model/objectproperty'; +import { getAllOtherIds } from '../../../resolver/reducer'; /*istanbul ignore next */ export class MetadataSourceBase implements Wizard { @@ -11,6 +12,8 @@ export class MetadataSourceBase implements Wizard { steps: WizardStep[] = []; schema = ''; + validatorParams = [getAllOtherIds]; + bindings = { '/securityInfo/x509CertificateAvailable': [ { @@ -86,7 +89,6 @@ export class MetadataSourceBase implements Wizard { const checkOrg = (value, property, form) => { const org = property.parent; const orgValue = org.value || {}; - console.log(orgValue); const err = Object.keys(orgValue) && !value ? { code: 'ORG_INCOMPLETE', path: `#${property.path}`, diff --git a/ui/src/app/metadata/filter/model/entity-attributes.filter.ts b/ui/src/app/metadata/filter/model/entity-attributes.filter.ts index 78ee24e43..a2642ddc9 100644 --- a/ui/src/app/metadata/filter/model/entity-attributes.filter.ts +++ b/ui/src/app/metadata/filter/model/entity-attributes.filter.ts @@ -2,6 +2,7 @@ import { FormDefinition } from '../../../wizard/model'; import { MetadataFilter } from '../../domain/model'; import { removeNulls } from '../../../shared/util'; import { EntityAttributesFilterEntity } from '../../domain/entity'; +import { getFilterNames } from '../reducer'; export const EntityAttributesFilter: FormDefinition = { label: 'EntityAttributes', @@ -10,6 +11,7 @@ export const EntityAttributesFilter: FormDefinition = { getEntity(filter: MetadataFilter): EntityAttributesFilterEntity { return new EntityAttributesFilterEntity(filter); }, + validatorParams: [getFilterNames], getValidators(namesList: string[] = []): any { const validators = { '/': (value, property, form_current) => { diff --git a/ui/src/app/metadata/filter/model/nameid.filter.ts b/ui/src/app/metadata/filter/model/nameid.filter.ts index f3cf67960..9751d4ce9 100644 --- a/ui/src/app/metadata/filter/model/nameid.filter.ts +++ b/ui/src/app/metadata/filter/model/nameid.filter.ts @@ -1,6 +1,7 @@ import { FormDefinition } from '../../../wizard/model'; import { MetadataFilter } from '../../domain/model'; import { NameIDFormatFilterEntity } from '../../domain/entity/filter/nameid-format-filter'; +import { getFilterNames } from '../reducer'; export const NameIDFilter: FormDefinition = { label: 'NameIDFormat', @@ -9,6 +10,7 @@ export const NameIDFilter: FormDefinition = { getEntity(filter: MetadataFilter): NameIDFormatFilterEntity { return new NameIDFormatFilterEntity(filter); }, + validatorParams: [getFilterNames], getValidators(namesList: string[] = []): any { const validators = { '/': (value, property, form_current) => { diff --git a/ui/src/app/metadata/provider/container/provider-edit-step.component.spec.ts b/ui/src/app/metadata/provider/container/provider-edit-step.component.spec.ts index 093dc85c1..a9013176a 100644 --- a/ui/src/app/metadata/provider/container/provider-edit-step.component.spec.ts +++ b/ui/src/app/metadata/provider/container/provider-edit-step.component.spec.ts @@ -52,8 +52,7 @@ describe('Provider Edit Step Component', () => { } } }, - locked: false, - schemaCollection: [] + locked: false } }) }) diff --git a/ui/src/app/metadata/provider/container/provider-edit.component.spec.ts b/ui/src/app/metadata/provider/container/provider-edit.component.spec.ts index bd6ed6698..a5601d594 100644 --- a/ui/src/app/metadata/provider/container/provider-edit.component.spec.ts +++ b/ui/src/app/metadata/provider/container/provider-edit.component.spec.ts @@ -62,8 +62,7 @@ describe('Provider Edit Component', () => { schemaPath: '', loading: false, schema: {}, - locked: false, - schemaCollection: [] + locked: false } }) }), diff --git a/ui/src/app/metadata/provider/container/provider-wizard.component.ts b/ui/src/app/metadata/provider/container/provider-wizard.component.ts index 125966878..f1957718e 100644 --- a/ui/src/app/metadata/provider/container/provider-wizard.component.ts +++ b/ui/src/app/metadata/provider/container/provider-wizard.component.ts @@ -63,7 +63,7 @@ export class ProviderWizardComponent implements OnDestroy { this.summary$ = combineLatest( this.store.select(fromWizard.getWizardDefinition), - this.store.select(fromWizard.getSchemaCollection), + this.store.select(fromWizard.getSchema), this.store.select(fromProvider.getEntityChanges) ).pipe( map(([ definition, schema, model ]) => ({ definition, schema, model })) diff --git a/ui/src/app/metadata/provider/model/base.provider.form.ts b/ui/src/app/metadata/provider/model/base.provider.form.ts index a27a1cbc6..645e21f23 100644 --- a/ui/src/app/metadata/provider/model/base.provider.form.ts +++ b/ui/src/app/metadata/provider/model/base.provider.form.ts @@ -1,11 +1,13 @@ import { Wizard } from '../../../wizard/model'; import { BaseMetadataProvider } from '../../domain/model/providers'; +import { getProviderNames, getProviderXmlIds } from '../reducer'; export const BaseMetadataProviderEditor: Wizard = { label: 'BaseMetadataProvider', type: 'BaseMetadataResolver', schema: '', - getValidators(namesList: string[]): any { + validatorParams: [getProviderNames, getProviderXmlIds], + getValidators(namesList: string[], xmlIdList: string[]): any { const validators = { '/': (value, property, form_current) => { let errors; @@ -29,6 +31,15 @@ export const BaseMetadataProviderEditor: Wizard = { params: [value] } : null; return err; + }, + '/xmlId': (value, property, form) => { + const err = xmlIdList.indexOf(value) > -1 ? { + code: 'INVALID_ID', + path: `#${property.path}`, + message: 'message.id-unique', + params: [value] + } : null; + return err; } }; return validators; diff --git a/ui/src/app/metadata/provider/model/dynamic-http.provider.form.ts b/ui/src/app/metadata/provider/model/dynamic-http.provider.form.ts index 2583e3c69..9ef3bf2bb 100644 --- a/ui/src/app/metadata/provider/model/dynamic-http.provider.form.ts +++ b/ui/src/app/metadata/provider/model/dynamic-http.provider.form.ts @@ -17,16 +17,7 @@ export const DynamicHttpMetadataProviderWizard: Wizard { - const err = xmlIdList.indexOf(value) > -1 ? { - code: 'INVALID_ID', - path: `#${property.path}`, - message: 'message.id-unique', - params: [value] - } : null; - return err; - }; + const validators = BaseMetadataProviderEditor.getValidators(namesList, xmlIdList); validators['/metadataRequestURLConstructionScheme'] = (value, property, form) => { let errors; diff --git a/ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts b/ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts index 0df10c0ea..dde0ec3f1 100644 --- a/ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts +++ b/ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts @@ -18,16 +18,7 @@ export const FileBackedHttpMetadataProviderWizard: Wizard { - const err = xmlIdList.indexOf(value) > -1 ? { - code: 'INVALID_ID', - path: `#${property.path}`, - message: 'message.id-unique', - params: [value] - } : null; - return err; - }; + const validators = BaseMetadataProviderEditor.getValidators(namesList, xmlIdList); validators['/metadataURL'] = (value, property, form) => { return !UriValidator.isUri(value) ? { code: 'INVALID_URI', diff --git a/ui/src/app/metadata/provider/model/file-system.provider.form.ts b/ui/src/app/metadata/provider/model/file-system.provider.form.ts index 331a53c99..fcf780ac5 100644 --- a/ui/src/app/metadata/provider/model/file-system.provider.form.ts +++ b/ui/src/app/metadata/provider/model/file-system.provider.form.ts @@ -16,19 +16,6 @@ export const FileSystemMetadataProviderWizard: Wizard { - const err = xmlIdList.indexOf(value) > -1 ? { - code: 'INVALID_ID', - path: `#${property.path}`, - message: 'message.id-unique', - params: [value] - } : null; - return err; - }; - return validators; - }, schema: '/api/ui/MetadataResolver/FilesystemMetadataResolver', steps: [ { diff --git a/ui/src/app/metadata/provider/model/local-dynamic.provider.form.ts b/ui/src/app/metadata/provider/model/local-dynamic.provider.form.ts index aeeef302a..c53562873 100644 --- a/ui/src/app/metadata/provider/model/local-dynamic.provider.form.ts +++ b/ui/src/app/metadata/provider/model/local-dynamic.provider.form.ts @@ -17,19 +17,6 @@ export const LocalDynamicMetadataProviderWizard: Wizard { - const err = xmlIdList.indexOf(value) > -1 ? { - code: 'INVALID_ID', - path: `#${property.path}`, - message: 'message.id-unique', - params: [value] - } : null; - return err; - }; - return validators; - }, schema: '/api/ui/MetadataResolver/LocalDynamicMetadataResolver', steps: [ { diff --git a/ui/src/app/metadata/resolver/container/resolver-wizard-step.component.spec.ts b/ui/src/app/metadata/resolver/container/resolver-wizard-step.component.spec.ts index 56c4784a7..c64445dd4 100644 --- a/ui/src/app/metadata/resolver/container/resolver-wizard-step.component.spec.ts +++ b/ui/src/app/metadata/resolver/container/resolver-wizard-step.component.spec.ts @@ -66,11 +66,6 @@ describe('Resolver Wizard Step Component', () => { index: 'common', disabled: false, definition: new MetadataSourceWizard(), - schemaCollection: { - common: { - ...schema - } - }, schemaPath: '/foo/bar', loading: false, schema: { diff --git a/ui/src/app/metadata/resolver/container/resolver-wizard.component.spec.ts b/ui/src/app/metadata/resolver/container/resolver-wizard.component.spec.ts index 1b6c5df32..3cec9bf25 100644 --- a/ui/src/app/metadata/resolver/container/resolver-wizard.component.spec.ts +++ b/ui/src/app/metadata/resolver/container/resolver-wizard.component.spec.ts @@ -72,11 +72,6 @@ describe('Resolver Wizard Component', () => { index: 'page', disabled: false, definition: new MetadataSourceWizard(), - schemaCollection: { - page: { - ...schema - } - }, schemaPath: '/foo/bar', loading: false, schema: { diff --git a/ui/src/app/metadata/resolver/container/resolver-wizard.component.ts b/ui/src/app/metadata/resolver/container/resolver-wizard.component.ts index 3ccddc90e..ffabac236 100644 --- a/ui/src/app/metadata/resolver/container/resolver-wizard.component.ts +++ b/ui/src/app/metadata/resolver/container/resolver-wizard.component.ts @@ -117,7 +117,7 @@ export class ResolverWizardComponent implements OnDestroy, CanComponentDeactivat this.summary$ = combine( this.store.select(fromWizard.getWizardDefinition), - this.store.select(fromWizard.getSchemaCollection), + this.store.select(fromWizard.getSchema), this.store.select(fromResolver.getEntityChanges) ).pipe( map(([definition, schema, model]) => ( diff --git a/ui/src/app/metadata/resolver/reducer/index.ts b/ui/src/app/metadata/resolver/reducer/index.ts index bf38715d9..43bc2fbc7 100644 --- a/ui/src/app/metadata/resolver/reducer/index.ts +++ b/ui/src/app/metadata/resolver/reducer/index.ts @@ -103,3 +103,9 @@ export const getAllResolvers = createSelector(getDraftCollection, getResolverCol export const getAllResolverIds = createSelector(getDraftIds, getResolverIds, combineAllFn); export const getAllEntityIds = createSelector(getAllResolvers, getEntityIdsFn); + +export const getAllOtherIds = createSelector( + getAllResolvers, + getSelectedResolverId, + (ids, selected) => ids.filter(id => id !== selected) +); diff --git a/ui/src/app/wizard/model/form-definition.ts b/ui/src/app/wizard/model/form-definition.ts index 3f03957c0..4402aa354 100644 --- a/ui/src/app/wizard/model/form-definition.ts +++ b/ui/src/app/wizard/model/form-definition.ts @@ -1,8 +1,11 @@ +import { Selector } from '@ngrx/store'; + export interface FormDefinition { label: string; type: string; schema: string; bindings?: any; + validatorParams: any[]; getEntity?(entity: any): any; parser(changes: Partial, schema?: any); formatter(changes: Partial, schema?: any); diff --git a/ui/src/app/wizard/reducer/index.ts b/ui/src/app/wizard/reducer/index.ts index 4c3321c62..d85d930be 100644 --- a/ui/src/app/wizard/reducer/index.ts +++ b/ui/src/app/wizard/reducer/index.ts @@ -2,8 +2,6 @@ import * as fromRoot from '../../app.reducer'; import * as fromWizard from './wizard.reducer'; import { createFeatureSelector, createSelector } from '@ngrx/store'; import { Wizard, WizardStep } from '../model'; -import { diff } from 'deep-object-diff'; -import { SchemaService } from '../../schema-form/service/schema.service'; export interface WizardState { wizard: fromWizard.State; @@ -46,7 +44,6 @@ export const getState = createSelector(getWizardState, getWizardStateFn); export const getWizardIndex = createSelector(getState, fromWizard.getIndex); export const getWizardIsDisabled = createSelector(getState, fromWizard.getDisabled); export const getWizardDefinition = createSelector(getState, fromWizard.getDefinition); -export const getSchemaCollection = createSelector(getState, fromWizard.getCollection); export const getSchemaPath = (wizard: Wizard) => wizard ? wizard.schema : null; @@ -135,3 +132,9 @@ export const getSchemaObject = createSelector(getState, fromWizard.getSchema); export const getParsedSchema = createSelector(getSchemaObject, getLocked, getSchemaParseFn); export const getSchema = createSelector(getParsedSchema, getCurrent, getSplitSchema); + +export const getWizardDefinitionValidationParams = createSelector(getWizardDefinition, def => def.validatorParams); + +export const getValidators = (params: any) => createSelector(getWizardDefinition, (definition) => { + return definition.getValidators(...params); +}); diff --git a/ui/src/app/wizard/reducer/wizard.reducer.spec.ts b/ui/src/app/wizard/reducer/wizard.reducer.spec.ts index 8a50eb116..d43ad334e 100644 --- a/ui/src/app/wizard/reducer/wizard.reducer.spec.ts +++ b/ui/src/app/wizard/reducer/wizard.reducer.spec.ts @@ -34,12 +34,6 @@ describe('Wizard Reducer', () => { }); }); - describe(`${WizardActionTypes.ADD_SCHEMA}`, () => { - it('should add the payload to the schema collection', () => { - expect(reducer(snapshot, new AddSchema({id: 'foo', schema: SCHEMA })).schemaCollection).toEqual({ 'foo': SCHEMA }); - }); - }); - describe(`${WizardActionTypes.SET_DISABLED}`, () => { it('should set the disabled property on the wizard', () => { expect(reducer(snapshot, new SetDisabled(true)).disabled).toBe(true); @@ -108,7 +102,6 @@ describe('Wizard Reducer', () => { describe('selector functions', () => { it('should return pieces of state', () => { - expect(selectors.getCollection(snapshot)).toEqual(snapshot.schemaCollection); expect(selectors.getDefinition(snapshot)).toEqual(snapshot.definition); expect(selectors.getDisabled(snapshot)).toEqual(snapshot.disabled); expect(selectors.getIndex(snapshot)).toEqual(snapshot.index); diff --git a/ui/src/app/wizard/reducer/wizard.reducer.ts b/ui/src/app/wizard/reducer/wizard.reducer.ts index 603fbaeda..11205afb3 100644 --- a/ui/src/app/wizard/reducer/wizard.reducer.ts +++ b/ui/src/app/wizard/reducer/wizard.reducer.ts @@ -5,7 +5,6 @@ export interface State { index: string; disabled: boolean; definition: Wizard; - schemaCollection: { [id: string]: any }; schemaPath: string; loading: boolean; @@ -17,8 +16,6 @@ export const initialState: State = { index: null, disabled: false, definition: null, - schemaCollection: {}, - schemaPath: null, loading: false, schema: null, @@ -62,16 +59,6 @@ export function reducer(state = initialState, action: WizardActionUnion): State locked: false }; } - - case WizardActionTypes.ADD_SCHEMA: { - return { - ...state, - schemaCollection: { - ...state.schemaCollection, - [action.payload.id]: action.payload.schema - } - }; - } case WizardActionTypes.SET_DISABLED: { return { ...state, @@ -121,4 +108,3 @@ export const getLocked = (state: State) => state.locked; export const getIndex = (state: State) => state.index; export const getDisabled = (state: State) => state.disabled; export const getDefinition = (state: State) => state.definition; -export const getCollection = (state: State) => state.schemaCollection; From 2e39d4f5e5dff006fff7a32e8442b9b7429430dd Mon Sep 17 00:00:00 2001 From: Jj! Date: Fri, 16 Aug 2019 10:38:51 -0500 Subject: [PATCH 12/27] [SHIBUI-1414] WIP --- .../resources/metadata-sources-ui-schema.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/backend/src/main/resources/metadata-sources-ui-schema.json b/backend/src/main/resources/metadata-sources-ui-schema.json index 9d64e75b4..8743acd12 100644 --- a/backend/src/main/resources/metadata-sources-ui-schema.json +++ b/backend/src/main/resources/metadata-sources-ui-schema.json @@ -26,7 +26,7 @@ "default": false }, "organization": { - "type": "object", + "type": ["object", "null"], "properties": { "name": { "title": "label.organization-name", @@ -68,13 +68,13 @@ "contacts": { "title": "label.contact-information", "description": "tooltip.contact-information", - "type": "array", + "type": ["array", "null"], "items": { "$ref": "#/definitions/Contact" } }, "mdui": { - "type": "object", + "type": ["object", "null"], "widget": { "id": "fieldset" }, @@ -143,7 +143,7 @@ } }, "securityInfo": { - "type": "object", + "type": ["object", "null"], "widget": { "id": "fieldset" }, @@ -245,13 +245,13 @@ "assertionConsumerServices": { "title": "label.assertion-consumer-service-endpoints", "description": "", - "type": "array", + "type": ["array", "null"], "items": { "$ref": "#/definitions/AssertionConsumerService" } }, "serviceProviderSsoDescriptor": { - "type": "object", + "type": ["object", "null"], "widget": { "id": "fieldset" }, @@ -299,17 +299,17 @@ "logoutEndpoints": { "title": "label.logout-endpoints", "description": "tooltip.logout-endpoints", - "type": "array", + "type": ["array", "null"], "items": { "$ref": "#/definitions/LogoutEndpoint" } }, "relyingPartyOverrides": { - "type": "object", + "type": ["object", "null"], "properties": {} }, "attributeRelease": { - "type": "array", + "type": ["array", "null"], "title": "label.attribute-release", "description": "Attribute release table - select the attributes you want to release (default unchecked)", "widget": { From 8da94bd8c2c1cfd9b0c8c5d5c85e26914fa63959 Mon Sep 17 00:00:00 2001 From: Jj! Date: Mon, 19 Aug 2019 09:25:16 -0500 Subject: [PATCH 13/27] [SHIBUI-1414] update tests --- ...lerVersionEndpointsIntegrationTests.groovy | 51 +++++++++++++++++++ ...efinitionControllerIntegrationTests.groovy | 4 +- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerVersionEndpointsIntegrationTests.groovy b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerVersionEndpointsIntegrationTests.groovy index 99b5810ce..82b143c00 100644 --- a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerVersionEndpointsIntegrationTests.groovy +++ b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerVersionEndpointsIntegrationTests.groovy @@ -1,11 +1,22 @@ package edu.internet2.tier.shibboleth.admin.ui.controller import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor +import edu.internet2.tier.shibboleth.admin.ui.domain.Organization +import edu.internet2.tier.shibboleth.admin.ui.domain.OrganizationDisplayName +import edu.internet2.tier.shibboleth.admin.ui.domain.OrganizationName +import edu.internet2.tier.shibboleth.admin.ui.domain.OrganizationURL import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository +import groovy.json.JsonOutput +import groovy.json.JsonSlurper import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.web.client.TestRestTemplate +import org.springframework.http.HttpEntity +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpMethod +import org.springframework.http.MediaType +import org.springframework.test.annotation.DirtiesContext import org.springframework.test.context.ActiveProfiles import spock.lang.Specification @@ -100,6 +111,46 @@ class EntityDescriptorControllerVersionEndpointsIntegrationTests extends Specifi edv2.body.serviceProviderName == 'SP2' } + @DirtiesContext(methodMode = DirtiesContext.MethodMode.AFTER_METHOD) + def 'SHIBUI-1414'() { + given: + def ed = new EntityDescriptor(entityID: 'testme', serviceProviderName: 'testme').with { + entityDescriptorRepository.save(it) + }.with { + it.setOrganization(new Organization().with { + it.organizationNames = [new OrganizationName(value: 'testme', XMLLang: 'en')] + it.organizationDisplayNames = [new OrganizationDisplayName(value: 'testme', XMLLang: 'en')] + it.organizationURLs = [new OrganizationURL(value: 'http://testme.org', XMLLang: 'en')] + it + }) + entityDescriptorRepository.save(it) + } + + when: + def headers = new HttpHeaders().with { + it.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + it + } + + def allVersions = getAllEntityDescriptorVersions(ed.resourceId, List) + def edv1 = getEntityDescriptorForVersion(ed.resourceId, allVersions.body[0].id, String).body + def tedv2 = getEntityDescriptorForVersion(ed.resourceId, allVersions.body[1].id, EntityDescriptorRepresentation).body + + def aedv1 = new JsonSlurper().parseText(edv1).with { + it.put('version', tedv2.version) + it + }.with { + JsonOutput.toJson(it) + } + + def request = new HttpEntity(aedv1, headers) + def response = this.restTemplate.exchange("/api/EntityDescriptor/${ed.resourceId}", HttpMethod.PUT, request, String) + + then: + response.statusCodeValue != 400 + noExceptionThrown() + } + private getAllEntityDescriptorVersions(String resourceId, responseType) { this.restTemplate.getForEntity(resourceUriFor(ALL_VERSIONS_URI, resourceId), responseType) } diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy index af758f131..b9789201f 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy @@ -7,6 +7,7 @@ import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.context.TestConfiguration import org.springframework.boot.test.web.client.TestRestTemplate import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Profile import org.springframework.core.io.ResourceLoader import org.springframework.test.context.ActiveProfiles import spock.lang.Specification @@ -23,7 +24,7 @@ import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResour * @author Dmitriy Kopylenko */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@ActiveProfiles("no-auth") +@ActiveProfiles(["no-auth", "badjson"]) class BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests extends Specification { @Autowired @@ -42,6 +43,7 @@ class BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests extends Speci } @TestConfiguration + @Profile('badjson') static class Config { @Bean JsonSchemaResourceLocationRegistry jsonSchemaResourceLocationRegistry(ResourceLoader resourceLoader, From 643b12ba36fbecec9bdf962a53b4a3f7ba3b0c25 Mon Sep 17 00:00:00 2001 From: Jj! Date: Mon, 19 Aug 2019 09:30:38 -0500 Subject: [PATCH 14/27] Revert "[SHIBUI-1414] WIP" This reverts commit 2e39d4f5 --- .../resources/metadata-sources-ui-schema.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/backend/src/main/resources/metadata-sources-ui-schema.json b/backend/src/main/resources/metadata-sources-ui-schema.json index 8743acd12..9d64e75b4 100644 --- a/backend/src/main/resources/metadata-sources-ui-schema.json +++ b/backend/src/main/resources/metadata-sources-ui-schema.json @@ -26,7 +26,7 @@ "default": false }, "organization": { - "type": ["object", "null"], + "type": "object", "properties": { "name": { "title": "label.organization-name", @@ -68,13 +68,13 @@ "contacts": { "title": "label.contact-information", "description": "tooltip.contact-information", - "type": ["array", "null"], + "type": "array", "items": { "$ref": "#/definitions/Contact" } }, "mdui": { - "type": ["object", "null"], + "type": "object", "widget": { "id": "fieldset" }, @@ -143,7 +143,7 @@ } }, "securityInfo": { - "type": ["object", "null"], + "type": "object", "widget": { "id": "fieldset" }, @@ -245,13 +245,13 @@ "assertionConsumerServices": { "title": "label.assertion-consumer-service-endpoints", "description": "", - "type": ["array", "null"], + "type": "array", "items": { "$ref": "#/definitions/AssertionConsumerService" } }, "serviceProviderSsoDescriptor": { - "type": ["object", "null"], + "type": "object", "widget": { "id": "fieldset" }, @@ -299,17 +299,17 @@ "logoutEndpoints": { "title": "label.logout-endpoints", "description": "tooltip.logout-endpoints", - "type": ["array", "null"], + "type": "array", "items": { "$ref": "#/definitions/LogoutEndpoint" } }, "relyingPartyOverrides": { - "type": ["object", "null"], + "type": "object", "properties": {} }, "attributeRelease": { - "type": ["array", "null"], + "type": "array", "title": "label.attribute-release", "description": "Attribute release table - select the attributes you want to release (default unchecked)", "widget": { From e85b71cc9b19b84cb82cbb1462cf38a973cd554d Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Tue, 20 Aug 2019 07:53:34 -0700 Subject: [PATCH 15/27] SHIBUI-1385 Fixed issue with wizard-summary --- .../metadata/configuration/effect/restore.effect.ts | 5 +++-- ui/src/app/metadata/configuration/reducer/index.ts | 6 ++++++ .../domain/component/wizard-summary.component.ts | 6 +----- .../provider/container/provider-wizard.component.ts | 2 +- ui/src/app/metadata/provider/effect/editor.effect.ts | 11 +---------- ui/src/app/wizard/action/wizard.action.ts | 7 ------- ui/src/app/wizard/reducer/wizard.reducer.spec.ts | 1 - 7 files changed, 12 insertions(+), 26 deletions(-) diff --git a/ui/src/app/metadata/configuration/effect/restore.effect.ts b/ui/src/app/metadata/configuration/effect/restore.effect.ts index bacb422f6..4825c32dc 100644 --- a/ui/src/app/metadata/configuration/effect/restore.effect.ts +++ b/ui/src/app/metadata/configuration/effect/restore.effect.ts @@ -24,7 +24,8 @@ import { getVersionModel, getConfigurationModelId, getConfigurationModelKind, - getConfigurationDefinition + getConfigurationDefinition, + getRestorationModel } from '../reducer'; import { SetMetadata } from '../action/configuration.action'; @@ -39,7 +40,7 @@ export class RestoreEffects { this.store.select(getConfigurationModelId), this.store.select(getConfigurationModelKind), this.store.select(getConfigurationModel), - this.store.select(getVersionModel) + this.store.select(getRestorationModel) ), switchMap(([action, id, kind, current, version]) => this.historyService.updateVersion(id, kind, { diff --git a/ui/src/app/metadata/configuration/reducer/index.ts b/ui/src/app/metadata/configuration/reducer/index.ts index 7bd043a2c..b26766af9 100644 --- a/ui/src/app/metadata/configuration/reducer/index.ts +++ b/ui/src/app/metadata/configuration/reducer/index.ts @@ -195,6 +195,12 @@ export const getFormattedModel = createSelector( (model, definition) => definition.formatter(model) ); +export const getRestorationModel = createSelector( + getVersionModel, + getRestorationChanges, + (model, changes) => model +); + // Mixed states export const getConfigurationModelFn = (kind, version, provider, resolver) => { diff --git a/ui/src/app/metadata/domain/component/wizard-summary.component.ts b/ui/src/app/metadata/domain/component/wizard-summary.component.ts index e8b8deb34..82cd86c8d 100644 --- a/ui/src/app/metadata/domain/component/wizard-summary.component.ts +++ b/ui/src/app/metadata/domain/component/wizard-summary.component.ts @@ -34,15 +34,11 @@ export class WizardSummaryComponent implements OnChanges { ngOnChanges(changes: SimpleChanges): void { if (changes.summary && this.summary) { - const schemas = this.summary.schema; + const schema = this.summary.schema; const model = this.summary.model; const def = this.summary.definition; const steps = def.steps; - const schema = Object.keys(schemas).reduce((coll, key) => ({ - ...merge(coll, schemas[key]) - }), {} as any); - this.sections = steps .filter(step => step.id !== 'summary') .map( diff --git a/ui/src/app/metadata/provider/container/provider-wizard.component.ts b/ui/src/app/metadata/provider/container/provider-wizard.component.ts index f1957718e..f501f7cb8 100644 --- a/ui/src/app/metadata/provider/container/provider-wizard.component.ts +++ b/ui/src/app/metadata/provider/container/provider-wizard.component.ts @@ -63,7 +63,7 @@ export class ProviderWizardComponent implements OnDestroy { this.summary$ = combineLatest( this.store.select(fromWizard.getWizardDefinition), - this.store.select(fromWizard.getSchema), + this.store.select(fromWizard.getSchemaObject), this.store.select(fromProvider.getEntityChanges) ).pipe( map(([ definition, schema, model ]) => ({ definition, schema, model })) diff --git a/ui/src/app/metadata/provider/effect/editor.effect.ts b/ui/src/app/metadata/provider/effect/editor.effect.ts index f9ac13baf..60ec2d719 100644 --- a/ui/src/app/metadata/provider/effect/editor.effect.ts +++ b/ui/src/app/metadata/provider/effect/editor.effect.ts @@ -12,8 +12,7 @@ import { LoadSchemaSuccess, LoadSchemaFail, SetDefinition, - WizardActionTypes, - AddSchema + WizardActionTypes } from '../../../wizard/action/wizard.action'; import { ResetChanges } from '../action/entity.action'; @@ -38,14 +37,6 @@ export class EditorEffects { ) ); - @Effect() - $loadSchemaSuccess = this.actions$.pipe( - ofType(WizardActionTypes.LOAD_SCHEMA_SUCCESS), - map(action => action.payload), - withLatestFrom(this.store.select(fromWizard.getWizardIndex)), - map(([schema, id]) => new AddSchema({ id, schema })) - ); - @Effect() $resetChanges = this.actions$.pipe( ofType(WizardActionTypes.SET_DEFINITION), diff --git a/ui/src/app/wizard/action/wizard.action.ts b/ui/src/app/wizard/action/wizard.action.ts index 83a5f08a5..3e630f1a1 100644 --- a/ui/src/app/wizard/action/wizard.action.ts +++ b/ui/src/app/wizard/action/wizard.action.ts @@ -58,12 +58,6 @@ export class Previous implements Action { constructor(public payload: string) { } } -export class AddSchema implements Action { - readonly type = WizardActionTypes.ADD_SCHEMA; - - constructor(public payload: { id: string, schema: any }) { } -} - export class ClearWizard implements Action { readonly type = WizardActionTypes.CLEAR; } @@ -102,7 +96,6 @@ export type WizardActionUnion = | Next | Previous | ClearWizard - | AddSchema | LoadSchemaRequest | LoadSchemaSuccess | LoadSchemaFail diff --git a/ui/src/app/wizard/reducer/wizard.reducer.spec.ts b/ui/src/app/wizard/reducer/wizard.reducer.spec.ts index d43ad334e..8276a2d68 100644 --- a/ui/src/app/wizard/reducer/wizard.reducer.spec.ts +++ b/ui/src/app/wizard/reducer/wizard.reducer.spec.ts @@ -3,7 +3,6 @@ import * as selectors from './wizard.reducer'; import { WizardActionTypes, ClearWizard, - AddSchema, SetDisabled, SetDefinition, SetIndex, From 10d03e702d630250b76d9499a14f099f288ba8e3 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Tue, 20 Aug 2019 09:34:33 -0700 Subject: [PATCH 16/27] SHIBUI-1382 Fixed issue with restoration --- .../configuration/container/restore.component.spec.ts | 4 ++++ ui/src/app/metadata/configuration/effect/restore.effect.ts | 1 + ui/src/app/metadata/configuration/service/history.service.ts | 3 ++- ui/src/app/metadata/filter/effect/collection.effect.ts | 3 +-- .../filter/model/entity-attributes-configuration.filter.ts | 1 - ui/src/app/schema-form/service/schema.service.ts | 3 ++- 6 files changed, 10 insertions(+), 5 deletions(-) diff --git a/ui/src/app/metadata/configuration/container/restore.component.spec.ts b/ui/src/app/metadata/configuration/container/restore.component.spec.ts index 433a11ebd..e79cbcac9 100644 --- a/ui/src/app/metadata/configuration/container/restore.component.spec.ts +++ b/ui/src/app/metadata/configuration/container/restore.component.spec.ts @@ -10,6 +10,7 @@ import * as fromResolvers from '../../resolver/reducer'; import { MockI18nModule } from '../../../../testing/i18n.stub'; import { RestoreComponent } from './restore.component'; import { of } from 'rxjs'; +import { DatePipe } from '@angular/common'; @Component({ template: ` @@ -44,6 +45,9 @@ describe('Metadata Restore Page Component', () => { RestoreComponent, TestHostComponent ], + providers: [ + DatePipe + ] }).compileComponents(); store = TestBed.get(Store); diff --git a/ui/src/app/metadata/configuration/effect/restore.effect.ts b/ui/src/app/metadata/configuration/effect/restore.effect.ts index d91682c61..cfdf45057 100644 --- a/ui/src/app/metadata/configuration/effect/restore.effect.ts +++ b/ui/src/app/metadata/configuration/effect/restore.effect.ts @@ -17,6 +17,7 @@ import { Router, ActivatedRoute } from '@angular/router'; import { AddNotification } from '../../../notification/action/notification.action'; import { Notification, NotificationType } from '../../../notification/model/notification'; +import { removeNulls } from '../../../shared/util'; @Injectable() diff --git a/ui/src/app/metadata/configuration/service/history.service.ts b/ui/src/app/metadata/configuration/service/history.service.ts index 02529cde5..6a5f79607 100644 --- a/ui/src/app/metadata/configuration/service/history.service.ts +++ b/ui/src/app/metadata/configuration/service/history.service.ts @@ -8,6 +8,7 @@ import { MetadataVersion } from '../model/version'; import { map, catchError, switchMap } from 'rxjs/operators'; import { Metadata } from '../../domain/domain.type'; import { withLatestFrom } from 'rxjs-compat/operator/withLatestFrom'; +import { removeNulls } from '../../../shared/util'; @Injectable() export class MetadataHistoryService { @@ -48,7 +49,7 @@ export class MetadataHistoryService { restoreVersion(resourceId: string, type: string, versionId: string): Observable { return this.getVersions(resourceId, [null, versionId], type).pipe( switchMap(([current, toRestore]) => - this.updateVersion(resourceId, type, { ...toRestore, version: current.version }) + this.updateVersion(resourceId, type, { ...removeNulls(toRestore), version: current.version }) ) ); } diff --git a/ui/src/app/metadata/filter/effect/collection.effect.ts b/ui/src/app/metadata/filter/effect/collection.effect.ts index 58ecd41f7..dcf67b246 100644 --- a/ui/src/app/metadata/filter/effect/collection.effect.ts +++ b/ui/src/app/metadata/filter/effect/collection.effect.ts @@ -35,8 +35,7 @@ import { FilterCollectionActionTypes } from '../action/collection.action'; import * as fromFilter from '../reducer'; import * as fromProvider from '../../provider/reducer'; import { MetadataFilter } from '../../domain/model'; -import { removeNulls, array_move } from '../../../shared/util'; -import { EntityAttributesFilterEntity } from '../../domain/entity/filter/entity-attributes-filter'; +import { array_move } from '../../../shared/util'; import { MetadataFilterService } from '../../domain/service/filter.service'; import { SelectProviderRequest } from '../../provider/action/collection.action'; import { UpdateFilterChanges, ClearFilter } from '../action/filter.action'; diff --git a/ui/src/app/metadata/filter/model/entity-attributes-configuration.filter.ts b/ui/src/app/metadata/filter/model/entity-attributes-configuration.filter.ts index 5333e08f1..06a6609b6 100644 --- a/ui/src/app/metadata/filter/model/entity-attributes-configuration.filter.ts +++ b/ui/src/app/metadata/filter/model/entity-attributes-configuration.filter.ts @@ -1,6 +1,5 @@ import { Wizard } from '../../../wizard/model'; import { MetadataFilter } from '../../domain/model'; -import { removeNulls } from '../../../shared/util'; import { EntityAttributesFilter } from './entity-attributes.filter'; export const EntityAttributesFilterConfiguration: Wizard = { diff --git a/ui/src/app/schema-form/service/schema.service.ts b/ui/src/app/schema-form/service/schema.service.ts index 617769de6..0e5905a8a 100644 --- a/ui/src/app/schema-form/service/schema.service.ts +++ b/ui/src/app/schema-form/service/schema.service.ts @@ -28,12 +28,13 @@ export class SchemaService { const conditions = formProperty.parent.schema.anyOf || []; const values = formProperty.parent.value; const currentConditions = conditions.filter(condition => - Object + 'properties' in condition ? Object .keys(condition.properties) .some( key => values.hasOwnProperty(key) && condition.properties[key].enum ? condition.properties[key].enum[0] === values[key] : false ) + : false ); currentConditions.forEach(el => { requiredFields = el.required || []; From 4863ee6f5e3938d2902ba257a2357dbe43787bdb Mon Sep 17 00:00:00 2001 From: Dmitriy Kopylenko Date: Wed, 21 Aug 2019 09:15:31 -0400 Subject: [PATCH 17/27] Do not expose null values in JSON payloads --- .../src/main/resources/application.properties | 1 + .../EntitiesControllerIntegrationTests.groovy | 17 ++++------------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 559a6c082..909b2943a 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -20,6 +20,7 @@ spring.jpa.database-platform=org.hibernate.dialect.H2Dialect spring.h2.console.enabled=true # spring.jackson.default-property-inclusion=non_absent +spring.jackson.default-property-inclusion=NON_NULL # Database Configuration PostgreSQL #spring.datasource.url=jdbc:postgresql://localhost:5432/shibui diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerIntegrationTests.groovy index 8bf12484a..d8d9e7afb 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerIntegrationTests.groovy @@ -42,25 +42,16 @@ class EntitiesControllerIntegrationTests extends Specification { def "GET /api/entities returns the proper json"() { given: def expectedBody = ''' - { - "id":null, - "serviceProviderName":null, - "entityId":"http://test.scaldingspoon.org/test1", - "organization":null, - "contacts":null, - "mdui":null, + { + "entityId":"http://test.scaldingspoon.org/test1", "serviceProviderSsoDescriptor": { "protocolSupportEnum":"SAML 2", "nameIdFormats":["urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"] - }, - "logoutEndpoints":null, - "securityInfo":null, + }, "assertionConsumerServices":[ {"locationUrl":"https://test.scaldingspoon.org/test1/acs","binding":"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST","makeDefault":false} ], - "serviceEnabled":false, - "createdDate":null, - "modifiedDate":null, + "serviceEnabled":false, "relyingPartyOverrides":{}, "attributeRelease":["givenName","employeeNumber"] } From 58b0c2577d01559c8ecf79dc67d83270ca335609 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Wed, 21 Aug 2019 10:06:58 -0700 Subject: [PATCH 18/27] SHIBUI-1382 Fixed issue with date display --- .../container/restore.component.html | 8 ++--- .../container/restore.component.ts | 31 +++++++++++++------ .../configuration/effect/restore.effect.ts | 2 +- .../metadata/configuration/reducer/index.ts | 2 +- 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/ui/src/app/metadata/configuration/container/restore.component.html b/ui/src/app/metadata/configuration/container/restore.component.html index 66f994d1d..c2ac6f75e 100644 --- a/ui/src/app/metadata/configuration/container/restore.component.html +++ b/ui/src/app/metadata/configuration/container/restore.component.html @@ -1,5 +1,5 @@ -

- +

+ Restore Version ( date )

@@ -8,11 +8,11 @@

+ [translateParams]="{ 'date': date$ | async }"> Create New Version from Version ( date ) Settings

+ [translateParams]="{ 'date': date$ | async }"> Restoring this version will copy the Version ( date ) configuration and create a new Version from the selected version settings. You can then edit the configuration before saving the new version.

Cancel  diff --git a/ui/src/app/metadata/configuration/container/restore.component.ts b/ui/src/app/metadata/configuration/container/restore.component.ts index 388c5f8d8..0b6faff29 100644 --- a/ui/src/app/metadata/configuration/container/restore.component.ts +++ b/ui/src/app/metadata/configuration/container/restore.component.ts @@ -1,11 +1,11 @@ import { Component, ChangeDetectionStrategy, OnDestroy } from '@angular/core'; import { ActivatedRoute, } from '@angular/router'; import { Store } from '@ngrx/store'; -import { Subject } from 'rxjs'; +import { Subject, Observable } from 'rxjs'; import * as fromConfiguration from '../reducer'; import { CONFIG_DATE_FORMAT } from '../configuration.values'; -import { RestoreVersionRequest } from '../action/restore.action'; +import { RestoreVersionRequest, SelectVersionRestoreRequest } from '../action/restore.action'; import { withLatestFrom, map, takeUntil } from 'rxjs/operators'; import { DatePipe } from '@angular/common'; @@ -20,25 +20,36 @@ export class RestoreComponent implements OnDestroy { readonly subj = new Subject(); restore$ = this.subj.asObservable(); - date$ = this.store.select(fromConfiguration.getConfigurationVersionDate); - date: string; + date$: Observable; + kind$: Observable = this.store.select(fromConfiguration.getConfigurationModelKind); + id$: Observable = this.store.select(fromConfiguration.getConfigurationModelId); constructor( private store: Store, - private datePipe: DatePipe + private datePipe: DatePipe, + private route: ActivatedRoute ) { this.restore$.pipe( withLatestFrom( this.store.select(fromConfiguration.getSelectedVersionId), - this.store.select(fromConfiguration.getConfigurationModelKind), - this.store.select(fromConfiguration.getConfigurationModelId) + this.kind$, + this.id$ ), map(([restore, version, type, id]) => new RestoreVersionRequest({ id, type, version })) ).subscribe(this.store); - this.date$.pipe(takeUntil(this.subj)).subscribe( - (date) => this.date = this.datePipe.transform(date, CONFIG_DATE_FORMAT) - ); + this.date$ = this.store + .select(fromConfiguration.getConfigurationVersionDate) + .pipe( + map((date) => this.datePipe.transform(date, CONFIG_DATE_FORMAT)) + ); + + this.route.queryParams.pipe( + takeUntil(this.subj), + map(params => params.version), + withLatestFrom(this.id$, this.kind$), + map(([version, id, type]) => new SelectVersionRestoreRequest({ version, id, type })) + ).subscribe(this.store); } restore() { diff --git a/ui/src/app/metadata/configuration/effect/restore.effect.ts b/ui/src/app/metadata/configuration/effect/restore.effect.ts index cfdf45057..df5272ced 100644 --- a/ui/src/app/metadata/configuration/effect/restore.effect.ts +++ b/ui/src/app/metadata/configuration/effect/restore.effect.ts @@ -28,7 +28,7 @@ export class RestoreVersionEffects { ofType(RestoreActionTypes.SELECT_VERSION_REQUEST), map(action => action.payload), switchMap(({ type, id, version }) => { - return this.historyService.getVersion(id, version, type).pipe( + return this.historyService.getVersion(id, type, version).pipe( map(v => new SelectVersionRestoreSuccess(v)), catchError(err => of(new SelectVersionRestoreError(err))) ); diff --git a/ui/src/app/metadata/configuration/reducer/index.ts b/ui/src/app/metadata/configuration/reducer/index.ts index 469fe740e..ab217de6d 100644 --- a/ui/src/app/metadata/configuration/reducer/index.ts +++ b/ui/src/app/metadata/configuration/reducer/index.ts @@ -183,4 +183,4 @@ export const getConfigurationModelType = createSelector(getConfigurationModel, g export const getConfigurationHasXml = createSelector(getConfigurationXml, xml => !!xml); export const getConfigurationFilters = createSelector(getConfigurationModel, model => model.metadataFilters); -export const getConfigurationVersionDate = createSelector(getConfigurationModel, version => version.modifiedDate); +export const getConfigurationVersionDate = createSelector(getRestoreModel, version => version && version.modifiedDate); From d010d6957e535349b15ef02e0c2b29e3f35b0940 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Thu, 29 Aug 2019 08:34:05 -0700 Subject: [PATCH 19/27] SHIBUI-1382 Fixed translation --- .../metadata/configuration/container/restore.component.html | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ui/src/app/metadata/configuration/container/restore.component.html b/ui/src/app/metadata/configuration/container/restore.component.html index c2ac6f75e..77fc2c7cb 100644 --- a/ui/src/app/metadata/configuration/container/restore.component.html +++ b/ui/src/app/metadata/configuration/container/restore.component.html @@ -6,10 +6,8 @@

-

- Create New Version from Version ( date ) Settings +

+ Create New Version from Previous Settings

From 483a446c0a98f450911c50c9aac1bd2f3ae0cef5 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Thu, 29 Aug 2019 08:34:40 -0700 Subject: [PATCH 20/27] SHIBUI-1382 Fixed translation --- backend/src/main/resources/i18n/messages.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/resources/i18n/messages.properties b/backend/src/main/resources/i18n/messages.properties index 182a2491e..621dc4e63 100644 --- a/backend/src/main/resources/i18n/messages.properties +++ b/backend/src/main/resources/i18n/messages.properties @@ -473,7 +473,7 @@ message.database-constraint=There was a database constraint problem processing t message.no-filters=No Filters message.no-filters-added=No filters have been added to this Metadata Provider -message.create-new-version-from-version=Create New Version from Version ({ date }) Settings +message.create-new-version-from-version=Create New Version from Previous Settings message.restoring-this-version-will-copy=Restoring this version will copy the Version ({ date }) configuration and create a new Version from the selected version settings. You can then edit the configuration before saving the new version. tooltip.entity-id=Entity ID From f778a743ac4c5c17f73737ff0fc18bd06082c80b Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Thu, 29 Aug 2019 15:28:26 -0700 Subject: [PATCH 21/27] SHIBUI-1385 Implemented version restore editing --- .../configuration/action/restore.action.ts | 17 +++++ .../component/editor.component.html | 2 +- .../component/editor.component.ts | 5 +- .../metadata-header.component.spec.ts | 2 - .../container/configuration.component.spec.ts | 8 --- .../restore-edit-step.component.html | 3 + .../container/restore-edit-step.component.ts | 70 +++++++++++-------- .../container/restore-edit.component.html | 17 +---- .../container/restore-edit.component.ts | 18 +++-- .../container/restore.component.spec.ts | 11 +-- .../configuration/effect/restore.effect.ts | 22 +++++- .../reducer/configuration.reducer.spec.ts | 4 +- .../metadata/configuration/reducer/index.ts | 29 ++++++-- .../reducer/restore.reducer.spec.ts | 34 +++++++++ .../configuration/reducer/restore.reducer.ts | 14 ++++ .../service/history.service.spec.ts | 17 ----- .../configuration/service/history.service.ts | 2 +- .../wizard-summary.component.spec.ts | 5 +- .../component/wizard-summary.component.ts | 19 ++--- .../container/provider-edit-step.component.ts | 4 +- .../provider/model/base.provider.form.ts | 4 +- ui/src/app/metadata/provider/reducer/index.ts | 10 +++ .../container/resolver-wizard.component.ts | 6 +- ui/src/app/metadata/resolver/reducer/index.ts | 15 ++++ ui/src/app/wizard/model/form-definition.ts | 2 +- ui/src/app/wizard/reducer/wizard.reducer.ts | 1 - 26 files changed, 230 insertions(+), 111 deletions(-) create mode 100644 ui/src/app/metadata/configuration/reducer/restore.reducer.spec.ts diff --git a/ui/src/app/metadata/configuration/action/restore.action.ts b/ui/src/app/metadata/configuration/action/restore.action.ts index 04a15fca7..0407e830b 100644 --- a/ui/src/app/metadata/configuration/action/restore.action.ts +++ b/ui/src/app/metadata/configuration/action/restore.action.ts @@ -10,6 +10,9 @@ export enum RestoreActionTypes { UPDATE_RESTORATION_REQUEST = '[Restore Version] Update Changes Request', UPDATE_RESTORATION_SUCCESS = '[Restore Version] Update Changes Success', + UPDATE_STATUS = '[Restore Version] Update Restore Form Status', + SET_SAVING_STATUS = '[Restore Version] Set Saving Status', + CLEAR_VERSION = '[Restore Version] Clear Versions', CANCEL_RESTORE = '[Restore Version] Cancel Restore' } @@ -39,6 +42,18 @@ export class UpdateRestorationChangesSuccess implements Action { constructor(public payload: any) { } } +export class UpdateRestoreFormStatus implements Action { + readonly type = RestoreActionTypes.UPDATE_STATUS; + + constructor(public payload: { [key: string]: string }) { } +} + +export class SetSavingStatus implements Action { + readonly type = RestoreActionTypes.SET_SAVING_STATUS; + + constructor(public payload: boolean) { } +} + export class CancelRestore implements Action { readonly type = RestoreActionTypes.CANCEL_RESTORE; constructor() { } @@ -50,4 +65,6 @@ export type RestoreActionsUnion = | RestoreVersionError | UpdateRestorationChangesRequest | UpdateRestorationChangesSuccess + | UpdateRestoreFormStatus + | SetSavingStatus | CancelRestore; diff --git a/ui/src/app/metadata/configuration/component/editor.component.html b/ui/src/app/metadata/configuration/component/editor.component.html index 53414fcf6..fd71771aa 100644 --- a/ui/src/app/metadata/configuration/component/editor.component.html +++ b/ui/src/app/metadata/configuration/component/editor.component.html @@ -11,4 +11,4 @@ [validators]="validators" [bindings]="bindings" (onChange)="change.emit($event.value)" - (onError)="status.emit($event)"> \ No newline at end of file + (onErrorChange)="status.emit($event)"> \ No newline at end of file diff --git a/ui/src/app/metadata/configuration/component/editor.component.ts b/ui/src/app/metadata/configuration/component/editor.component.ts index 8e0ab78dd..59f3ef375 100644 --- a/ui/src/app/metadata/configuration/component/editor.component.ts +++ b/ui/src/app/metadata/configuration/component/editor.component.ts @@ -18,9 +18,12 @@ export class MetadataEditorComponent { @Output() change: EventEmitter = new EventEmitter(); @Output() status: EventEmitter = new EventEmitter(); + @Output() onLockChange: EventEmitter = new EventEmitter(); lock: FormControl = new FormControl(true); - constructor() {} + constructor() { + this.lock.valueChanges.subscribe(locked => this.onLockChange.emit(locked)); + } } diff --git a/ui/src/app/metadata/configuration/component/metadata-header.component.spec.ts b/ui/src/app/metadata/configuration/component/metadata-header.component.spec.ts index e8db42c36..f3cfd0d45 100644 --- a/ui/src/app/metadata/configuration/component/metadata-header.component.spec.ts +++ b/ui/src/app/metadata/configuration/component/metadata-header.component.spec.ts @@ -9,7 +9,6 @@ import { MetadataHeaderComponent } from './metadata-header.component'; ` @@ -25,7 +24,6 @@ class TestHostComponent { creator: 'foobar', date: new Date().toDateString() }; - versionNumber = 1; isCurrent = false; } diff --git a/ui/src/app/metadata/configuration/container/configuration.component.spec.ts b/ui/src/app/metadata/configuration/container/configuration.component.spec.ts index 0d2fca0d3..71bb05ab0 100644 --- a/ui/src/app/metadata/configuration/container/configuration.component.spec.ts +++ b/ui/src/app/metadata/configuration/container/configuration.component.spec.ts @@ -59,12 +59,4 @@ describe('Metadata Configuration Page Component', () => { it('should load metadata objects', async(() => { expect(app).toBeTruthy(); })); - - describe('hasVersion function', () => { - it('should determine if a version is defined', () => { - expect(app.hasVersion([[{id: 'foo'}], { version: 'foo' }])).toBe('foo'); - expect(app.hasVersion([[{ id: 'foo' }], {}])).toBe('foo'); - expect(app.hasVersion([[], {}])).toBeNull(); - }); - }); }); diff --git a/ui/src/app/metadata/configuration/container/restore-edit-step.component.html b/ui/src/app/metadata/configuration/container/restore-edit-step.component.html index 496aa29f0..be953b86b 100644 --- a/ui/src/app/metadata/configuration/container/restore-edit-step.component.html +++ b/ui/src/app/metadata/configuration/container/restore-edit-step.component.html @@ -2,5 +2,8 @@ [schema]="schema$ | async" [model]="model$ | async" [validators]="validators$ | async" + [lockable]="lockable$ | async" (change)="onChange($event)" + (status)="statusChange$.next($event)" + (onLockChange)="lockChange$.next($event)" > \ No newline at end of file diff --git a/ui/src/app/metadata/configuration/container/restore-edit-step.component.ts b/ui/src/app/metadata/configuration/container/restore-edit-step.component.ts index 2e066a3d2..3d93f5ebd 100644 --- a/ui/src/app/metadata/configuration/container/restore-edit-step.component.ts +++ b/ui/src/app/metadata/configuration/container/restore-edit-step.component.ts @@ -1,18 +1,18 @@ -import { Component } from '@angular/core'; -import { Observable, combineLatest, of } from 'rxjs'; +import { Component, OnDestroy } from '@angular/core'; +import { Observable, combineLatest, of, Subject } from 'rxjs'; import { ActivatedRoute } from '@angular/router'; import { Store } from '@ngrx/store'; import { - ConfigurationState, - getFormattedModel, - getRestorationChanges + ConfigurationState, getFormattedModel } from '../reducer'; -import { getWizardDefinition, getSchema, getValidators } from '../../../wizard/reducer'; -import { Wizard } from '../../../wizard/model'; +import { getWizardDefinition, getSchema, getValidators, getCurrent, getWizardIndex } from '../../../wizard/reducer'; +import { Wizard, WizardStep } from '../../../wizard/model'; import { Metadata } from '../../domain/domain.type'; -import { map, switchMap } from 'rxjs/operators'; +import { map, switchMap, withLatestFrom } from 'rxjs/operators'; import { NAV_FORMATS } from '../../domain/component/editor-nav.component'; -import { UpdateRestorationChangesRequest } from '../action/restore.action'; +import { UpdateRestorationChangesRequest, UpdateRestoreFormStatus } from '../action/restore.action'; +import { LockEditor, UnlockEditor } from '../../../wizard/action/wizard.action'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'restore-edit-step', @@ -20,11 +20,19 @@ import { UpdateRestorationChangesRequest } from '../action/restore.action'; styleUrls: [] }) -export class RestoreEditStepComponent { +export class RestoreEditStepComponent implements OnDestroy { + + private ngUnsubscribe: Subject = new Subject(); + + readonly lockChange$: Subject = new Subject(); + readonly statusChange$: Subject = new Subject(); definition$: Observable> = this.store.select(getWizardDefinition); schema$: Observable = this.store.select(getSchema); model$: Observable = this.store.select(getFormattedModel); + step$: Observable = this.store.select(getCurrent); + + lockable$: Observable = this.step$.pipe(map(step => step.locked)); validators$: Observable; @@ -40,29 +48,31 @@ export class RestoreEditStepComponent { switchMap(selections => this.store.select(getValidators(selections))) ); - this.store.select(getRestorationChanges).subscribe(console.log); + this.step$ + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe(s => this.lockChange$.next(s && s.locked ? true : false)); + + this.lockChange$ + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe(locked => this.store.dispatch(locked ? new LockEditor() : new UnlockEditor())); + + this.statusChange$ + .pipe( + takeUntil(this.ngUnsubscribe), + withLatestFrom(this.store.select(getWizardIndex)) + ) + .subscribe(([errors, currentPage]) => { + const status = { [currentPage]: !(errors.value) ? 'VALID' : 'INVALID' }; + this.store.dispatch(new UpdateRestoreFormStatus(status)); + }); } onChange(changes: any): void { this.store.dispatch(new UpdateRestorationChangesRequest(changes)); } -} -/* -this.valueChangeEmitted$.pipe( - map(changes => changes.value), - withLatestFrom(this.definition$, this.store.select(fromProvider.getSelectedProvider)), - filter(([changes, definition, provider]) => definition && changes && provider), - map(([changes, definition, provider]) => { - const parsed = definition.parser(changes); - return ({ - ...parsed, - metadataFilters: [ - ...provider.metadataFilters, - ...(parsed.metadataFilters || []) - ] - }); - }) - ) - .subscribe(changes => this.store.dispatch(new UpdateProvider(changes))); -*/ + ngOnDestroy() { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); + } +} diff --git a/ui/src/app/metadata/configuration/container/restore-edit.component.html b/ui/src/app/metadata/configuration/container/restore-edit.component.html index 6199dc016..7b1569f9a 100644 --- a/ui/src/app/metadata/configuration/container/restore-edit.component.html +++ b/ui/src/app/metadata/configuration/container/restore-edit.component.html @@ -4,14 +4,6 @@ - - -

@@ -46,15 +38,8 @@
diff --git a/ui/src/app/metadata/configuration/container/restore-edit.component.ts b/ui/src/app/metadata/configuration/container/restore-edit.component.ts index a50c0a52b..7bd939ee4 100644 --- a/ui/src/app/metadata/configuration/container/restore-edit.component.ts +++ b/ui/src/app/metadata/configuration/container/restore-edit.component.ts @@ -2,12 +2,17 @@ import { Component } from '@angular/core'; import { Observable, of } from 'rxjs'; import { Store } from '@ngrx/store'; import { - ConfigurationState, getConfigurationModelKind + ConfigurationState, + getConfigurationModelKind, + getRestorationIsSaving, + getRestorationIsValid, + getInvalidRestorationForms } from '../../configuration/reducer'; import { RestoreVersionRequest, CancelRestore } from '../action/restore.action'; import { NAV_FORMATS } from '../../domain/component/editor-nav.component'; import { Metadata } from '../../domain/domain.type'; import { getVersionModel } from '../reducer'; +import { map } from 'rxjs/operators'; @Component({ @@ -21,10 +26,9 @@ export class RestoreEditComponent { model$: Observable = this.store.select(getVersionModel); kind$: Observable = this.store.select(getConfigurationModelKind); - isInvalid$: Observable = of(false); - canFilter$: Observable = of(false); - status$: Observable = of('VALID'); - isSaving$: Observable = of(false); + isInvalid$: Observable = this.store.select(getRestorationIsValid).pipe(map(v => !v)); + status$: Observable = this.store.select(getInvalidRestorationForms); + isSaving$: Observable = this.store.select(getRestorationIsSaving); validators$: Observable; @@ -32,7 +36,9 @@ export class RestoreEditComponent { constructor( private store: Store - ) {} + ) { + // this.status$.subscribe(console.log); + } save() { this.store.dispatch(new RestoreVersionRequest()); diff --git a/ui/src/app/metadata/configuration/container/restore.component.spec.ts b/ui/src/app/metadata/configuration/container/restore.component.spec.ts index e79cbcac9..ef3b0f3fa 100644 --- a/ui/src/app/metadata/configuration/container/restore.component.spec.ts +++ b/ui/src/app/metadata/configuration/container/restore.component.spec.ts @@ -11,6 +11,7 @@ import { MockI18nModule } from '../../../../testing/i18n.stub'; import { RestoreComponent } from './restore.component'; import { of } from 'rxjs'; import { DatePipe } from '@angular/common'; +import { Router } from '@angular/router'; @Component({ template: ` @@ -28,6 +29,7 @@ describe('Metadata Restore Page Component', () => { let instance: TestHostComponent; let app: RestoreComponent; let store: Store; + let router: Router; beforeEach(async(() => { TestBed.configureTestingModule({ @@ -51,6 +53,7 @@ describe('Metadata Restore Page Component', () => { }).compileComponents(); store = TestBed.get(Store); + router = TestBed.get(Router); spyOn(store, 'dispatch'); spyOn(store, 'select').and.callFake(() => of(new Date().toDateString())); @@ -62,15 +65,15 @@ describe('Metadata Restore Page Component', () => { it('should load metadata objects', async(() => { expect(app).toBeTruthy(); - expect(store.select).toHaveBeenCalledTimes(4); + expect(store.select).toHaveBeenCalledTimes(1); expect(store.dispatch).not.toHaveBeenCalled(); })); describe('restore method', () => { - it('should emit a value from the restore subject', () => { - spyOn(app.subj, 'next').and.callThrough(); + it('should navigate to the restore edit page', () => { + spyOn(router, 'navigate').and.callThrough(); app.restore(); - expect(app.subj.next).toHaveBeenCalled(); + expect(router.navigate).toHaveBeenCalled(); }); }); }); diff --git a/ui/src/app/metadata/configuration/effect/restore.effect.ts b/ui/src/app/metadata/configuration/effect/restore.effect.ts index b34cfe997..7be47103e 100644 --- a/ui/src/app/metadata/configuration/effect/restore.effect.ts +++ b/ui/src/app/metadata/configuration/effect/restore.effect.ts @@ -9,7 +9,8 @@ import { RestoreVersionError, CancelRestore, UpdateRestorationChangesRequest, - UpdateRestorationChangesSuccess + UpdateRestorationChangesSuccess, + SetSavingStatus } from '../action/restore.action'; import { MetadataHistoryService } from '../service/history.service'; import { of } from 'rxjs'; @@ -28,6 +29,7 @@ import { } from '../reducer'; import { SetMetadata } from '../action/configuration.action'; import { removeNulls } from '../../../shared/util'; +import { getModel } from '../../../wizard/reducer'; @Injectable() @@ -53,6 +55,24 @@ export class RestoreEffects { ) ); + @Effect() + restoreVersionSaving$ = this.actions$.pipe( + ofType(RestoreActionTypes.RESTORE_VERSION_REQUEST), + map(() => new SetSavingStatus(true)) + ); + + @Effect() + restoreVersionSaved$ = this.actions$.pipe( + ofType(RestoreActionTypes.RESTORE_VERSION_SUCCESS), + map(() => new SetSavingStatus(false)) + ); + + @Effect() + restoreVersionError$ = this.actions$.pipe( + ofType(RestoreActionTypes.RESTORE_VERSION_ERROR), + map(() => new SetSavingStatus(false)) + ); + @Effect({ dispatch: false }) restoreVersionCancel$ = this.actions$.pipe( ofType(RestoreActionTypes.CANCEL_RESTORE), diff --git a/ui/src/app/metadata/configuration/reducer/configuration.reducer.spec.ts b/ui/src/app/metadata/configuration/reducer/configuration.reducer.spec.ts index a0dd05d58..714da74b4 100644 --- a/ui/src/app/metadata/configuration/reducer/configuration.reducer.spec.ts +++ b/ui/src/app/metadata/configuration/reducer/configuration.reducer.spec.ts @@ -27,7 +27,7 @@ describe('Configuration Reducer', () => { describe('SET_DEFINITION action', () => { it('should set the state definition', () => { - const action = new actions.SetDefinition(definition); + const action = new actions.SetConfigurationDefinition(definition); const result = reducer(initialState, action); expect(result).toEqual({ ...initialState, definition }); @@ -36,7 +36,7 @@ describe('Configuration Reducer', () => { describe('SET_SCHEMA action', () => { it('should set the state schema', () => { - const action = new actions.SetSchema(schema); + const action = new actions.SetConfigurationSchema(schema); const result = reducer(initialState, action); expect(result).toEqual({ ...initialState, schema }); diff --git a/ui/src/app/metadata/configuration/reducer/index.ts b/ui/src/app/metadata/configuration/reducer/index.ts index fe961cfc3..b64b6e070 100644 --- a/ui/src/app/metadata/configuration/reducer/index.ts +++ b/ui/src/app/metadata/configuration/reducer/index.ts @@ -9,7 +9,7 @@ import * as fromRestore from './restore.reducer'; import { WizardStep } from '../../../wizard/model'; import * as utils from '../../domain/utility/configuration'; -import { getSplitSchema } from '../../../wizard/reducer'; +import { getSplitSchema, getModel } from '../../../wizard/reducer'; import { getInCollectionFn } from '../../domain/domain.util'; import { MetadataConfiguration } from '../model/metadata-configuration'; import { Metadata } from '../../domain/domain.type'; @@ -192,13 +192,34 @@ export const getInvalidRestorationForms = createSelector(getRestoreState, fromRe export const getFormattedModel = createSelector( getVersionModel, getConfigurationDefinition, - (model, definition) => definition.formatter(model) + (model, definition) => definition ? definition.formatter(model) : null +); + +export const getFormattedChanges = createSelector( + getRestorationChanges, + getConfigurationDefinition, + (model, definition) => definition ? definition.formatter(model) : null +); + +export const getFormattedModelWithChanges = createSelector( + getVersionModel, + getRestorationChanges, + getConfigurationDefinition, + (model, changes, definition) => definition ? definition.formatter({ + ...model, + ...changes + }) : null ); export const getRestorationModel = createSelector( getVersionModel, getRestorationChanges, - (model, changes) => model + getModel, + (model, changes, empty) => ({ + ...model, + ...empty, + ...changes + }) ); // Mixed states @@ -229,4 +250,4 @@ export const getConfigurationModelType = createSelector(getConfigurationModel, g export const getConfigurationHasXml = createSelector(getConfigurationXml, xml => !!xml); export const getConfigurationFilters = createSelector(getConfigurationModel, model => model.metadataFilters); -export const getConfigurationVersionDate = createSelector(getRestoreModel, version => version && version.modifiedDate); +export const getConfigurationVersionDate = createSelector(getVersionModel, version => version && version.modifiedDate); diff --git a/ui/src/app/metadata/configuration/reducer/restore.reducer.spec.ts b/ui/src/app/metadata/configuration/reducer/restore.reducer.spec.ts new file mode 100644 index 000000000..9aa588b98 --- /dev/null +++ b/ui/src/app/metadata/configuration/reducer/restore.reducer.spec.ts @@ -0,0 +1,34 @@ +import { reducer } from './restore.reducer'; +import * as fromRestore from './restore.reducer'; +import * as actions from '../action/restore.action'; + +describe('Restore Reducer', () => { + + const baseState = fromRestore.initialState; + + describe('undefined action', () => { + it('should return the default state', () => { + const result = reducer(undefined, {} as any); + + expect(result).toEqual(fromRestore.initialState); + }); + }); + + describe('SET_HISTORY action', () => { + it('should set the state metadata model', () => { + const serviceEnabled = true; + const action = new actions.UpdateRestorationChangesSuccess({ serviceEnabled }); + const result = reducer(fromRestore.initialState, action); + + expect(Object.keys(result.changes)).toEqual({serviceEnabled: true}); + }); + }); + + describe('selector function', () => { + describe('getChanges', () => { + it('should return the selected version id', () => { + expect(fromRestore.getChanges({ ...baseState, serviceEnabled: false })).toEqual({ serviceEnabled: false }); + }); + }); + }); +}); diff --git a/ui/src/app/metadata/configuration/reducer/restore.reducer.ts b/ui/src/app/metadata/configuration/reducer/restore.reducer.ts index 4ca626459..f2e000728 100644 --- a/ui/src/app/metadata/configuration/reducer/restore.reducer.ts +++ b/ui/src/app/metadata/configuration/reducer/restore.reducer.ts @@ -23,6 +23,20 @@ export function reducer(state = initialState, action: RestoreActionsUnion): Rest ...action.payload } }; + case RestoreActionTypes.SET_SAVING_STATUS: + return { + ...state, + saving: action.payload + }; + case RestoreActionTypes.UPDATE_STATUS: { + return { + ...state, + status: { + ...state.status, + ...action.payload + } + }; + } default: { return state; } diff --git a/ui/src/app/metadata/configuration/service/history.service.spec.ts b/ui/src/app/metadata/configuration/service/history.service.spec.ts index 0927f8e70..e4fa72dac 100644 --- a/ui/src/app/metadata/configuration/service/history.service.spec.ts +++ b/ui/src/app/metadata/configuration/service/history.service.spec.ts @@ -80,21 +80,4 @@ describe(`Attributes Service`, () => { } ))); }); - - describe('restoreVersion method', () => { - it(`should send a put request`, async(inject([MetadataHistoryService, HttpTestingController], - (service: MetadataHistoryService, backend: HttpTestingController) => { - const resourceId = 'foo'; - const type = 'resource'; - const versionId = '1'; - const response = {version: 'bar'} as Metadata; - spyOn(service, 'getVersions').and.returnValue(of([response, { ...response, version: 'foo' }])); - spyOn(service, 'updateVersion').and.returnValue(of(response)); - service.restoreVersion(resourceId, type, versionId).subscribe(updated => { - expect(service.getVersions).toHaveBeenCalled(); - expect(service.updateVersion).toHaveBeenCalled(); - }); - } - ))); - }); }); diff --git a/ui/src/app/metadata/configuration/service/history.service.ts b/ui/src/app/metadata/configuration/service/history.service.ts index 4f480f916..9d3edc212 100644 --- a/ui/src/app/metadata/configuration/service/history.service.ts +++ b/ui/src/app/metadata/configuration/service/history.service.ts @@ -32,7 +32,7 @@ export class MetadataHistoryService { )); } - getVersion(resourceId: string, type: string, versionId: string): Observable { + getVersion(resourceId: string, type: string, versionId: string = null): Observable { const api = versionId ? `/${this.base}/${PATHS[type]}/${resourceId}/${this.path}/${versionId}` : diff --git a/ui/src/app/metadata/domain/component/wizard-summary.component.spec.ts b/ui/src/app/metadata/domain/component/wizard-summary.component.spec.ts index 107ef7241..ecd51b1b9 100644 --- a/ui/src/app/metadata/domain/component/wizard-summary.component.spec.ts +++ b/ui/src/app/metadata/domain/component/wizard-summary.component.spec.ts @@ -12,6 +12,8 @@ import { SummaryPropertyComponent } from './summary-property.component'; import { SCHEMA } from '../../../../testing/form-schema.stub'; import { MockI18nModule } from '../../../../testing/i18n.stub'; import { MetadataProviderWizard } from '../../provider/model'; +import { AttributesService } from '../service/attributes.service'; +import { MockAttributeService } from '../../../../testing/attributes.stub'; @Component({ template: ` @@ -54,7 +56,8 @@ describe('Provider Wizard Summary Component', () => { TestHostComponent ], providers: [ - { provide: WidgetRegistry, useClass: DefaultWidgetRegistry } + { provide: WidgetRegistry, useClass: DefaultWidgetRegistry }, + { provide: AttributesService, useClass: MockAttributeService } ] }).compileComponents(); diff --git a/ui/src/app/metadata/domain/component/wizard-summary.component.ts b/ui/src/app/metadata/domain/component/wizard-summary.component.ts index 82cd86c8d..80f246c83 100644 --- a/ui/src/app/metadata/domain/component/wizard-summary.component.ts +++ b/ui/src/app/metadata/domain/component/wizard-summary.component.ts @@ -43,16 +43,19 @@ export class WizardSummaryComponent implements OnChanges { .filter(step => step.id !== 'summary') .map( (step: WizardStep, num: number) => { + const { id, index, label } = step; + const split = getSplitSchema(schema, step); + const properties = getStepProperties( + split, + def.formatter(model), + schema.definitions || {} + ); return ({ - id: step.id, + id, pageNumber: num + 1, - index: step.index, - label: step.label, - properties: getStepProperties( - getSplitSchema(schema, step), - def.formatter(model), - schema.definitions || {} - ) + index, + label, + properties }); } ); diff --git a/ui/src/app/metadata/provider/container/provider-edit-step.component.ts b/ui/src/app/metadata/provider/container/provider-edit-step.component.ts index be79bb1fa..c869c8e9c 100644 --- a/ui/src/app/metadata/provider/container/provider-edit-step.component.ts +++ b/ui/src/app/metadata/provider/container/provider-edit-step.component.ts @@ -1,6 +1,7 @@ import { Component, OnDestroy } from '@angular/core'; import { Observable, Subject } from 'rxjs'; import { Store } from '@ngrx/store'; +import { FormControl } from '@angular/forms'; import * as fromProvider from '../reducer'; import { UpdateStatus } from '../action/editor.action'; @@ -9,9 +10,8 @@ import { MetadataProvider } from '../../domain/model'; import { LockEditor, UnlockEditor } from '../../../wizard/action/wizard.action'; import * as fromWizard from '../../../wizard/reducer'; -import { withLatestFrom, map, skipWhile, distinctUntilChanged, takeUntil, filter } from 'rxjs/operators'; +import { withLatestFrom, map, distinctUntilChanged, filter } from 'rxjs/operators'; import { UpdateProvider } from '../action/entity.action'; -import { FormControl } from '@angular/forms'; @Component({ selector: 'provider-edit-step', diff --git a/ui/src/app/metadata/provider/model/base.provider.form.ts b/ui/src/app/metadata/provider/model/base.provider.form.ts index 645e21f23..1fce5263d 100644 --- a/ui/src/app/metadata/provider/model/base.provider.form.ts +++ b/ui/src/app/metadata/provider/model/base.provider.form.ts @@ -1,12 +1,12 @@ import { Wizard } from '../../../wizard/model'; import { BaseMetadataProvider } from '../../domain/model/providers'; -import { getProviderNames, getProviderXmlIds } from '../reducer'; +import { getFilteredProviderNames, getFilteredProviderXmlIds } from '../reducer'; export const BaseMetadataProviderEditor: Wizard = { label: 'BaseMetadataProvider', type: 'BaseMetadataResolver', schema: '', - validatorParams: [getProviderNames, getProviderXmlIds], + validatorParams: [getFilteredProviderNames, getFilteredProviderXmlIds], getValidators(namesList: string[], xmlIdList: string[]): any { const validators = { '/': (value, property, form_current) => { diff --git a/ui/src/app/metadata/provider/reducer/index.ts b/ui/src/app/metadata/provider/reducer/index.ts index d40313299..562a4ab44 100644 --- a/ui/src/app/metadata/provider/reducer/index.ts +++ b/ui/src/app/metadata/provider/reducer/index.ts @@ -66,9 +66,19 @@ export const getProviderIds = createSelector(getCollectionState, fromCollection. export const getProviderCollectionIsLoaded = createSelector(getCollectionState, fromCollection.getIsLoaded); export const getProviderNames = createSelector(getAllProviders, (providers: MetadataProvider[]) => providers.map(p => p.name)); +export const getFilteredProviderNames = createSelector( + getProviderNames, + getSelectedProvider, (names, provider) => names.filter(name => name !== provider.name) +); + export const getProviderFilters = createSelector(getSelectedProvider, provider => provider.metadataFilters); export const getProviderXmlIds = createSelector(getAllProviders, (providers: MetadataProvider[]) => providers.map(p => p.xmlId)); export const getOrderedProviders = createSelector(getAllProviders, getProviderOrder, utils.mergeOrderFn); export const getOrderedProvidersInSearch = createSelector(getAllProviders, getProviderOrder, utils.mergeOrderFn); + +export const getFilteredProviderXmlIds = createSelector( + getProviderXmlIds, + getSelectedProvider, (ids, provider) => ids.filter(id => id !== provider.xmlId) +); diff --git a/ui/src/app/metadata/resolver/container/resolver-wizard.component.ts b/ui/src/app/metadata/resolver/container/resolver-wizard.component.ts index ffabac236..56848ad5e 100644 --- a/ui/src/app/metadata/resolver/container/resolver-wizard.component.ts +++ b/ui/src/app/metadata/resolver/container/resolver-wizard.component.ts @@ -117,13 +117,13 @@ export class ResolverWizardComponent implements OnDestroy, CanComponentDeactivat this.summary$ = combine( this.store.select(fromWizard.getWizardDefinition), - this.store.select(fromWizard.getSchema), - this.store.select(fromResolver.getEntityChanges) + this.store.select(fromWizard.getSchemaObject), + this.store.select(fromResolver.getDraftModelWithChanges) ).pipe( map(([definition, schema, model]) => ( { definition, - schema, + schema: schema || {}, model } )) diff --git a/ui/src/app/metadata/resolver/reducer/index.ts b/ui/src/app/metadata/resolver/reducer/index.ts index 43bc2fbc7..a1cc5256d 100644 --- a/ui/src/app/metadata/resolver/reducer/index.ts +++ b/ui/src/app/metadata/resolver/reducer/index.ts @@ -5,6 +5,7 @@ import * as fromSearch from './search.reducer'; import * as fromCopy from './copy.reducer'; import * as fromDraft from './draft.reducer'; import * as fromCollection from './collection.reducer'; +import * as fromWizard from '../../../wizard/reducer'; import { combineAllFn, getEntityIdsFn, getInCollectionFn, doesExistFn } from '../../domain/domain.util'; @@ -109,3 +110,17 @@ export const getAllOtherIds = createSelector( getSelectedResolverId, (ids, selected) => ids.filter(id => id !== selected) ); + +export const getDraftModelWithChanges = createSelector( + fromWizard.getSchema, + fromWizard.getModel, + getSelectedDraft, + getEntityChanges, + fromWizard.getWizardDefinition, + (schema, wizardModel, selectedDraft, changes, definition) => definition.formatter({ + ...wizardModel, + ...selectedDraft, + ...changes + }) +); + diff --git a/ui/src/app/wizard/model/form-definition.ts b/ui/src/app/wizard/model/form-definition.ts index 4402aa354..bde8e10ad 100644 --- a/ui/src/app/wizard/model/form-definition.ts +++ b/ui/src/app/wizard/model/form-definition.ts @@ -5,7 +5,7 @@ export interface FormDefinition { type: string; schema: string; bindings?: any; - validatorParams: any[]; + validatorParams: any; getEntity?(entity: any): any; parser(changes: Partial, schema?: any); formatter(changes: Partial, schema?: any); diff --git a/ui/src/app/wizard/reducer/wizard.reducer.ts b/ui/src/app/wizard/reducer/wizard.reducer.ts index 11205afb3..819a82533 100644 --- a/ui/src/app/wizard/reducer/wizard.reducer.ts +++ b/ui/src/app/wizard/reducer/wizard.reducer.ts @@ -5,7 +5,6 @@ export interface State { index: string; disabled: boolean; definition: Wizard; - schemaPath: string; loading: boolean; schema: any; From 51bfa3c688dbd7f838a3642b4e3efbafd244f120 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Fri, 30 Aug 2019 12:20:13 -0700 Subject: [PATCH 22/27] SHIBUI-1385 Added unit tests --- .../configuration/action/restore.action.ts | 4 +- .../container/configuration.component.spec.ts | 8 +- .../restore-edit-step.component.spec.ts | 107 +++++++++++++++++ .../container/restore-edit-step.component.ts | 28 +++-- .../container/restore-edit.component.spec.ts | 79 +++++++++++++ .../version-options.component.spec.ts | 82 +++++++++++++ .../container/version.component.spec.ts | 56 +++++++++ .../reducer/restore.reducer.spec.ts | 74 +++++++++++- .../configuration/reducer/restore.reducer.ts | 2 +- .../reducer/version.reducer.spec.ts | 108 ++++++++++++++++++ 10 files changed, 522 insertions(+), 26 deletions(-) create mode 100644 ui/src/app/metadata/configuration/container/restore-edit-step.component.spec.ts create mode 100644 ui/src/app/metadata/configuration/container/restore-edit.component.spec.ts create mode 100644 ui/src/app/metadata/configuration/container/version-options.component.spec.ts create mode 100644 ui/src/app/metadata/configuration/container/version.component.spec.ts diff --git a/ui/src/app/metadata/configuration/action/restore.action.ts b/ui/src/app/metadata/configuration/action/restore.action.ts index 0407e830b..978e9eee4 100644 --- a/ui/src/app/metadata/configuration/action/restore.action.ts +++ b/ui/src/app/metadata/configuration/action/restore.action.ts @@ -34,12 +34,12 @@ export class RestoreVersionError implements Action { export class UpdateRestorationChangesRequest implements Action { readonly type = RestoreActionTypes.UPDATE_RESTORATION_REQUEST; - constructor(public payload: any) { } + constructor(public payload: Partial) { } } export class UpdateRestorationChangesSuccess implements Action { readonly type = RestoreActionTypes.UPDATE_RESTORATION_SUCCESS; - constructor(public payload: any) { } + constructor(public payload: Partial) { } } export class UpdateRestoreFormStatus implements Action { diff --git a/ui/src/app/metadata/configuration/container/configuration.component.spec.ts b/ui/src/app/metadata/configuration/container/configuration.component.spec.ts index 71bb05ab0..b1d9bc2f2 100644 --- a/ui/src/app/metadata/configuration/container/configuration.component.spec.ts +++ b/ui/src/app/metadata/configuration/container/configuration.component.spec.ts @@ -1,10 +1,9 @@ -import { Component, ViewChild, Input } from '@angular/core'; +import { Component, ViewChild } from '@angular/core'; import { TestBed, async, ComponentFixture } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { StoreModule, combineReducers } from '@ngrx/store'; import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; -import { MetadataConfiguration } from '../model/metadata-configuration'; import { ConfigurationComponent } from './configuration.component'; import * as fromConfiguration from '../reducer'; import * as fromProviders from '../../provider/reducer'; @@ -19,11 +18,6 @@ import { MockI18nModule } from '../../../../testing/i18n.stub'; class TestHostComponent { @ViewChild(ConfigurationComponent) public componentUnderTest: ConfigurationComponent; - - configuration: MetadataConfiguration = { - dates: [], - sections: [] - }; } describe('Metadata Configuration Page Component', () => { diff --git a/ui/src/app/metadata/configuration/container/restore-edit-step.component.spec.ts b/ui/src/app/metadata/configuration/container/restore-edit-step.component.spec.ts new file mode 100644 index 000000000..7d468b5af --- /dev/null +++ b/ui/src/app/metadata/configuration/container/restore-edit-step.component.spec.ts @@ -0,0 +1,107 @@ +import { Component, ViewChild, NO_ERRORS_SCHEMA } from '@angular/core'; +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { StoreModule, combineReducers, Store } from '@ngrx/store'; +import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; + +import { RestoreEditStepComponent } from './restore-edit-step.component'; +import * as fromConfiguration from '../reducer'; +import * as fromProviders from '../../provider/reducer'; +import * as fromResolvers from '../../resolver/reducer'; +import * as fromWizard from '../../../wizard/reducer'; +import { MockI18nModule } from '../../../../testing/i18n.stub'; + +import { + RestoreActionTypes +} from '../action/restore.action'; +import { WizardActionTypes } from '../../../wizard/action/wizard.action'; + +@Component({ + template: ` + + ` +}) +class TestHostComponent { + @ViewChild(RestoreEditStepComponent) + public componentUnderTest: RestoreEditStepComponent; +} + +describe('Restore Version Edit Step Component', () => { + + let fixture: ComponentFixture; + let instance: TestHostComponent; + let app: RestoreEditStepComponent; + let store: Store; + let dispatchSpy; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + NgbDropdownModule, + StoreModule.forRoot({ + 'metadata-configuration': combineReducers(fromConfiguration.reducers), + 'provider': combineReducers(fromProviders.reducers), + 'resolver': combineReducers(fromResolvers.reducers), + 'wizard': combineReducers(fromWizard.reducers) + }), + MockI18nModule, + RouterTestingModule + ], + declarations: [ + RestoreEditStepComponent, + TestHostComponent + ], + schemas: [ + NO_ERRORS_SCHEMA + ] + }).compileComponents(); + + store = TestBed.get(Store); + dispatchSpy = spyOn(store, 'dispatch'); + + fixture = TestBed.createComponent(TestHostComponent); + instance = fixture.componentInstance; + app = instance.componentUnderTest; + fixture.detectChanges(); + })); + + it('should compile', () => { + expect(app).toBeTruthy(); + }); + + describe('onChange', () => { + it('should dispatch an update changes event', () => { + app.onChange({ name: 'test' }); + expect(store.dispatch).toHaveBeenCalled(); + expect(dispatchSpy.calls.mostRecent().args[0].type).toBe(RestoreActionTypes.UPDATE_RESTORATION_REQUEST); + }); + }); + + describe('updateStatus', () => { + it('should dispatch an update form status event', () => { + app.updateStatus([{ value: 'foo' }, 'common']); + expect(store.dispatch).toHaveBeenCalled(); + expect(dispatchSpy.calls.mostRecent().args[0].type).toBe(RestoreActionTypes.UPDATE_STATUS); + }); + + it('should dispatch an update form status event', () => { + app.updateStatus([{}, 'common']); + expect(store.dispatch).toHaveBeenCalled(); + expect(dispatchSpy.calls.mostRecent().args[0].type).toBe(RestoreActionTypes.UPDATE_STATUS); + }); + }); + + describe('updateLock', () => { + it('should dispatch a LockEditor event when passed a locked status', () => { + app.updateLock(true); + expect(store.dispatch).toHaveBeenCalled(); + expect(dispatchSpy.calls.mostRecent().args[0].type).toBe(WizardActionTypes.LOCK); + }); + + it('should dispatch a UnlockEditor event when passed a locked status', () => { + app.updateLock(false); + expect(store.dispatch).toHaveBeenCalled(); + expect(dispatchSpy.calls.mostRecent().args[0].type).toBe(WizardActionTypes.UNLOCK); + }); + }); +}); diff --git a/ui/src/app/metadata/configuration/container/restore-edit-step.component.ts b/ui/src/app/metadata/configuration/container/restore-edit-step.component.ts index 3d93f5ebd..904cf43f6 100644 --- a/ui/src/app/metadata/configuration/container/restore-edit-step.component.ts +++ b/ui/src/app/metadata/configuration/container/restore-edit-step.component.ts @@ -8,7 +8,7 @@ import { import { getWizardDefinition, getSchema, getValidators, getCurrent, getWizardIndex } from '../../../wizard/reducer'; import { Wizard, WizardStep } from '../../../wizard/model'; import { Metadata } from '../../domain/domain.type'; -import { map, switchMap, withLatestFrom } from 'rxjs/operators'; +import { map, switchMap, withLatestFrom, filter } from 'rxjs/operators'; import { NAV_FORMATS } from '../../domain/component/editor-nav.component'; import { UpdateRestorationChangesRequest, UpdateRestoreFormStatus } from '../action/restore.action'; import { LockEditor, UnlockEditor } from '../../../wizard/action/wizard.action'; @@ -32,45 +32,51 @@ export class RestoreEditStepComponent implements OnDestroy { model$: Observable = this.store.select(getFormattedModel); step$: Observable = this.store.select(getCurrent); - lockable$: Observable = this.step$.pipe(map(step => step.locked)); + lockable$: Observable = this.step$.pipe(filter(s => !!s), map(step => step.locked)); validators$: Observable; formats = NAV_FORMATS; constructor( - private store: Store, - private route: ActivatedRoute + private store: Store ) { this.validators$ = this.definition$.pipe( + filter(def => !!def), map(def => def.validatorParams), switchMap(params => combineLatest(params.map(p => this.store.select(p)))), switchMap(selections => this.store.select(getValidators(selections))) ); this.step$ - .pipe(takeUntil(this.ngUnsubscribe)) - .subscribe(s => this.lockChange$.next(s && s.locked ? true : false)); + .pipe(takeUntil(this.ngUnsubscribe), filter(step => !!step)) + .subscribe(s => this.lockChange$.next(s.locked ? true : false)); this.lockChange$ .pipe(takeUntil(this.ngUnsubscribe)) - .subscribe(locked => this.store.dispatch(locked ? new LockEditor() : new UnlockEditor())); + .subscribe(this.updateLock); this.statusChange$ .pipe( takeUntil(this.ngUnsubscribe), withLatestFrom(this.store.select(getWizardIndex)) ) - .subscribe(([errors, currentPage]) => { - const status = { [currentPage]: !(errors.value) ? 'VALID' : 'INVALID' }; - this.store.dispatch(new UpdateRestoreFormStatus(status)); - }); + .subscribe(this.updateStatus); } onChange(changes: any): void { this.store.dispatch(new UpdateRestorationChangesRequest(changes)); } + updateStatus([errors, currentPage]) { + const status = { [currentPage]: !(errors.value) ? 'VALID' : 'INVALID' }; + this.store.dispatch(new UpdateRestoreFormStatus(status)); + } + + updateLock(locked: boolean) { + this.store.dispatch(locked ? new LockEditor() : new UnlockEditor()); + } + ngOnDestroy() { this.ngUnsubscribe.next(); this.ngUnsubscribe.complete(); diff --git a/ui/src/app/metadata/configuration/container/restore-edit.component.spec.ts b/ui/src/app/metadata/configuration/container/restore-edit.component.spec.ts new file mode 100644 index 000000000..847ab5623 --- /dev/null +++ b/ui/src/app/metadata/configuration/container/restore-edit.component.spec.ts @@ -0,0 +1,79 @@ +import { Component, ViewChild, NO_ERRORS_SCHEMA } from '@angular/core'; +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { StoreModule, combineReducers, Store } from '@ngrx/store'; +import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; + +import { RestoreEditComponent } from './restore-edit.component'; +import * as fromConfiguration from '../reducer'; +import * as fromProviders from '../../provider/reducer'; +import * as fromResolvers from '../../resolver/reducer'; +import { MockI18nModule } from '../../../../testing/i18n.stub'; +import { RestoreActionTypes } from '../action/restore.action'; + +@Component({ + template: ` + + ` +}) +class TestHostComponent { + @ViewChild(RestoreEditComponent) + public componentUnderTest: RestoreEditComponent; +} + +describe('Restore Version Edit Component', () => { + + let fixture: ComponentFixture; + let instance: TestHostComponent; + let app: RestoreEditComponent; + let store: Store; + let dispatchSpy; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + NgbDropdownModule, + StoreModule.forRoot({ + 'metadata-configuration': combineReducers(fromConfiguration.reducers), + 'provider': combineReducers(fromProviders.reducers), + 'resolver': combineReducers(fromResolvers.reducers) + }), + MockI18nModule, + RouterTestingModule + ], + declarations: [ + RestoreEditComponent, + TestHostComponent + ], + schemas: [ NO_ERRORS_SCHEMA ] + }).compileComponents(); + + store = TestBed.get(Store); + dispatchSpy = spyOn(store, 'dispatch'); + + fixture = TestBed.createComponent(TestHostComponent); + instance = fixture.componentInstance; + app = instance.componentUnderTest; + fixture.detectChanges(); + })); + + it('should compile', () => { + expect(app).toBeTruthy(); + }); + + describe('save', () => { + it('should dispatch a save request event', () => { + app.save(); + expect(store.dispatch).toHaveBeenCalled(); + expect(dispatchSpy.calls.mostRecent().args[0].type).toBe(RestoreActionTypes.RESTORE_VERSION_REQUEST); + }); + }); + + describe('cancel', () => { + it('should dispatch a cancel request event', () => { + app.cancel(); + expect(store.dispatch).toHaveBeenCalled(); + expect(dispatchSpy.calls.mostRecent().args[0].type).toBe(RestoreActionTypes.CANCEL_RESTORE); + }); + }); +}); diff --git a/ui/src/app/metadata/configuration/container/version-options.component.spec.ts b/ui/src/app/metadata/configuration/container/version-options.component.spec.ts new file mode 100644 index 000000000..86f78f839 --- /dev/null +++ b/ui/src/app/metadata/configuration/container/version-options.component.spec.ts @@ -0,0 +1,82 @@ +import { Component, ViewChild, NO_ERRORS_SCHEMA } from '@angular/core'; +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { StoreModule, combineReducers } from '@ngrx/store'; +import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; + +import { VersionOptionsComponent } from './version-options.component'; +import * as fromConfiguration from '../reducer'; +import * as fromProviders from '../../provider/reducer'; +import * as fromResolvers from '../../resolver/reducer'; +import { MockI18nModule } from '../../../../testing/i18n.stub'; +import { Metadata } from '../../domain/domain.type'; +import { ViewportScroller } from '@angular/common'; + +@Component({ + template: ` + + ` +}) +class TestHostComponent { + @ViewChild(VersionOptionsComponent) + public componentUnderTest: VersionOptionsComponent; +} + +describe('Metadata Version Options Page Component', () => { + + let fixture: ComponentFixture; + let instance: TestHostComponent; + let app: VersionOptionsComponent; + let scroller: ViewportScroller; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + NgbDropdownModule, + StoreModule.forRoot({ + 'metadata-configuration': combineReducers(fromConfiguration.reducers), + 'provider': combineReducers(fromProviders.reducers), + 'resolver': combineReducers(fromResolvers.reducers) + }), + MockI18nModule, + RouterTestingModule + ], + declarations: [ + VersionOptionsComponent, + TestHostComponent + ], + schemas: [ NO_ERRORS_SCHEMA ] + }).compileComponents(); + + scroller = TestBed.get(ViewportScroller); + + fixture = TestBed.createComponent(TestHostComponent); + instance = fixture.componentInstance; + app = instance.componentUnderTest; + fixture.detectChanges(); + })); + + it('should compile', () => { + expect(app).toBeTruthy(); + }); + + describe('setModel method', () => { + it('should set attributes based on the passed data', () => { + app.setModel({ id: 'foo', '@type': 'bar' } as Metadata); + expect(app.id).toBe('foo'); + expect(app.kind).toBe('provider'); + + app.setModel({ resourceId: 'baz' } as Metadata); + expect(app.id).toBe('baz'); + expect(app.kind).toBe('resolver'); + }); + }); + + describe('onScrollTo method', () => { + it('should set attributes based on the passed data', () => { + spyOn(scroller, 'scrollToAnchor'); + app.onScrollTo('foo'); + expect(scroller.scrollToAnchor).toHaveBeenCalledWith('foo'); + }); + }); +}); diff --git a/ui/src/app/metadata/configuration/container/version.component.spec.ts b/ui/src/app/metadata/configuration/container/version.component.spec.ts new file mode 100644 index 000000000..ae745f1b4 --- /dev/null +++ b/ui/src/app/metadata/configuration/container/version.component.spec.ts @@ -0,0 +1,56 @@ +import { Component, ViewChild } from '@angular/core'; +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { StoreModule, combineReducers } from '@ngrx/store'; +import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; + +import { VersionComponent } from './version.component'; +import * as fromConfiguration from '../reducer'; +import * as fromProviders from '../../provider/reducer'; +import * as fromResolvers from '../../resolver/reducer'; +import { MockI18nModule } from '../../../../testing/i18n.stub'; + +@Component({ + template: ` + + ` +}) +class TestHostComponent { + @ViewChild(VersionComponent) + public componentUnderTest: VersionComponent; +} + +describe('Metadata Version Page Component', () => { + + let fixture: ComponentFixture; + let instance: TestHostComponent; + let app: VersionComponent; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + NgbDropdownModule, + StoreModule.forRoot({ + 'metadata-configuration': combineReducers(fromConfiguration.reducers), + 'provider': combineReducers(fromProviders.reducers), + 'resolver': combineReducers(fromResolvers.reducers) + }), + MockI18nModule, + RouterTestingModule + ], + declarations: [ + VersionComponent, + TestHostComponent + ], + }).compileComponents(); + + fixture = TestBed.createComponent(TestHostComponent); + instance = fixture.componentInstance; + app = instance.componentUnderTest; + fixture.detectChanges(); + })); + + it('should compile', () => { + expect(app).toBeTruthy(); + }); +}); diff --git a/ui/src/app/metadata/configuration/reducer/restore.reducer.spec.ts b/ui/src/app/metadata/configuration/reducer/restore.reducer.spec.ts index 9aa588b98..97795549b 100644 --- a/ui/src/app/metadata/configuration/reducer/restore.reducer.spec.ts +++ b/ui/src/app/metadata/configuration/reducer/restore.reducer.spec.ts @@ -1,6 +1,13 @@ import { reducer } from './restore.reducer'; import * as fromRestore from './restore.reducer'; -import * as actions from '../action/restore.action'; +import { + RestoreActionTypes, + RestoreActionsUnion, + UpdateRestorationChangesSuccess, + SetSavingStatus, + UpdateRestoreFormStatus +} from '../action/restore.action'; +import { Metadata } from '../../domain/domain.type'; describe('Restore Reducer', () => { @@ -14,20 +21,77 @@ describe('Restore Reducer', () => { }); }); - describe('SET_HISTORY action', () => { + describe(`${RestoreActionTypes.UPDATE_RESTORATION_SUCCESS} action`, () => { it('should set the state metadata model', () => { const serviceEnabled = true; - const action = new actions.UpdateRestorationChangesSuccess({ serviceEnabled }); + const action = new UpdateRestorationChangesSuccess({ serviceEnabled }); const result = reducer(fromRestore.initialState, action); - expect(Object.keys(result.changes)).toEqual({serviceEnabled: true}); + expect(result.changes).toEqual({serviceEnabled} as Metadata); + }); + }); + + describe(`${RestoreActionTypes.SET_SAVING_STATUS} action`, () => { + it('should set the state saving status', () => { + const action = new SetSavingStatus(true); + const result = reducer(fromRestore.initialState, action); + + expect(result.saving).toBe(true); + }); + }); + + describe(`${RestoreActionTypes.UPDATE_STATUS} action`, () => { + it('should set the state saving status', () => { + const action = new UpdateRestoreFormStatus({foo: 'INVALID'}); + const result = reducer(fromRestore.initialState, action); + + expect(result.status.foo).toBe('INVALID'); }); }); describe('selector function', () => { describe('getChanges', () => { it('should return the selected version id', () => { - expect(fromRestore.getChanges({ ...baseState, serviceEnabled: false })).toEqual({ serviceEnabled: false }); + expect(fromRestore.getChanges({ ...baseState, changes: { serviceEnabled: false } })).toEqual({ serviceEnabled: false }); + }); + }); + + describe('isRestorationSaved', () => { + it('should return false if there are outstanding changes', () => { + expect(fromRestore.isRestorationSaved({ ...baseState, changes: { name: 'too' } })).toBe(false); + }); + + it('should return true if there are no outstanding changes', () => { + expect(fromRestore.isRestorationSaved({ ...baseState, changes: {} })).toBe(true); + }); + }); + + describe('getFormStatus', () => { + it('should return the current form status', () => { + expect(fromRestore.getFormStatus({ ...baseState, status: { common: 'INVALID' } })).toEqual({ common: 'INVALID' }); + }); + }); + + describe('isRestorationSaving', () => { + it('should return the saving status', () => { + expect(fromRestore.isRestorationSaving({ ...baseState })).toBe(false); + expect(fromRestore.isRestorationSaving({ ...baseState, saving: true })).toBe(true); + }); + }); + + describe('isRestorationValid', () => { + it('should return false if any forms have an invalid status', () => { + expect(fromRestore.isRestorationValid({ ...baseState, status: { common: 'INVALID' } })).toBe(false); + }); + + it('should return true if all forms have a valid status', () => { + expect(fromRestore.isRestorationValid({ ...baseState, status: { common: 'VALID' } })).toBe(true); + }); + }); + + describe('getInvalidRestorationForms', () => { + it('should return the form names that are invalid', () => { + expect(fromRestore.getInvalidRestorationForms({ ...baseState, status: { common: 'INVALID' } })).toEqual(['common']); }); }); }); diff --git a/ui/src/app/metadata/configuration/reducer/restore.reducer.ts b/ui/src/app/metadata/configuration/reducer/restore.reducer.ts index f2e000728..e0c58c2b7 100644 --- a/ui/src/app/metadata/configuration/reducer/restore.reducer.ts +++ b/ui/src/app/metadata/configuration/reducer/restore.reducer.ts @@ -4,7 +4,7 @@ import { RestoreActionTypes, RestoreActionsUnion } from '../action/restore.actio export interface RestoreState { saving: boolean; status: { [key: string]: string }; - changes: Metadata; + changes: Partial; } export const initialState: RestoreState = { diff --git a/ui/src/app/metadata/configuration/reducer/version.reducer.spec.ts b/ui/src/app/metadata/configuration/reducer/version.reducer.spec.ts index e69de29bb..a564df384 100644 --- a/ui/src/app/metadata/configuration/reducer/version.reducer.spec.ts +++ b/ui/src/app/metadata/configuration/reducer/version.reducer.spec.ts @@ -0,0 +1,108 @@ +import { + reducer, + getVersionModel, + getVersionModelLoaded, + getSelectedMetadataId, + getSelectedVersionId, + getSelectedVersionType +} from './version.reducer'; +import * as fromVersion from './version.reducer'; +import { + VersionActionTypes, + SelectVersionRequest, + ClearVersion, + SelectVersionSuccess +} from '../action/version.action'; +import { Metadata } from '../../domain/domain.type'; +import { VersionRequest } from '../model/request'; + +describe('Restore Reducer', () => { + + let baseState; + + const req: VersionRequest = { + type: 'provider', + version: 'foo', + id: 'bar' + }; + + const model: Metadata = { + id: 'bar', + name: 'foo', + '@type': 'MetadataProvider', + type: 'provider', + resourceId: 'foo', + createdBy: 'bar' + }; + + beforeEach(() => { + baseState = { ...fromVersion.initialState }; + }); + + describe('undefined action', () => { + it('should return the default state', () => { + const result = reducer(undefined, {} as any); + + expect(result).toEqual(baseState); + }); + }); + + describe(`${VersionActionTypes.SELECT_VERSION_REQUEST} action`, () => { + it('should set the needed metadata properties', () => { + const action = new SelectVersionRequest(req); + const result = reducer(baseState, action); + + expect(result.selectedMetadataId).toEqual(req.id); + }); + }); + + describe(`${VersionActionTypes.SELECT_VERSION_SUCCESS} action`, () => { + it('should set the needed metadata properties', () => { + const action = new SelectVersionSuccess(model as Metadata); + const result = reducer(baseState, action); + + expect(result).toEqual({ ...baseState, model, loaded: true }); + }); + }); + + describe(`${VersionActionTypes.CLEAR_VERSION} action`, () => { + it('should set the needed metadata properties', () => { + const action = new ClearVersion(); + const result = reducer(baseState, action); + + expect(result).toEqual(baseState); + }); + }); + + describe('selector function', () => { + describe('getSelectedMetadataId', () => { + it('should return the selected version id', () => { + expect(getVersionModel({ ...baseState, model })).toEqual(model); + }); + }); + + describe('getSelectedMetadataVersion', () => { + it('should return the selected version id', () => { + expect(getVersionModelLoaded({ ...baseState, loaded: true })).toBe(true); + }); + }); + + describe('getSelectedMetadataId', () => { + it('should return the selected resource id', () => { + expect(getSelectedMetadataId({ ...baseState, selectedMetadataId: req.id})).toEqual(req.id); + }); + }); + + describe('getSelectedMetadataType', () => { + it('should return the selected version type', () => { + expect(getSelectedVersionType({ ...baseState, selectedVersionType: req.type })).toEqual(req.type); + }); + }); + + describe('getSelectedMetadataType', () => { + it('should return the selected version id', () => { + expect(getSelectedVersionId({ ...baseState, selectedVersionId: req.version })).toEqual(req.version); + }); + }); + }); +}); From 8cb1d49279e1328220bf190790b9d0fc85b5d0c1 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Fri, 30 Aug 2019 13:36:35 -0700 Subject: [PATCH 23/27] SHIBUI-1385 Added loading indicator --- .../restore-edit-step.component.spec.ts | 4 +- .../container/restore-edit-step.component.ts | 6 +-- .../container/restore.component.html | 42 +++++++++++-------- .../container/restore.component.spec.ts | 2 +- .../container/restore.component.ts | 2 + .../metadata/configuration/reducer/index.ts | 3 ++ .../configuration/reducer/version.reducer.ts | 18 ++++++-- .../widget/datalist/datalist.component.html | 2 +- .../autocomplete/autocomplete.component.html | 7 +++- 9 files changed, 57 insertions(+), 29 deletions(-) diff --git a/ui/src/app/metadata/configuration/container/restore-edit-step.component.spec.ts b/ui/src/app/metadata/configuration/container/restore-edit-step.component.spec.ts index 7d468b5af..d42f0be51 100644 --- a/ui/src/app/metadata/configuration/container/restore-edit-step.component.spec.ts +++ b/ui/src/app/metadata/configuration/container/restore-edit-step.component.spec.ts @@ -79,13 +79,13 @@ describe('Restore Version Edit Step Component', () => { describe('updateStatus', () => { it('should dispatch an update form status event', () => { - app.updateStatus([{ value: 'foo' }, 'common']); + app.updateStatus({ value: 'foo' }, 'common'); expect(store.dispatch).toHaveBeenCalled(); expect(dispatchSpy.calls.mostRecent().args[0].type).toBe(RestoreActionTypes.UPDATE_STATUS); }); it('should dispatch an update form status event', () => { - app.updateStatus([{}, 'common']); + app.updateStatus({}, 'common'); expect(store.dispatch).toHaveBeenCalled(); expect(dispatchSpy.calls.mostRecent().args[0].type).toBe(RestoreActionTypes.UPDATE_STATUS); }); diff --git a/ui/src/app/metadata/configuration/container/restore-edit-step.component.ts b/ui/src/app/metadata/configuration/container/restore-edit-step.component.ts index 904cf43f6..2a9a73a0e 100644 --- a/ui/src/app/metadata/configuration/container/restore-edit-step.component.ts +++ b/ui/src/app/metadata/configuration/container/restore-edit-step.component.ts @@ -54,21 +54,21 @@ export class RestoreEditStepComponent implements OnDestroy { this.lockChange$ .pipe(takeUntil(this.ngUnsubscribe)) - .subscribe(this.updateLock); + .subscribe(locked => this.updateLock(locked)); this.statusChange$ .pipe( takeUntil(this.ngUnsubscribe), withLatestFrom(this.store.select(getWizardIndex)) ) - .subscribe(this.updateStatus); + .subscribe(([errors, currentPage]) => this.updateStatus(errors, currentPage)); } onChange(changes: any): void { this.store.dispatch(new UpdateRestorationChangesRequest(changes)); } - updateStatus([errors, currentPage]) { + updateStatus(errors, currentPage) { const status = { [currentPage]: !(errors.value) ? 'VALID' : 'INVALID' }; this.store.dispatch(new UpdateRestoreFormStatus(status)); } diff --git a/ui/src/app/metadata/configuration/container/restore.component.html b/ui/src/app/metadata/configuration/container/restore.component.html index 855426142..3072dbfc3 100644 --- a/ui/src/app/metadata/configuration/container/restore.component.html +++ b/ui/src/app/metadata/configuration/container/restore.component.html @@ -1,20 +1,26 @@ -

- - Restore Version ( date ) - -

-
-
-
-

- Create New Version from Previous Settings -

-

- Restoring this version will copy the Version ( date ) configuration and create a new Version from the selected version settings. You can then edit the configuration before saving the new version. -

-   - + +

+ + Restore Version ( date ) + +

+
+
+
+

+ Create New Version from Previous Settings +

+

+ Restoring this version will copy the Version ( date ) configuration and create a new Version from the selected version settings. You can then edit the configuration before saving the new version. +

+   + +
-
+ +
+ + Loading... +
\ No newline at end of file diff --git a/ui/src/app/metadata/configuration/container/restore.component.spec.ts b/ui/src/app/metadata/configuration/container/restore.component.spec.ts index ef3b0f3fa..36420e2f6 100644 --- a/ui/src/app/metadata/configuration/container/restore.component.spec.ts +++ b/ui/src/app/metadata/configuration/container/restore.component.spec.ts @@ -65,7 +65,7 @@ describe('Metadata Restore Page Component', () => { it('should load metadata objects', async(() => { expect(app).toBeTruthy(); - expect(store.select).toHaveBeenCalledTimes(1); + expect(store.select).toHaveBeenCalledTimes(2); expect(store.dispatch).not.toHaveBeenCalled(); })); diff --git a/ui/src/app/metadata/configuration/container/restore.component.ts b/ui/src/app/metadata/configuration/container/restore.component.ts index 6fc4181c1..bb8ffc2cf 100644 --- a/ui/src/app/metadata/configuration/container/restore.component.ts +++ b/ui/src/app/metadata/configuration/container/restore.component.ts @@ -19,6 +19,8 @@ import { Router, ActivatedRoute } from '@angular/router'; export class RestoreComponent { dateString$ = this.store.select(fromConfiguration.getConfigurationVersionDate); + loading$ = this.store.select(fromConfiguration.getVersionLoading); + loaded$ = this.loading$.pipe(map(loading => !loading)); date$: Observable; constructor( diff --git a/ui/src/app/metadata/configuration/reducer/index.ts b/ui/src/app/metadata/configuration/reducer/index.ts index b64b6e070..a9485de94 100644 --- a/ui/src/app/metadata/configuration/reducer/index.ts +++ b/ui/src/app/metadata/configuration/reducer/index.ts @@ -167,6 +167,9 @@ export const getVersionModelFiltersFn = null; export const getVersionState = createSelector(getState, getVersionStateFn); + +export const getVersionLoading = createSelector(getVersionState, fromVersion.isVersionLoading); + export const getVersionModel = createSelector(getVersionState, fromVersion.getVersionModel); export const getVersionModels = createSelector(getVersionModel, (model) => model ? [model] : null); export const getVersionConfigurationSections = createSelector( diff --git a/ui/src/app/metadata/configuration/reducer/version.reducer.ts b/ui/src/app/metadata/configuration/reducer/version.reducer.ts index 9d2dd9986..ae14c33a4 100644 --- a/ui/src/app/metadata/configuration/reducer/version.reducer.ts +++ b/ui/src/app/metadata/configuration/reducer/version.reducer.ts @@ -7,6 +7,7 @@ export interface State { selectedVersionType: string; selectedMetadataId: string; loaded: Boolean; + loading: Boolean; } export const initialState: State = { @@ -14,7 +15,8 @@ export const initialState: State = { selectedVersionId: null, selectedMetadataId: null, selectedVersionType: null, - loaded: false + loaded: false, + loading: false }; export function reducer(state = initialState, action: VersionActionsUnion): State { @@ -24,13 +26,22 @@ export function reducer(state = initialState, action: VersionActionsUnion): Stat ...state, selectedMetadataId: action.payload.id, selectedVersionId: action.payload.version, - selectedVersionType: action.payload.type + selectedVersionType: action.payload.type, + loading: true }; case VersionActionTypes.SELECT_VERSION_SUCCESS: return { ...state, model: action.payload, - loaded: true + loaded: true, + loading: false + }; + case VersionActionTypes.SELECT_VERSION_ERROR: + return { + ...state, + model: null, + loaded: false, + loading: false }; case VersionActionTypes.CLEAR_VERSION: return { @@ -44,6 +55,7 @@ export function reducer(state = initialState, action: VersionActionsUnion): Stat export const getVersionModel = (state: State) => state.model; export const getVersionModelLoaded = (state: State) => state.loaded; +export const isVersionLoading = (state: State) => state.loading; export const getSelectedMetadataId = (state: State) => state.selectedMetadataId; export const getSelectedVersionId = (state: State) => state.selectedVersionId; diff --git a/ui/src/app/schema-form/widget/datalist/datalist.component.html b/ui/src/app/schema-form/widget/datalist/datalist.component.html index a4f377035..58cf01c46 100644 --- a/ui/src/app/schema-form/widget/datalist/datalist.component.html +++ b/ui/src/app/schema-form/widget/datalist/datalist.component.html @@ -1,5 +1,5 @@
-