From 3d07ae8ca61cb76f268e78a12ce7e08fb6bcfe4b Mon Sep 17 00:00:00 2001 From: Artjoms Porss Date: Mon, 28 Oct 2024 15:50:51 +0000 Subject: [PATCH] improve UX of dropdown inputs (#2774) Signed-off-by: aporss --- .../denali/__snapshots__/Alert.test.js.snap | 12 + .../denali/__snapshots__/Input.test.js.snap | 3 + .../__snapshots__/InputDropdown.test.js.snap | 5 + .../denali/__snapshots__/Modal.test.js.snap | 1 + .../__snapshots__/SearchInput.test.js.snap | 1 + .../denali/__snapshots__/Tag.test.js.snap | 2 + .../icons/__snapshots__/Icon.test.js.snap | 1 + .../__snapshots__/ManageDomains.test.js.snap | 9 + .../__snapshots__/UserDomain.test.js.snap | 5 + .../components/group/AddGroup.test.js | 6 - .../group/__snapshots__/GroupRow.test.js.snap | 7 + .../__snapshots__/GroupTable.test.js.snap | 14 + .../__snapshots__/DomainDetails.test.js.snap | 2 + .../DomainNameHeader.test.js.snap | 2 + .../header/__snapshots__/Header.test.js.snap | 5 + .../__snapshots__/HeaderMenu.test.js.snap | 4 + .../__snapshots__/RoleNameHeader.test.js.snap | 2 + .../__snapshots__/HistoryList.test.js.snap | 1 + .../__snapshots__/MemberList.test.js.snap | 35 +++ .../__snapshots__/MemberRow.test.js.snap | 4 + .../__snapshots__/MemberTable.test.js.snap | 17 ++ .../AddSegmentation.test.js.snap | 3 + .../AddStaticInstances.test.js.snap | 2 + .../__snapshots__/RuleRow.test.js.snap | 4 + .../__snapshots__/RuleTable.test.js.snap | 5 + .../__snapshots__/RulesList.test.js.snap | 10 + .../PendingApprovalTable.test.js.snap | 4 + .../PendingApprovalTableHeader.test.js.snap | 4 + .../PendingApprovalTableRow.test.js.snap | 4 + .../components/policy/AddAssertion.test.js | 2 +- .../components/policy/AddPolicy.test.js | 2 +- .../__snapshots__/AddAssertion.test.js.snap | 3 + .../__snapshots__/AddPolicy.test.js.snap | 4 + .../__snapshots__/AddRuleForm.test.js.snap | 2 + .../__snapshots__/PolicyRow.test.js.snap | 4 + .../PolicyRuleTable.test.js.snap | 2 + .../__snapshots__/RolePolicyRow.test.js.snap | 1 + .../role/__snapshots__/AddRole.test.js.snap | 6 + .../role/__snapshots__/RoleGroup.test.js.snap | 1 + .../role/__snapshots__/RoleList.test.js.snap | 1 + .../__snapshots__/RoleMember.test.js.snap | 1 + .../role/__snapshots__/RoleRow.test.js.snap | 8 + .../__snapshots__/RoleSectionRow.test.js.snap | 7 + .../role/__snapshots__/RoleTable.test.js.snap | 16 ++ .../__snapshots__/UserRoleTable.test.js.snap | 4 + .../__snapshots__/InstanceList.test.js.snap | 8 + .../__snapshots__/InstanceRow.test.js.snap | 1 + .../__snapshots__/InstanceTable.test.js.snap | 1 + .../__snapshots__/ProviderTable.test.js.snap | 1 + .../__snapshots__/ServiceList.test.js.snap | 25 ++ .../__snapshots__/ServiceRow.test.js.snap | 5 + .../__snapshots__/SettingTable.test.js.snap | 2 + .../tag/__snapshots__/AddTagForm.test.js.snap | 2 + .../tag/__snapshots__/TagList.test.js.snap | 31 +++ .../tag/__snapshots__/TagRow.test.js.snap | 8 + .../__snapshots__/TemplateList.test.js.snap | 1 + .../__snapshots__/TemplateRow.test.js.snap | 4 + .../__snapshots__/AuthHistory.test.js.snap | 2 + .../ServiceDependency.test.js.snap | 1 + .../__snapshots__/VisibilityList.test.js.snap | 2 + .../pages/__snapshots__/index.test.js.snap | 12 + .../domain-settings.test.js.snap | 8 + .../[domain]/__snapshots__/group.test.js.snap | 8 + .../microsegmentation.test.js.snap | 8 + .../__snapshots__/policy.test.js.snap | 8 + .../[domain]/__snapshots__/role.test.js.snap | 9 + .../__snapshots__/service.test.js.snap | 18 ++ .../[domain]/__snapshots__/tags.test.js.snap | 8 + .../__snapshots__/template.test.js.snap | 8 + .../__snapshots__/visibility.test.js.snap | 8 + .../__snapshots__/members.test.js.snap | 7 + .../[group]/__snapshots__/review.test.js.snap | 6 + .../[group]/__snapshots__/roles.test.js.snap | 7 + .../__snapshots__/settings.test.js.snap | 8 + .../[group]/__snapshots__/tags.test.js.snap | 10 + .../[policy]/__snapshots__/tags.test.js.snap | 13 + .../[role]/__snapshots__/members.test.js.snap | 7 + .../[role]/__snapshots__/policy.test.js.snap | 6 + .../[role]/__snapshots__/review.test.js.snap | 6 + .../__snapshots__/settings.test.js.snap | 8 + .../[role]/__snapshots__/tags.test.js.snap | 9 + .../microsegmentation.test.js.snap | 20 ++ .../[service]/__snapshots__/tags.test.js.snap | 6 + .../__snapshots__/dynamic.test.js.snap | 12 + .../__snapshots__/static.test.js.snap | 10 + .../domain/__snapshots__/create.test.js.snap | 5 + .../domain/__snapshots__/manage.test.js.snap | 12 + .../__snapshots__/search.test.js.snap | 7 + .../workflow/__snapshots__/admin.test.js.snap | 16 ++ .../__snapshots__/domain.test.js.snap | 17 ++ .../workflow/__snapshots__/group.test.js.snap | 4 + .../workflow/__snapshots__/role.test.js.snap | 4 + ui/src/__tests__/spec/tests/domain.spec.js | 252 +++++++++++++++++ ui/src/__tests__/spec/tests/groups.spec.js | 45 ++++ ui/src/__tests__/spec/tests/policies.spec.js | 147 ++++++++++ ui/src/__tests__/spec/tests/role.spec.js | 140 ---------- ui/src/__tests__/spec/tests/roles.spec.js | 254 ++++++++++++++++++ ui/src/components/denali/InputDropdown.js | 18 +- ui/src/components/denali/icons/Icon.js | 2 + ui/src/components/domain/ManageDomains.js | 27 +- ui/src/components/group/AddGroup.js | 20 ++ ui/src/components/header/DomainDetails.js | 33 ++- ui/src/components/history/HistoryList.js | 1 + ui/src/components/member/AddPoc.js | 17 ++ ui/src/components/member/MemberRow.js | 2 + .../components/modal/BusinessServiceModal.js | 17 ++ ui/src/components/policy/AddAssertion.js | 2 +- ui/src/components/policy/AddPolicy.js | 2 +- ui/src/components/policy/AddRuleForm.js | 18 +- ui/src/components/policy/PolicyRow.js | 2 + .../role-policy/AddRuleFormForRole.js | 5 - ui/src/components/role/AddMemberToRoles.js | 23 +- ui/src/components/role/AddRole.js | 20 ++ ui/src/components/role/RoleRow.js | 2 + ui/src/pages/workflow/domain.js | 1 + 115 files changed, 1508 insertions(+), 169 deletions(-) create mode 100644 ui/src/__tests__/spec/tests/policies.spec.js delete mode 100644 ui/src/__tests__/spec/tests/role.spec.js diff --git a/ui/src/__tests__/components/denali/__snapshots__/Alert.test.js.snap b/ui/src/__tests__/components/denali/__snapshots__/Alert.test.js.snap index 19138013318..93f513ebaa1 100644 --- a/ui/src/__tests__/components/denali/__snapshots__/Alert.test.js.snap +++ b/ui/src/__tests__/components/denali/__snapshots__/Alert.test.js.snap @@ -175,6 +175,7 @@ exports[`Alert should disable animations 1`] = `
add @@ -962,6 +965,7 @@ exports[`PageManageDomains should render 1`] = ` > add @@ -1034,6 +1038,7 @@ exports[`PageManageDomains should render 1`] = ` > yca.US @@ -1045,6 +1050,7 @@ exports[`PageManageDomains should render 1`] = ` add @@ -1195,6 +1202,7 @@ exports[`PageManageDomains should render 1`] = ` > add @@ -1206,6 +1214,7 @@ exports[`PageManageDomains should render 1`] = ` { await submitButton.click(); await expect(securityPocAnchor).toHaveTextContaining('Chandu Raman'); }); + + it('modal to add business service - should preserve input on blur, make input bold when selected in dropdown, reject unselected input, allow submission of empty input', async () => { + await browser.newUser(); + await browser.url(`/domain/athenz.dev.functional-test/role`); + await expect(browser).toHaveUrlContaining('athenz'); + + // expand domain details + let expand = await $(`.//*[local-name()="svg" and @data-wdio="domain-details-expand-icon"]`); + await browser.waitUntil(async () => await expand.isClickable()); + await expand.click(); + + // click add business service + let addBusinessService = await $('a[data-testid="add-business-service"]'); + await browser.waitUntil(async () => await addBusinessService.isClickable()); + await addBusinessService.click(); + + await browser.pause(2000); // wait to make sure dropdown options are loaded + + // add random text to modal input + let bsInput = await $('input[name="business-service-drop"]'); + await bsInput.addValue('nonexistent.service'); + + // blur + await browser.keys('Tab'); + + // input did not change + expect(await bsInput.getValue()).toBe('nonexistent.service'); + + // input is not bold + let fontWeight = await bsInput.getCSSProperty('font-weight').value; + expect(fontWeight).toBeUndefined(); + + // submit (item in dropdown is not selected) + let submitButton = await $('button*=Submit'); + await submitButton.click(); + + // verify error message + let errorMessage = await $('div[data-testid="error-message"]'); + expect(await errorMessage.getText()).toBe('Business Service must be selected in the dropdown or clear input before submitting'); + + // unclick checkbox to allow selection of business services not associated with current account + let checkbox = await $('input[id="checkbox-show-all-bservices"]'); + await browser.execute(function(checkboxElem) { + checkboxElem.click(); + }, checkbox); + + + // type valid input and select item in dropdown + let clearInput = await $(`.//*[local-name()="svg" and @data-wdio="clear-input"]`); + await clearInput.click(); + // make dropdown visible + await bsInput.click(); + await bsInput.addValue('PolicyEnforcementService.GLB'); + let dropdownOption = await $('//div[contains(text(), "PolicyEnforcementService.GLB")]'); + await dropdownOption.click(); + + // verify input contains pes service + expect(await bsInput.getValue()).toBe('PolicyEnforcementService.GLB'); + + // verify input is in bold + fontWeight = await bsInput.getCSSProperty('font-weight'); + expect(fontWeight.value === 700).toBe(true); + + // submit + submitButton = await $('button*=Submit'); + await submitButton.click(); + + // business service can be seen added to domain + addBusinessService = await $('a[data-testid="add-business-service"]'); + await expect(addBusinessService).toHaveTextContaining('PolicyEnforcementService.GLB'); + + // click add business service + await browser.waitUntil(async () => await addBusinessService.isClickable()); + await addBusinessService.click(); + + // clear current input + clearInput = await $(`.//*[local-name()="svg" and @data-wdio="clear-input"]`); + await browser.waitUntil(async () => await clearInput.isClickable()); + await clearInput.click(); + + // submit empty input + submitButton = await $('button*=Submit'); + await submitButton.click(); + + // business service for the domain should be empty + await browser.waitUntil(async () => await addBusinessService.isClickable()); + expect(addBusinessService).toHaveTextContaining('add'); + }); + + it('Manage Domains - modal to change add business service - should preserve input on blur, make input bold when selected in dropdown, reject unselected input', async () => { + await browser.newUser(); + + // open athenz manage domains page + await browser.url(`/domain/manage`); + await expect(browser).toHaveUrlContaining('athenz'); + + // click add business service + let addBusinessService = await $('a[data-testid="business-service-athenz.dev.functional-test"]'); + await browser.waitUntil(async () => await addBusinessService.isClickable()); + await addBusinessService.click(); + + await browser.pause(4000); // wait to make sure dropdown options are loaded + + // add random text + let bsInput = await $('input[name="business-service-drop"]'); + await bsInput.addValue('nonexistent.service'); + + // blur + await browser.keys('Tab'); + + // input did not change + expect(await bsInput.getValue()).toBe('nonexistent.service'); + + // input is not bold + let fontWeight = await bsInput.getCSSProperty('font-weight').value; + expect(fontWeight).toBeUndefined(); + + // submit (item in dropdown is not selected) + let submitButton = await $('button*=Submit'); + await submitButton.click(); + + // verify error message + let errorMessage = await $('div[data-testid="error-message"]'); + expect(await errorMessage.getText()).toBe('Business Service must be selected in the dropdown'); + + let clearInput = await $(`.//*[local-name()="svg" and @data-wdio="clear-input"]`); + await clearInput.click(); + + let checkbox = await $('input[id="checkbox-show-all-bservices"]'); + await browser.execute(function(checkboxElem) { + checkboxElem.click(); + }, checkbox); + + + // make dropdown visible + await bsInput.click(); + // type valid input and select item in dropdown + await bsInput.addValue('PolicyEnforcementService.GLB'); + let dropdownOption = await $('div*=PolicyEnforcementService.GLB'); + await dropdownOption.click(); + + // verify input contains pes service + expect(await bsInput.getValue()).toBe('PolicyEnforcementService.GLB'); + + // verify input is in bold + fontWeight = await bsInput.getCSSProperty('font-weight'); + expect(fontWeight.value === 700).toBe(true); + + // submit + submitButton = await $('button*=Submit'); + await submitButton.click(); + + // business service can be seen added to domain + addBusinessService = await $('a[data-testid="business-service-athenz.dev.functional-test"]'); + await expect(addBusinessService).toHaveTextContaining('PolicyEnforcementService.GLB'); + + // click add business service + await browser.waitUntil(async () => await addBusinessService.isClickable()); + await addBusinessService.click(); + + // clear current input + clearInput = await $(`.//*[local-name()="svg" and @data-wdio="clear-input"]`); + await browser.waitUntil(async () => await clearInput.isClickable()); + await clearInput.click(); + + // submit empty input + submitButton = await $('button*=Submit'); + await submitButton.click(); + + // business service for the domain should be empty + await browser.waitUntil(async () => await addBusinessService.isClickable()); + expect(addBusinessService).toHaveTextContaining('add'); + }); + + it('Domain History - modal to change add business service - should preserve input on blur, make input bold when selected in dropdown', async () => { + await browser.newUser(); + + // open domain history page + await browser.url(`/domain/athenz.dev.functional-test/history`); + await expect(browser).toHaveUrlContaining('athenz'); + + const nonexistentRole = 'nonexistent.role'; + + // add random text + let input = await $('input[name="roles"]'); + await input.addValue(nonexistentRole); + + // blur + await browser.keys('Tab'); + + // input did not change + expect(await input.getValue()).toBe(nonexistentRole); + + // input is not bold + let fontWeight = await input.getCSSProperty('font-weight').value; + expect(fontWeight).toBeUndefined(); + + let clearInput = await $(`.//*[local-name()="svg" and @data-wdio="clear-input"]`); + await clearInput.click(); + + // type valid input and select item in dropdown + await input.addValue('admin'); + let dropdownOption = await $('div*=admin'); + await dropdownOption.click(); + + // verify input contains pes service + expect(await input.getValue()).toBe('admin'); + + // verify input is in bold + fontWeight = await input.getCSSProperty('font-weight'); + expect(fontWeight.value === 700).toBe(true); + }); + + it('Domain Workflow - input to select dommain - should preserve input on blur, make input bold when selected in dropdown', async () => { + await browser.newUser(); + + // open domain history page + await browser.url(`/workflow/domain?domain=`); + await expect(browser).toHaveUrlContaining('athenz'); + + const nonexistentDomain = 'nonexistent.domain'; + + // add random text + let input = await $('input[name="domains-inputd"]'); + await input.addValue(nonexistentDomain); + + // blur + await browser.keys('Tab'); + + // input did not change + expect(await input.getValue()).toBe(nonexistentDomain); + + // input is not bold + let fontWeight = await input.getCSSProperty('font-weight').value; + expect(fontWeight).toBeUndefined(); + + let clearInput = await $(`.//*[local-name()="svg" and @data-wdio="clear-input"]`); + await clearInput.click(); + + // type valid input and select item in dropdown + const testDomain = 'athenz.dev.functional-test'; + await input.addValue(testDomain); + let dropdownOption = await $(`div*=${testDomain}`); + await dropdownOption.click(); + + // verify input contains pes service + expect(await input.getValue()).toBe(testDomain); + + // verify input is in bold + fontWeight = await input.getCSSProperty('font-weight'); + expect(fontWeight.value === 700).toBe(true); + }); }); diff --git a/ui/src/__tests__/spec/tests/groups.spec.js b/ui/src/__tests__/spec/tests/groups.spec.js index cb22312c4ac..0f70ddb54d4 100644 --- a/ui/src/__tests__/spec/tests/groups.spec.js +++ b/ui/src/__tests__/spec/tests/groups.spec.js @@ -15,6 +15,7 @@ */ + describe('group screen tests', () => { it('group history should be visible when navigating to it and after page refresh', async () => { // open browser @@ -87,4 +88,48 @@ describe('group screen tests', () => { let modalDeleteButton = await $('button*=Delete'); await modalDeleteButton.click(); }); + + it('dropdown input for adding user during group creation - should preserve input on blur, make input bold when selected in dropdown, reject unselected input', async () => { + // open browser + await browser.newUser(); + await browser.url(`/domain/athenz.dev.functional-test/group`); + + // open Add Group modal + let addGroupButton = await $('button*=Add Group'); + await addGroupButton.click(); + // add group info + let inputGroupName = await $('#group-name-input'); + let groupName = 'input-dropdown-test-group'; + await inputGroupName.addValue(groupName); + // add user + let addMemberInput = await $('[name="member-name"]'); + // add invalid item + await addMemberInput.addValue('invalidusername'); + // blur + await browser.keys('Tab'); + // input did not change + expect(await addMemberInput.getValue()).toBe('invalidusername'); + // input is not bold + let fontWeight = await addMemberInput.getCSSProperty('font-weight').value; + expect(fontWeight).toBeUndefined(); + // submit (item in dropdown is not selected) + let submitButton = await $('button*=Submit'); + await submitButton.click(); + // verify error message + let errorMessage = await $('div[data-testid="error-message"]'); + expect(await errorMessage.getText()).toBe('Member must be selected in the dropdown or member input field must be empty.'); + // clear input + let clearInput = await $(`.//*[local-name()="svg" and @data-wdio="clear-input"]`); + await clearInput.click(); + // add valid input + await addMemberInput.addValue('unix.yahoo'); + // click dropdown + let userOption = await $('div*=unix.yahoo'); + await userOption.click(); + // verify input contains pes service + expect(await addMemberInput.getValue()).toBe('unix.yahoo'); + // verify input is in bold + fontWeight = await addMemberInput.getCSSProperty('font-weight'); + expect(fontWeight.value === 700).toBe(true); + }); }) diff --git a/ui/src/__tests__/spec/tests/policies.spec.js b/ui/src/__tests__/spec/tests/policies.spec.js new file mode 100644 index 00000000000..5b0fbfd3e50 --- /dev/null +++ b/ui/src/__tests__/spec/tests/policies.spec.js @@ -0,0 +1,147 @@ +/* + * Copyright The Athenz Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +const dropdownTestPolicyName = 'policy-dropdown-test'; + +describe('Policies Screen', () => { + it('add policy to new role and existing role - should preserve input on blur, make input bold when selected in dropdown, reject unselected input', async () => { + await browser.newUser(); + await browser.url(`/domain/athenz.dev.functional-test/policy`); + await expect(browser).toHaveUrlContaining('athenz'); + + // click add policy + let addPolicyBtn = await $('button*=Add Policy'); + await browser.waitUntil(async () => await addPolicyBtn.isClickable()); + await addPolicyBtn.click(); + + await $('input[id="policy-name"]').addValue(dropdownTestPolicyName); + await $('input[id="rule-action"]').addValue('rule-action'); + await $('input[id="rule-resource"]').addValue('rule-resource'); + + const invalidRole = 'admi'; + // add random text to modal input + let roleInput = await $('input[name="rule-role"]'); + await roleInput.addValue(invalidRole); + + // blur + await browser.keys('Tab'); + + // input did not change + expect(await roleInput.getValue()).toBe(invalidRole); + + // input is not bold + let fontWeight = await roleInput.getCSSProperty('font-weight').value; + expect(fontWeight).toBeUndefined(); + + // submit (item in dropdown is not selected) + let submitButton = await $('button*=Submit'); + await submitButton.click(); + + // verify error message + let errorMessage = await $('div[data-testid="error-message"]'); + expect(await errorMessage.getText()).toBe('Role must be selected in the dropdown.'); + + // type valid input and select item in dropdown + let clearInput = await $(`.//*[local-name()="svg" and @data-wdio="clear-input"]`); + await clearInput.click(); + const validRole = 'admin'; + await roleInput.addValue(validRole); + let dropdownOption = await $(`div*=${validRole}`); + await dropdownOption.click(); + + // verify input contains selected role + expect(await roleInput.getValue()).toBe(validRole); + + // verify input is in bold + fontWeight = await roleInput.getCSSProperty('font-weight'); + expect(fontWeight.value === 700).toBe(true); + + // submit + submitButton = await $('button*=Submit'); + await submitButton.click(); + + // policy can be seen added + let policyRow = await $(`td*=${dropdownTestPolicyName}`); + await expect(policyRow).toHaveTextContaining(dropdownTestPolicyName); + + + // TEST ADD RULE TO EXISTING POLICY + + // show rules for the policy we created + await $(`.//*[local-name()="svg" and @data-wdio="${dropdownTestPolicyName}-rules"]`).click(); + // open add rule window + await $('a*=Add rule').click(); + // fill the form + const resource = 'dropdown-test-resource'; + await $('input[id="rule-action"]').addValue('rule-action'); + await $('input[id="rule-resource"]').addValue(resource); + + // test incomplete input in dropdown + roleInput = await $('input[name="rule-role"]'); + await roleInput.addValue(invalidRole); + + // blur + await browser.keys('Tab'); + + // input did not change + expect(await roleInput.getValue()).toBe(invalidRole); + + // input is not bold + fontWeight = await roleInput.getCSSProperty('font-weight').value; + expect(fontWeight).toBeUndefined(); + + // submit (item in dropdown is not selected) + submitButton = await $('button*=Submit'); + await submitButton.click(); + + // verify error message + errorMessage = await $('div[data-testid="error-message"]'); + expect(await errorMessage.getText()).toBe('Role must be selected in the dropdown.'); + + // type valid input and select item in dropdown + clearInput = await $(`.//*[local-name()="svg" and @data-wdio="clear-input"]`); + await clearInput.click(); + await roleInput.addValue(validRole); + + dropdownOption = await $(`.//div[@role='option' and contains(., '${validRole}')]`); + await dropdownOption.click(); + + // verify input contains selected role + expect(await roleInput.getValue()).toBe(validRole); + + // verify input is in bold + fontWeight = await roleInput.getCSSProperty('font-weight'); + expect(fontWeight.value === 700).toBe(true); + + // submit + await submitButton.click(); + + // verify new rule was added + let newRuleResource = await $(`td*=athenz.dev.functional-test:${resource}`); + expect(newRuleResource).toHaveText(`athenz.dev.functional-test:${resource}`); + }); + + after(async() => { + // delete policy created in previous test + await browser.newUser(); + await browser.url(`/domain/athenz.dev.functional-test/policy`); + await expect(browser).toHaveUrlContaining('athenz'); + + await $(`.//*[local-name()="svg" and @data-wdio="${dropdownTestPolicyName}-delete"]`).click(); + await $('button*=Delete').click(); + }); +}); diff --git a/ui/src/__tests__/spec/tests/role.spec.js b/ui/src/__tests__/spec/tests/role.spec.js deleted file mode 100644 index 27e959139a9..00000000000 --- a/ui/src/__tests__/spec/tests/role.spec.js +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright The Athenz Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -describe('role screen tests', () => { - it('when creating or editing a delegated role, all additional settings except description must be disabled', async () => { - // open browser - await browser.newUser(); - await browser.url(`/`); - // select domain - let domain = 'athenz.dev.functional-test'; - let testDomain = await $(`a*=${domain}`); - await browser.waitUntil(async () => await testDomain.isClickable()); - await testDomain.click(); - - // open Add Role screen - let addRoleButton = await $('button*=Add Role'); - await browser.waitUntil(async () => await addRoleButton.isClickable()); - await addRoleButton.click(); - // select Delegated - let delegatedButton = await $('div*=Delegated'); - await delegatedButton.click(); - // verify all settings except Description are disabled - let advancedSettingsIcon = await $('#advanced-settings-icon'); - await advancedSettingsIcon.click(); - let switchSettingAuditEnabled = await $('#switch-settingauditEnabled'); - await expect(switchSettingAuditEnabled).toBeDisabled(); - let switchSettingReviewEnabled = await $('#switch-settingreviewEnabled'); - await expect(switchSettingReviewEnabled).toBeDisabled(); - let switchSettingDeleteProtection = await $('#switch-settingdeleteProtection'); - await expect(switchSettingDeleteProtection).toBeDisabled(); - let switchSettingSelfServe = await $('#switch-settingselfServe'); - await expect(switchSettingSelfServe).toBeDisabled(); - let switchSettingSelfRenew = await $('#switch-settingselfRenew'); - await expect(switchSettingSelfRenew).toBeDisabled(); - let inputSelfRenewMins = await $('#setting-selfRenewMins'); - await expect(inputSelfRenewMins).toBeDisabled(); - let inputMemberExpiryDays = await $('#setting-memberExpiryDays'); - await expect(inputMemberExpiryDays).toBeDisabled(); - let inputGroupExpiryDays = await $('#setting-groupExpiryDays'); - await expect(inputGroupExpiryDays).toBeDisabled(); - let inputGroupReviewDays = await $('#setting-groupReviewDays'); - await expect(inputGroupReviewDays).toBeDisabled(); - let inputServiceExpiryDays = await $('#setting-serviceExpiryDays'); - await expect(inputServiceExpiryDays).toBeDisabled(); - let inputServiceReviewDays = await $('#setting-serviceReviewDays'); - await expect(inputServiceReviewDays).toBeDisabled(); - let inputTokenExpiryMins = await $('#setting-tokenExpiryMins'); - await expect(inputTokenExpiryMins).toBeDisabled(); - let inputCertExpiryMins = await $('#setting-certExpiryMins'); - await expect(inputCertExpiryMins).toBeDisabled(); - let dropdownUserAuthorityFilter = await $('[name="setting-userAuthorityFilter"]'); - await expect(dropdownUserAuthorityFilter).toBeDisabled(); - let dropdownUserAuthorityExpiration = await $('[name="setting-userAuthorityExpiration"]'); - await expect(dropdownUserAuthorityExpiration).toBeDisabled(); - let inputSettingDescription = await $('#setting-description'); - await expect(inputSettingDescription).toBeEnabled(); - let inputMaxMembers = await $('#setting-maxMembers'); - await expect(inputMaxMembers).toBeDisabled(); - - // add role info - let inputRoleName = await $('#role-name-input'); - let roleName = 'delegated-role'; - await inputRoleName.addValue(roleName); - let inputDelegateTo = await $('#delegated-to-input'); - await inputDelegateTo.addValue('athenz.dev'); - let buttonSubmit = await $('button*=Submit'); - // submit role - await buttonSubmit.click(); - - // find row with 'delegated-role' in name and click settings svg - let buttonSettingsOfDelegatedRole = await $('.//*[local-name()="svg" and @id="delegated-role-setting-role-button"]'); - await buttonSettingsOfDelegatedRole.click(); - - // verify all settings except Description are disabled - switchSettingReviewEnabled = await $('#switch-settingreviewEnabled'); - await expect(switchSettingReviewEnabled).toBeDisabled(); - switchSettingDeleteProtection = await $('#switch-settingdeleteProtection'); - await expect(switchSettingDeleteProtection).toBeDisabled(); - switchSettingSelfServe = await $('#switch-settingselfServe'); - await expect(switchSettingSelfServe).toBeDisabled(); - switchSettingSelfRenew = await $('#switch-settingselfRenew'); - await expect(switchSettingSelfRenew).toBeDisabled(); - inputSelfRenewMins = await $('#setting-selfRenewMins'); - await expect(inputSelfRenewMins).toBeDisabled(); - inputMemberExpiryDays = await $('#setting-memberExpiryDays'); - await expect(inputMemberExpiryDays).toBeDisabled(); - inputGroupExpiryDays = await $('#setting-groupExpiryDays'); - await expect(inputGroupExpiryDays).toBeDisabled(); - inputGroupReviewDays = await $('#setting-groupReviewDays'); - await expect(inputGroupReviewDays).toBeDisabled(); - inputServiceExpiryDays = await $('#setting-serviceExpiryDays'); - await expect(inputServiceExpiryDays).toBeDisabled(); - inputServiceReviewDays = await $('#setting-serviceReviewDays'); - await expect(inputServiceReviewDays).toBeDisabled(); - inputTokenExpiryMins = await $('#setting-tokenExpiryMins'); - await expect(inputTokenExpiryMins).toBeDisabled(); - inputCertExpiryMins = await $('#setting-certExpiryMins'); - await expect(inputCertExpiryMins).toBeDisabled(); - dropdownUserAuthorityFilter = await $('[name="setting-userAuthorityFilter"]'); - await expect(dropdownUserAuthorityFilter).toBeDisabled(); - dropdownUserAuthorityExpiration = await $('[name="setting-userAuthorityExpiration"]'); - await expect(dropdownUserAuthorityExpiration).toBeDisabled(); - inputSettingDescription = await $('#setting-description'); - await expect(inputSettingDescription).toBeEnabled(); - inputMaxMembers = await $('#setting-maxMembers'); - await expect(inputMaxMembers).toBeDisabled(); - }); - - // after - runs after the last test in order of declaration - after(async() => { - // open browser - await browser.newUser(); - await browser.url(`/`); - // select domain - let domain = 'athenz.dev.functional-test'; - let testDomain = await $(`a*=${domain}`); - await browser.waitUntil(async () => await testDomain.isClickable()); - await testDomain.click(); - - // delete the delegate role used in the test - // find row with 'delegated-role' in name and click delete on svg - let buttonDeleteDelegatedRole = await $('.//*[local-name()="svg" and @id="delegated-role-delete-role-button"]'); - await buttonDeleteDelegatedRole.click(); - let modalDeleteButton = await $('button*=Delete'); - await modalDeleteButton.click(); - }); -}) diff --git a/ui/src/__tests__/spec/tests/roles.spec.js b/ui/src/__tests__/spec/tests/roles.spec.js index d620e8acdcc..45edfb54259 100644 --- a/ui/src/__tests__/spec/tests/roles.spec.js +++ b/ui/src/__tests__/spec/tests/roles.spec.js @@ -80,4 +80,258 @@ describe('role screen tests', () => { let modalDeleteButton = await $('button*=Delete'); await modalDeleteButton.click(); }); + + it('when creating or editing a delegated role, all additional settings except description must be disabled', async () => { + // open browser + await browser.newUser(); + await browser.url(`/`); + // select domain + let domain = 'athenz.dev.functional-test'; + let testDomain = await $(`a*=${domain}`); + await browser.waitUntil(async () => await testDomain.isClickable()); + await testDomain.click(); + + // open Add Role screen + let addRoleButton = await $('button*=Add Role'); + await browser.waitUntil(async () => await addRoleButton.isClickable()); + await addRoleButton.click(); + // select Delegated + let delegatedButton = await $('div*=Delegated'); + await delegatedButton.click(); + // verify all settings except Description are disabled + let advancedSettingsIcon = await $('#advanced-settings-icon'); + await advancedSettingsIcon.click(); + let switchSettingAuditEnabled = await $('#switch-settingauditEnabled'); + await expect(switchSettingAuditEnabled).toBeDisabled(); + let switchSettingReviewEnabled = await $('#switch-settingreviewEnabled'); + await expect(switchSettingReviewEnabled).toBeDisabled(); + let switchSettingDeleteProtection = await $('#switch-settingdeleteProtection'); + await expect(switchSettingDeleteProtection).toBeDisabled(); + let switchSettingSelfServe = await $('#switch-settingselfServe'); + await expect(switchSettingSelfServe).toBeDisabled(); + let switchSettingSelfRenew = await $('#switch-settingselfRenew'); + await expect(switchSettingSelfRenew).toBeDisabled(); + let inputSelfRenewMins = await $('#setting-selfRenewMins'); + await expect(inputSelfRenewMins).toBeDisabled(); + let inputMemberExpiryDays = await $('#setting-memberExpiryDays'); + await expect(inputMemberExpiryDays).toBeDisabled(); + let inputGroupExpiryDays = await $('#setting-groupExpiryDays'); + await expect(inputGroupExpiryDays).toBeDisabled(); + let inputGroupReviewDays = await $('#setting-groupReviewDays'); + await expect(inputGroupReviewDays).toBeDisabled(); + let inputServiceExpiryDays = await $('#setting-serviceExpiryDays'); + await expect(inputServiceExpiryDays).toBeDisabled(); + let inputServiceReviewDays = await $('#setting-serviceReviewDays'); + await expect(inputServiceReviewDays).toBeDisabled(); + let inputTokenExpiryMins = await $('#setting-tokenExpiryMins'); + await expect(inputTokenExpiryMins).toBeDisabled(); + let inputCertExpiryMins = await $('#setting-certExpiryMins'); + await expect(inputCertExpiryMins).toBeDisabled(); + let dropdownUserAuthorityFilter = await $('[name="setting-userAuthorityFilter"]'); + await expect(dropdownUserAuthorityFilter).toBeDisabled(); + let dropdownUserAuthorityExpiration = await $('[name="setting-userAuthorityExpiration"]'); + await expect(dropdownUserAuthorityExpiration).toBeDisabled(); + let inputSettingDescription = await $('#setting-description'); + await expect(inputSettingDescription).toBeEnabled(); + let inputMaxMembers = await $('#setting-maxMembers'); + await expect(inputMaxMembers).toBeDisabled(); + + // add role info + let inputRoleName = await $('#role-name-input'); + let roleName = 'delegated-role'; + await inputRoleName.addValue(roleName); + let inputDelegateTo = await $('#delegated-to-input'); + await inputDelegateTo.addValue('athenz.dev'); + let buttonSubmit = await $('button*=Submit'); + // submit role + await buttonSubmit.click(); + + // find row with 'delegated-role' in name and click settings svg + let buttonSettingsOfDelegatedRole = await $('.//*[local-name()="svg" and @id="delegated-role-setting-role-button"]'); + await buttonSettingsOfDelegatedRole.click(); + + // verify all settings except Description are disabled + switchSettingReviewEnabled = await $('#switch-settingreviewEnabled'); + await expect(switchSettingReviewEnabled).toBeDisabled(); + switchSettingDeleteProtection = await $('#switch-settingdeleteProtection'); + await expect(switchSettingDeleteProtection).toBeDisabled(); + switchSettingSelfServe = await $('#switch-settingselfServe'); + await expect(switchSettingSelfServe).toBeDisabled(); + switchSettingSelfRenew = await $('#switch-settingselfRenew'); + await expect(switchSettingSelfRenew).toBeDisabled(); + inputSelfRenewMins = await $('#setting-selfRenewMins'); + await expect(inputSelfRenewMins).toBeDisabled(); + inputMemberExpiryDays = await $('#setting-memberExpiryDays'); + await expect(inputMemberExpiryDays).toBeDisabled(); + inputGroupExpiryDays = await $('#setting-groupExpiryDays'); + await expect(inputGroupExpiryDays).toBeDisabled(); + inputGroupReviewDays = await $('#setting-groupReviewDays'); + await expect(inputGroupReviewDays).toBeDisabled(); + inputServiceExpiryDays = await $('#setting-serviceExpiryDays'); + await expect(inputServiceExpiryDays).toBeDisabled(); + inputServiceReviewDays = await $('#setting-serviceReviewDays'); + await expect(inputServiceReviewDays).toBeDisabled(); + inputTokenExpiryMins = await $('#setting-tokenExpiryMins'); + await expect(inputTokenExpiryMins).toBeDisabled(); + inputCertExpiryMins = await $('#setting-certExpiryMins'); + await expect(inputCertExpiryMins).toBeDisabled(); + dropdownUserAuthorityFilter = await $('[name="setting-userAuthorityFilter"]'); + await expect(dropdownUserAuthorityFilter).toBeDisabled(); + dropdownUserAuthorityExpiration = await $('[name="setting-userAuthorityExpiration"]'); + await expect(dropdownUserAuthorityExpiration).toBeDisabled(); + inputSettingDescription = await $('#setting-description'); + await expect(inputSettingDescription).toBeEnabled(); + inputMaxMembers = await $('#setting-maxMembers'); + await expect(inputMaxMembers).toBeDisabled(); + }); + + // after - runs after the last test in order of declaration + after(async() => { + // open browser + await browser.newUser(); + await browser.url(`/`); + // select domain + let domain = 'athenz.dev.functional-test'; + let testDomain = await $(`a*=${domain}`); + await browser.waitUntil(async () => await testDomain.isClickable()); + await testDomain.click(); + + // delete the delegate role used in the test + // find row with 'delegated-role' in name and click delete on svg + let buttonDeleteDelegatedRole = await $('.//*[local-name()="svg" and @id="delegated-role-delete-role-button"]'); + await buttonDeleteDelegatedRole.click(); + let modalDeleteButton = await $('button*=Delete'); + await modalDeleteButton.click(); + }); + + it('member dropdown when creating a role and adding to existing role - should preserve input on blur, make input bold when selected in dropdown, reject unselected input', async () => { + await browser.newUser(); + await browser.url(`/domain/athenz.dev.functional-test/role`); + await expect(browser).toHaveUrlContaining('athenz'); + + // click add role + let addRoleBtn = await $('button*=Add Role'); + await browser.waitUntil(async () => await addRoleBtn.isClickable()); + await addRoleBtn.click(); + + await $('input[id="role-name-input"]').addValue(dropdownTestRoleName); + + const invalidMember = 'admi'; + // add random text to modal input + let memberInput = await $('input[name="member-name"]'); + await memberInput.addValue(invalidMember); + + // blur without causing calendar widget to close other elements + await browser.keys('Tab'); + await memberInput.click(); + + // input did not change + expect(await memberInput.getValue()).toBe(invalidMember); + + // input is not bold + let fontWeight = await memberInput.getCSSProperty('font-weight').value; + expect(fontWeight).toBeUndefined(); + + // submit (item in dropdown is not selected) + let submitButton = await $('button*=Submit'); + await submitButton.click(); + + // verify error message + let errorMessage = await $('div[data-testid="error-message"]'); + expect(await errorMessage.getText()).toBe('Member must be selected in the dropdown or member input field must be empty.'); + + // type valid input and select item in dropdown + let clearInput = await $(`.//*[local-name()="svg" and @data-wdio="clear-input"]`); + await clearInput.click(); + const validMember = 'unix.yahoo'; + await memberInput.addValue(validMember); + let dropdownOption = await $(`div*=${validMember}`); + await dropdownOption.click(); + + // verify input contains selected member + expect(await memberInput.getValue()).toBe(validMember); + + // verify input is in bold + fontWeight = await memberInput.getCSSProperty('font-weight'); + expect(fontWeight.value === 700).toBe(true); + + // submit + submitButton = await $('button*=Submit'); + await submitButton.click(); + + // role can be seen added + let roleRow = await $(`div[data-wdio=${dropdownTestRoleName}-role-row]`).$(`span*=${dropdownTestRoleName}`); + await expect(roleRow).toHaveTextContaining(dropdownTestRoleName); + + // view role members + await $(`.//*[local-name()="svg" and @data-wdio="${dropdownTestRoleName}-view-members"]`).click(); + + // role has added member + let memberRow = await $(`tr[data-wdio='${validMember}-member-row']`).$(`td*=${validMember}`); + await expect(memberRow).toHaveTextContaining(validMember); + + // delete member + await $(`.//*[local-name()="svg" and @data-wdio="${validMember}-delete-member"]`).click(); + await $('button*=Delete').click(); + + // TEST ADD MEMBER TO EXISTING ROLE + + // open add member window + await $('button*=Add Member').click(); + + // test incomplete input in dropdown + memberInput = await $('input[name="member-name"]'); + await memberInput.addValue(invalidMember); + + // blur + await browser.keys('Tab'); + await memberInput.click(); + + // input did not change + expect(await memberInput.getValue()).toBe(invalidMember); + + // input is not bold + fontWeight = await memberInput.getCSSProperty('font-weight').value; + expect(fontWeight).toBeUndefined(); + + // submit (item in dropdown is not selected) + submitButton = await $('button*=Submit'); + await submitButton.click(); + + // verify error message + errorMessage = await $('div[data-testid="error-message"]'); + expect(await errorMessage.getText()).toBe('Member must be selected in the dropdown.'); + + // type valid input and select item in dropdown + clearInput = await $(`.//*[local-name()="svg" and @data-wdio="clear-input"]`); + await clearInput.click(); + await memberInput.addValue(validMember); + + dropdownOption = await $(`.//div[@role='option' and contains(., '${validMember}')]`); + await dropdownOption.click(); + + // verify input contains selected memeber + expect(await memberInput.getValue()).toBe(validMember); + + // verify input is in bold + fontWeight = await memberInput.getCSSProperty('font-weight'); + expect(fontWeight.value === 700).toBe(true); + + // submit + await submitButton.click(); + + // verify new member was added + let validMemberTd = await $(`tr[data-wdio='${validMember}-member-row']`).$(`td*=${validMember}`); + expect(validMemberTd).toHaveText(`${validMember}`); + }); + + after(async() => { + // delete role created in previous test + await browser.newUser(); + await browser.url(`/domain/athenz.dev.functional-test/role`); + await expect(browser).toHaveUrlContaining('athenz'); + + await $(`.//*[local-name()="svg" and @id="${dropdownTestRoleName}-delete-role-button"]`).click(); + await $('button*=Delete').click(); + }); }) diff --git a/ui/src/components/denali/InputDropdown.js b/ui/src/components/denali/InputDropdown.js index 91a9ee158f0..83b3924df89 100644 --- a/ui/src/components/denali/InputDropdown.js +++ b/ui/src/components/denali/InputDropdown.js @@ -210,6 +210,7 @@ class InputDropdown extends React.Component { {showClose && ( this.props.onChange(selected)} + onSelect={(selected) => this.props.onChange(selected)} // onSelect seem to trigger more consistently than onChange {...(this.props.onInputValueChange !== undefined && { onInputValueChange:(evt) => this.props.onInputValueChange(evt) })} - {...(this.props.defaultHighlightedIndex !== undefined && { - defaultHighlightedIndex: this.props.defaultHighlightedIndex - })} - {...(this.props.stateReducer !== undefined && { - stateReducer: (state, changes) => this.props.stateReducer(state, changes) - })} + defaultHighlightedIndex={0} + stateReducer={(state, changes)=>{ + // keep input changes when user clicks outside input + if(changes.type && (changes.type === Downshift.stateChangeTypes.mouseUp + || changes.type === Downshift.stateChangeTypes.blurInput)) { + changes.inputValue = state.inputValue; + } + return changes; + }} > {({ clearSelection, diff --git a/ui/src/components/denali/icons/Icon.js b/ui/src/components/denali/icons/Icon.js index 58d45714cd8..020dddc3416 100644 --- a/ui/src/components/denali/icons/Icon.js +++ b/ui/src/components/denali/icons/Icon.js @@ -25,6 +25,7 @@ const Icon = (props) => { const viewBoxDimensions = '0 0 ' + props.viewBoxWidth + ' ' + props.viewBoxHeight; const id = props.id || ''; + const dataWdio = props.dataWdio || ''; return ( { onClick={props.onClick} ref={props.innerRef} data-testid='icon' + data-wdio={dataWdio} id={id} > {props.enableTitle && ( diff --git a/ui/src/components/domain/ManageDomains.js b/ui/src/components/domain/ManageDomains.js index 3f243e4e18e..1e718da9dbc 100644 --- a/ui/src/components/domain/ManageDomains.js +++ b/ui/src/components/domain/ManageDomains.js @@ -124,6 +124,7 @@ class ManageDomains extends React.Component { this.saveJustification = this.saveJustification.bind(this); this.saveBusinessService = this.saveBusinessService.bind(this); this.domainNameProvided = this.domainNameProvided.bind(this); + this.onBusinessServiceInputChange = this.onBusinessServiceInputChange.bind(this); this.dateUtils = new DateUtils(); } @@ -174,9 +175,16 @@ class ManageDomains extends React.Component { saveBusinessService(val) { this.setState({ businessServiceName: val, + errorMessageForModal: '' }); } + onBusinessServiceInputChange(val) { + this.setState({ + businessServiceInInput: val, + }) + } + domainNameProvided(val) { this.setState({ domainNameProvided: val, @@ -285,6 +293,17 @@ class ManageDomains extends React.Component { return; } + if (this.state.businessServiceInInput) { + const colonIdx = this.state.businessServiceName.indexOf(':'); + if (colonIdx === -1 && this.state.businessServiceInInput) { + // text is in input but the service name is not selected + this.setState({ + errorMessageForModal: 'Business Service must be selected in the dropdown', + }) + return; + } + } + if (this.state.businessServiceName) { var index = this.props.validBusinessServicesAll.findIndex( (x) => @@ -305,7 +324,7 @@ class ManageDomains extends React.Component { let domainName = this.state.businessServiceDomainName; let businessServiceName = this.state.businessServiceName; let domainMeta = {}; - domainMeta.businessService = businessServiceName; + domainMeta.businessService = businessServiceName ? businessServiceName : ''; this.updateMeta(domainMeta, domainName, this.props._csrf); } @@ -387,7 +406,10 @@ class ManageDomains extends React.Component { align={center} title={title} > - + {title} @@ -452,6 +474,7 @@ class ManageDomains extends React.Component { validBusinessServicesAll={ this.props.validBusinessServicesAll } + onBusinessServiceInputChange={this.onBusinessServiceInputChange} /> ); } diff --git a/ui/src/components/group/AddGroup.js b/ui/src/components/group/AddGroup.js index faa6182c880..b6e1f312535 100644 --- a/ui/src/components/group/AddGroup.js +++ b/ui/src/components/group/AddGroup.js @@ -117,6 +117,7 @@ class AddGroup extends React.Component { saving: 'nope', name: '', newMemberName: '', + memberNameInInput: '', members: [], justification: '', auditEnabled: false, @@ -231,6 +232,13 @@ class AddGroup extends React.Component { return member != null || member != undefined; }) || []; + if (this.state.newMemberName.trim() !== this.state.memberNameInInput.trim()) { + this.setState({ + errorMessage: 'Member must be selected in the dropdown or member input field must be empty.' + }); + return; + } + if (this.state.newMemberName && this.state.newMemberName !== '') { let names = MemberUtils.getUserNames( this.state.newMemberName, @@ -287,6 +295,13 @@ class AddGroup extends React.Component { }); } + onInputValueChange(inputVal) { + this.setState({['memberNameInInput']: inputVal}); + if (this.state.newMemberName && this.state.newMemberName !== inputVal) { + this.setState({['newMemberName']:''}); + } + } + userSearch(part) { return MemberUtils.userSearch(part, this.props.userList); } @@ -338,6 +353,7 @@ class AddGroup extends React.Component { i === null ? '' : i.value @@ -351,6 +367,10 @@ class AddGroup extends React.Component { : '', }) } + onInputValueChange={(inputVal)=> { + // remove value from state if input changed + this.onInputValueChange(inputVal); + }} asyncSearchFunc={this.userSearch} noanim={true} fluid={true} diff --git a/ui/src/components/header/DomainDetails.js b/ui/src/components/header/DomainDetails.js index f1a4e9f89e1..e710f394c75 100644 --- a/ui/src/components/header/DomainDetails.js +++ b/ui/src/components/header/DomainDetails.js @@ -134,6 +134,7 @@ class DomainDetails extends React.Component { this.toggleOnboardToAWSModal = this.toggleOnboardToAWSModal.bind(this); this.saveBusinessService = this.saveBusinessService.bind(this); this.saveJustification = this.saveJustification.bind(this); + this.onBusinessServiceInputChange = this.onBusinessServiceInputChange.bind(this); } componentDidMount() { @@ -226,9 +227,16 @@ class DomainDetails extends React.Component { saveBusinessService(val) { this.setState({ tempBusinessServiceName: val, + errorMessageForModal: '' }); } + onBusinessServiceInputChange(val) { + this.setState({ + businessServiceInInput: val, + }) + } + updateMeta(meta, domainName, csrf, successMessage) { let auditMsg = this.state.auditRef; if (!auditMsg) { @@ -277,6 +285,23 @@ class DomainDetails extends React.Component { return; } + if (this.state.tempBusinessServiceName && this.state.businessServiceInInput) { + const colonIdx = this.state.tempBusinessServiceName.indexOf(':'); + if (colonIdx === -1 || this.state.tempBusinessServiceName.substring(colonIdx + 1) !== this.state.businessServiceInInput) { + // text in input doesn't match selected service + this.setState({ + errorMessageForModal: 'Business Service must be selected in the dropdown or clear input before submitting', + }) + return; + } + } else if (this.state.businessServiceInInput) { + // text is in input but the service name is not selected + this.setState({ + errorMessageForModal: 'Business Service must be selected in the dropdown or clear input before submitting', + }) + return; + } + if (this.state.tempBusinessServiceName) { var index = this.props.businessServicesAll.findIndex( (x) => @@ -297,7 +322,7 @@ class DomainDetails extends React.Component { let domainName = this.props.domainDetails.name; let businessServiceName = this.state.tempBusinessServiceName; let domainMeta = {}; - domainMeta.businessService = businessServiceName; + domainMeta.businessService = businessServiceName ? businessServiceName : ''; let successMessage = `Successfully set business service for domain ${domainName}`; this.updateMeta( domainMeta, @@ -578,6 +603,7 @@ class DomainDetails extends React.Component { icon={ this.state.expandedDomain ? arrowup : arrowdown } + dataWdio={'domain-details-expand-icon'} onClick={expandDomain} color={colors.icons} isLink @@ -631,6 +657,7 @@ class DomainDetails extends React.Component { validBusinessServicesAll={ this.props.businessServicesAll } + onBusinessServiceInputChange={this.onBusinessServiceInputChange} /> ) : null} {environmentModal} @@ -673,7 +700,9 @@ class DomainDetails extends React.Component { - + {businessServiceTitle} diff --git a/ui/src/components/history/HistoryList.js b/ui/src/components/history/HistoryList.js index bc7efe53503..5101bb0c3e4 100644 --- a/ui/src/components/history/HistoryList.js +++ b/ui/src/components/history/HistoryList.js @@ -323,6 +323,7 @@ class HistoryList extends React.Component {
@@ -452,6 +453,7 @@ class MemberRow extends React.Component { trigger={ { + // remove value from state if input changed + this.onInputValueChange(inputVal); + }} fluid name='business-service-drop' options={this.state.validBusinessServices} diff --git a/ui/src/components/policy/AddAssertion.js b/ui/src/components/policy/AddAssertion.js index d2f8ab202ff..500bfc602f7 100644 --- a/ui/src/components/policy/AddAssertion.js +++ b/ui/src/components/policy/AddAssertion.js @@ -64,7 +64,7 @@ class AddAssertion extends React.Component { if (!this.state.role || this.state.role === '') { this.setState({ - errorMessage: 'Role name is required.', + errorMessage: 'Role must be selected in the dropdown.', }); return; } diff --git a/ui/src/components/policy/AddPolicy.js b/ui/src/components/policy/AddPolicy.js index 858d9bcb854..81a12560546 100644 --- a/ui/src/components/policy/AddPolicy.js +++ b/ui/src/components/policy/AddPolicy.js @@ -46,7 +46,7 @@ export default class AddPolicy extends React.Component { if (!this.state.role || this.state.role === '') { this.setState({ - errorMessage: 'Role name is required.', + errorMessage: 'Role must be selected in the dropdown.', }); return; } diff --git a/ui/src/components/policy/AddRuleForm.js b/ui/src/components/policy/AddRuleForm.js index f465a2ef4c5..158099978c1 100644 --- a/ui/src/components/policy/AddRuleForm.js +++ b/ui/src/components/policy/AddRuleForm.js @@ -84,6 +84,7 @@ class AddRuleForm extends React.Component { selectedEffect: 'ALLOW', errorMessage: null, name: '', + nameInInput: '', action: '', resource: '', case: false, @@ -108,10 +109,19 @@ class AddRuleForm extends React.Component { } roleChanged(evt) { - this.setState({ selectedRole: evt }); + this.setState({ selectedRole: evt ? evt.value: ''}); this.props.onChange('role', evt ? evt.value : null); } + onInputValueChange(inputVal) { + // if value in input doesn't match selected, reset selected - to + // prevent submission of value different from the input + if (this.state.selectedRole && this.state.selectedRole !== inputVal) { + this.setState({selectedRole:''}); + this.props.onChange('role', null); // propagate role up to outer component which does form submission + } + } + inputChanged(key, evt) { if (key == 'case') { this.setState({ [key]: evt.target.checked }); @@ -188,12 +198,16 @@ class AddRuleForm extends React.Component { this.roleChanged(evt)} + onInputValueChange={(inputVal) => { + this.onInputValueChange(inputVal) + }} /> diff --git a/ui/src/components/policy/PolicyRow.js b/ui/src/components/policy/PolicyRow.js index 631196dce96..238aeba7aad 100644 --- a/ui/src/components/policy/PolicyRow.js +++ b/ui/src/components/policy/PolicyRow.js @@ -511,6 +511,7 @@ export class PolicyRow extends React.Component { {/*Rules*/} {this.state.enableDelete ? ( this.props.onClickDeletePolicy( diff --git a/ui/src/components/role-policy/AddRuleFormForRole.js b/ui/src/components/role-policy/AddRuleFormForRole.js index d7f7d94f340..289028c44cc 100644 --- a/ui/src/components/role-policy/AddRuleFormForRole.js +++ b/ui/src/components/role-policy/AddRuleFormForRole.js @@ -17,7 +17,6 @@ import React from 'react'; import styled from '@emotion/styled'; import InputLabel from '../denali/InputLabel'; import Input from '../denali/Input'; -import InputDropdown from '../denali/InputDropdown'; import RadioButtonGroup from '../denali/RadioButtonGroup'; import { colors } from '../denali/styles'; import Color from '../denali/Color'; @@ -54,10 +53,6 @@ const StyledRadioButtonGroup = styled(RadioButtonGroup)` margin-top: 8px; `; -const StyledInputDropDown = styled(InputDropdown)` - width: 500px; -`; - const ErrorDiv = styled.div` margin-left: 155px; `; diff --git a/ui/src/components/role/AddMemberToRoles.js b/ui/src/components/role/AddMemberToRoles.js index dcf6145a406..77a12093847 100644 --- a/ui/src/components/role/AddMemberToRoles.js +++ b/ui/src/components/role/AddMemberToRoles.js @@ -131,18 +131,27 @@ class AddMemberToRoles extends React.Component { .map((role) => NameUtils.getShortName(':role.', role.name)) .sort(), searchText: '', + memberName: '', + memberNameInInput: '' }; this.dateUtils = new DateUtils(); } onSubmit() { - if (!this.state.memberName || this.state.memberName === '') { + if (!this.state.memberName || !this.state.memberNameInInput) { this.setState({ errorMessage: 'Member name is required.', }); return; } + if (this.state.memberName.trim() !== this.state.memberNameInInput.trim()) { + this.setState({ + errorMessage: 'Member must be selected in the dropdown.' + }); + return; + } + if (!this.state.checkedRoles || this.state.checkedRoles.length === 0) { this.setState({ errorMessage: 'Should select at least one role to add members.', @@ -221,6 +230,13 @@ class AddMemberToRoles extends React.Component { this.setState({ checkedRoles }); } + onInputValueChange(inputVal) { + this.setState({['memberNameInInput']: inputVal}); + if (this.state.newMemberName && this.state.newMemberName !== inputVal) { + this.setState({['newMemberName']:''}); + } + } + userSearch(part) { return MemberUtils.userSearch(part, this.props.userList); } @@ -261,6 +277,11 @@ class AddMemberToRoles extends React.Component { { + // remove value from state if input changed + this.onInputValueChange(inputVal); + }} fluid={true} id='member-name' name='member-name' diff --git a/ui/src/components/role/AddRole.js b/ui/src/components/role/AddRole.js index 44764a58cc1..a8509f9e33e 100644 --- a/ui/src/components/role/AddRole.js +++ b/ui/src/components/role/AddRole.js @@ -174,6 +174,7 @@ class AddRole extends React.Component { category: 'regular', name: '', newMemberName: '', + memberNameInInput: '', memberExpiry: '', memberReviewReminder: '', members: [], @@ -283,6 +284,7 @@ class AddRole extends React.Component { newMemberName: '', memberExpiry: '', memberReviewReminder: '', + memberNameInInput: '' }); } @@ -303,6 +305,13 @@ class AddRole extends React.Component { }); } + onInputValueChange(inputVal) { + this.setState({['memberNameInInput']: inputVal}); + if (this.state.newMemberName && this.state.newMemberName !== inputVal) { + this.setState({['newMemberName']:''}); + } + } + onSubmit() { let roleName = this.state.name; @@ -350,6 +359,12 @@ class AddRole extends React.Component { draft.trust = this.state.trustDomain; } }); + if (this.state.newMemberName.trim() !== this.state.memberNameInInput.trim()) { + this.setState({ + errorMessage: 'Member must be selected in the dropdown or member input field must be empty.', + }); + return; + } if (this.state.category === 'delegated') { if (!role.trust) { this.setState({ @@ -476,6 +491,11 @@ class AddRole extends React.Component { { + // remove value from state if input changed + this.onInputValueChange(inputVal); + }} placeholder={ADD_ROLE_MEMBER_PLACEHOLDER} itemToString={(i) => i === null ? '' : i.value diff --git a/ui/src/components/role/RoleRow.js b/ui/src/components/role/RoleRow.js index 8ac0a9565ff..885c6f56371 100644 --- a/ui/src/components/role/RoleRow.js +++ b/ui/src/components/role/RoleRow.js @@ -304,6 +304,7 @@ class RoleRow extends React.Component { rows.push( @@ -334,6 +335,7 @@ class RoleRow extends React.Component { trigger={