From eadd6d782144c371ef282cc1ef9b99ffd5943b15 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Thu, 24 Jun 2021 18:53:03 -0500 Subject: [PATCH] [Enterprise Search] Add notices for deactivated users and SMTP callout (#103285) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Port #3904 to Kibana https://github.com/elastic/ent-search/pull/3904 * DRY out logic interfaces Should have done this long ago * Port #3920 to Kibana https://github.com/elastic/ent-search/pull/3920 * Lint fixes * Remove error state from form We already did this for the users flyout. Basically changes the dirty state of the form from an error state to just showing “Required”. i18n had not been translated yet for `ATTRIBUTE_VALUE_ERROR` * Add loading states * Remove manual disabling of button Co-authored-by: Constance * Remove manual disabling of other button * Lint fixes Co-authored-by: Constance --- .../components/role_mappings/role_mapping.tsx | 2 + .../role_mappings/role_mappings_logic.test.ts | 3 + .../role_mappings/role_mappings_logic.ts | 86 ++++++------------- .../components/role_mappings/user.test.tsx | 24 +++++- .../components/role_mappings/user.tsx | 11 +++ .../__mocks__/elasticsearch_users.ts | 1 + .../role_mapping/attribute_selector.test.tsx | 11 ++- .../role_mapping/attribute_selector.tsx | 5 +- .../shared/role_mapping/constants.ts | 40 +++++++-- .../deactivated_user_callout.test.tsx | 75 ++++++++++++++++ .../role_mapping/deactivated_user_callout.tsx | 28 ++++++ .../applications/shared/role_mapping/index.ts | 2 + .../role_mapping/role_mapping_flyout.test.tsx | 1 + .../role_mapping/role_mapping_flyout.tsx | 3 + .../applications/shared/role_mapping/types.ts | 66 ++++++++++++++ .../shared/role_mapping/user_flyout.test.tsx | 1 + .../shared/role_mapping/user_flyout.tsx | 4 +- .../role_mapping/user_selector.test.tsx | 3 +- .../shared/role_mapping/user_selector.tsx | 18 +++- .../shared/role_mapping/users_table.test.tsx | 20 ++++- .../shared/role_mapping/users_table.tsx | 10 ++- .../public/applications/shared/types.ts | 3 +- .../views/role_mappings/role_mapping.tsx | 2 + .../role_mappings/role_mappings_logic.test.ts | 3 + .../role_mappings/role_mappings_logic.ts | 84 ++++++------------ .../views/role_mappings/user.test.tsx | 24 +++++- .../views/role_mappings/user.tsx | 11 +++ 27 files changed, 406 insertions(+), 135 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/deactivated_user_callout.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/deactivated_user_callout.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/types.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx index dbebd8e46a2195..02ff4c0ba1c3d2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx @@ -45,6 +45,7 @@ export const RoleMapping: React.FC = () => { selectedEngines, selectedAuthProviders, roleMappingErrors, + formLoading, } = useValues(RoleMappingsLogic); const isNew = !roleMapping; @@ -67,6 +68,7 @@ export const RoleMapping: React.FC = () => { return ( { userCreated: false, userFormIsNewUser: true, userFormUserIsExisting: true, + smtpSettingsPresent: false, + formLoading: false, }; const mappingsServerProps = { @@ -70,6 +72,7 @@ describe('RoleMappingsLogic', () => { hasAdvancedRoles: false, singleUserRoleMappings: [asSingleUserRoleMapping], elasticsearchUsers, + smtpSettingsPresent: false, }; beforeEach(() => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts index 0b57e1d08a2946..fb574a35889898 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts @@ -7,14 +7,17 @@ import { kea, MakeLogicType } from 'kea'; -import { EuiComboBoxOptionOption } from '@elastic/eui'; - import { clearFlashMessages, flashAPIErrors, setSuccessMessage, } from '../../../shared/flash_messages'; import { HttpLogic } from '../../../shared/http'; +import { + RoleMappingsBaseServerDetails, + RoleMappingsBaseActions, + RoleMappingsBaseValues, +} from '../../../shared/role_mapping'; import { ANY_AUTH_PROVIDER } from '../../../shared/role_mapping/constants'; import { AttributeName, SingleUserRoleMapping, ElasticsearchUser } from '../../../shared/types'; import { ASRoleMapping, RoleTypes } from '../../types'; @@ -29,16 +32,11 @@ import { type UserMapping = SingleUserRoleMapping; -interface RoleMappingsServerDetails { +interface RoleMappingsServerDetails extends RoleMappingsBaseServerDetails { roleMappings: ASRoleMapping[]; - attributes: string[]; - authProviders: string[]; availableEngines: Engine[]; - elasticsearchRoles: string[]; - elasticsearchUsers: ElasticsearchUser[]; - hasAdvancedRoles: boolean; - multipleAuthProvidersConfig: boolean; singleUserRoleMappings: UserMapping[]; + hasAdvancedRoles: boolean; } const getFirstAttributeName = (roleMapping: ASRoleMapping) => @@ -47,24 +45,7 @@ const getFirstAttributeValue = (roleMapping: ASRoleMapping) => Object.entries(roleMapping.rules)[0][1] as AttributeName; const emptyUser = { username: '', email: '' } as ElasticsearchUser; -interface RoleMappingsActions { - handleAccessAllEnginesChange(selected: boolean): { selected: boolean }; - handleAuthProviderChange(value: string[]): { value: string[] }; - handleAttributeSelectorChange( - value: AttributeName, - firstElasticsearchRole: string - ): { value: AttributeName; firstElasticsearchRole: string }; - handleAttributeValueChange(value: string): { value: string }; - handleDeleteMapping(roleMappingId: string): { roleMappingId: string }; - handleEngineSelectionChange(engineNames: string[]): { engineNames: string[] }; - handleRoleChange(roleType: RoleTypes): { roleType: RoleTypes }; - handleUsernameSelectChange(username: string): { username: string }; - handleSaveMapping(): void; - handleSaveUser(): void; - initializeRoleMapping(roleMappingId?: string): { roleMappingId?: string }; - initializeSingleUserRoleMapping(roleMappingId?: string): { roleMappingId?: string }; - initializeRoleMappings(): void; - resetState(): void; +interface RoleMappingsActions extends RoleMappingsBaseActions { setRoleMapping(roleMapping: ASRoleMapping): { roleMapping: ASRoleMapping }; setSingleUserRoleMapping(data?: UserMapping): { singleUserRoleMapping: UserMapping }; setRoleMappings({ @@ -73,34 +54,14 @@ interface RoleMappingsActions { roleMappings: ASRoleMapping[]; }): { roleMappings: ASRoleMapping[] }; setRoleMappingsData(data: RoleMappingsServerDetails): RoleMappingsServerDetails; - setElasticsearchUser( - elasticsearchUser?: ElasticsearchUser - ): { elasticsearchUser: ElasticsearchUser }; - openRoleMappingFlyout(): void; - openSingleUserRoleMappingFlyout(): void; - closeUsersAndRolesFlyout(): void; - setRoleMappingErrors(errors: string[]): { errors: string[] }; - enableRoleBasedAccess(): void; - setUserExistingRadioValue(userFormUserIsExisting: boolean): { userFormUserIsExisting: boolean }; - setElasticsearchUsernameValue(username: string): { username: string }; - setElasticsearchEmailValue(email: string): { email: string }; - setUserCreated(): void; - setUserFormIsNewUser(userFormIsNewUser: boolean): { userFormIsNewUser: boolean }; + handleAccessAllEnginesChange(selected: boolean): { selected: boolean }; + handleEngineSelectionChange(engineNames: string[]): { engineNames: string[] }; + handleRoleChange(roleType: RoleTypes): { roleType: RoleTypes }; } -interface RoleMappingsValues { +interface RoleMappingsValues extends RoleMappingsBaseValues { accessAllEngines: boolean; - attributeName: AttributeName; - attributeValue: string; - attributes: string[]; - availableAuthProviders: string[]; availableEngines: Engine[]; - dataLoading: boolean; - elasticsearchRoles: string[]; - elasticsearchUsers: ElasticsearchUser[]; - elasticsearchUser: ElasticsearchUser; - hasAdvancedRoles: boolean; - multipleAuthProvidersConfig: boolean; roleMapping: ASRoleMapping | null; roleMappings: ASRoleMapping[]; singleUserRoleMapping: UserMapping | null; @@ -108,13 +69,7 @@ interface RoleMappingsValues { roleType: RoleTypes; selectedAuthProviders: string[]; selectedEngines: Set; - roleMappingFlyoutOpen: boolean; - singleUserRoleMappingFlyoutOpen: boolean; - selectedOptions: EuiComboBoxOptionOption[]; - roleMappingErrors: string[]; - userFormUserIsExisting: boolean; - userCreated: boolean; - userFormIsNewUser: boolean; + hasAdvancedRoles: boolean; } export const RoleMappingsLogic = kea>({ @@ -369,6 +324,21 @@ export const RoleMappingsLogic = kea userFormIsNewUser, }, ], + smtpSettingsPresent: [ + false, + { + setRoleMappingsData: (_, { smtpSettingsPresent }) => smtpSettingsPresent, + }, + ], + formLoading: [ + false, + { + handleSaveMapping: () => true, + handleSaveUser: () => true, + initializeRoleMappings: () => false, + setRoleMappingErrors: () => false, + }, + ], }, selectors: ({ selectors }) => ({ selectedOptions: [ diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.test.tsx index 88103532bd1492..cec7f1541a31a2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.test.tsx @@ -14,7 +14,12 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { UserFlyout, UserAddedInfo, UserInvitationCallout } from '../../../shared/role_mapping'; +import { + UserFlyout, + UserAddedInfo, + UserInvitationCallout, + DeactivatedUserCallout, +} from '../../../shared/role_mapping'; import { elasticsearchUsers } from '../../../shared/role_mapping/__mocks__/elasticsearch_users'; import { wsSingleUserRoleMapping } from '../../../shared/role_mapping/__mocks__/roles'; @@ -91,6 +96,23 @@ describe('User', () => { expect(wrapper.find(UserAddedInfo)).toHaveLength(1); }); + it('renders DeactivatedUserCallout', () => { + setMockValues({ + ...mockValues, + singleUserRoleMapping: { + ...wsSingleUserRoleMapping, + invitation: null, + elasticsearchUser: { + ...wsSingleUserRoleMapping.elasticsearchUser, + enabled: false, + }, + }, + }); + const wrapper = shallow(); + + expect(wrapper.find(DeactivatedUserCallout)).toHaveLength(1); + }); + it('disables form when username value not present', () => { setMockValues({ ...mockValues, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.tsx index df231fac64df74..018d29706b05f9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.tsx @@ -17,6 +17,7 @@ import { UserSelector, UserAddedInfo, UserInvitationCallout, + DeactivatedUserCallout, } from '../../../shared/role_mapping'; import { RoleTypes } from '../../types'; @@ -48,6 +49,8 @@ export const User: React.FC = () => { roleMappingErrors, userCreated, userFormIsNewUser, + smtpSettingsPresent, + formLoading, } = useValues(RoleMappingsLogic); const roleTypes = hasAdvancedRoles ? [...standardRoles, ...advancedRoles] : standardRoles; @@ -55,6 +58,11 @@ export const User: React.FC = () => { const showEngineAssignmentSelector = hasEngines && hasAdvancedRoles; const flyoutDisabled = !userFormUserIsExisting && (!elasticsearchUser.email || !elasticsearchUser.username); + const userIsDeactivated = !!( + singleUserRoleMapping && + !singleUserRoleMapping.invitation && + !singleUserRoleMapping.elasticsearchUser.enabled + ); const userAddedInfo = singleUserRoleMapping && ( { 0} error={roleMappingErrors}> { return ( { > {userCreated ? userAddedInfo : createUserForm} {userInvitationCallout} + {userIsDeactivated && } ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/__mocks__/elasticsearch_users.ts b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/__mocks__/elasticsearch_users.ts index 500f5606756790..6d9365d63c3204 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/__mocks__/elasticsearch_users.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/__mocks__/elasticsearch_users.ts @@ -9,5 +9,6 @@ export const elasticsearchUsers = [ { email: 'user1@user.com', username: 'user1', + enabled: true, }, ]; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.test.tsx index d19d090b6e706d..8fed459b0a8dc6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.test.tsx @@ -9,12 +9,12 @@ import React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; -import { EuiComboBox, EuiFieldText } from '@elastic/eui'; +import { EuiComboBox, EuiFieldText, EuiFormRow } from '@elastic/eui'; import { AttributeName } from '../types'; import { AttributeSelector } from './attribute_selector'; -import { ANY_AUTH_PROVIDER, ANY_AUTH_PROVIDER_OPTION_LABEL } from './constants'; +import { ANY_AUTH_PROVIDER, ANY_AUTH_PROVIDER_OPTION_LABEL, REQUIRED_LABEL } from './constants'; const handleAttributeSelectorChange = jest.fn(); const handleAttributeValueChange = jest.fn(); @@ -166,5 +166,12 @@ describe('AttributeSelector', () => { baseProps.elasticsearchRoles[0] ); }); + + it('shows helpText when attributeValueInvalid', () => { + const wrapper = shallow(); + const formRow = wrapper.find(EuiFormRow).at(2); + + expect(formRow.prop('helpText')).toEqual(REQUIRED_LABEL); + }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.tsx index bedb6b3df90f25..e24bc03bea452b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.tsx @@ -21,7 +21,7 @@ import { ANY_AUTH_PROVIDER, ANY_AUTH_PROVIDER_OPTION_LABEL, ATTRIBUTE_VALUE_LABEL, - ATTRIBUTE_VALUE_ERROR, + REQUIRED_LABEL, AUTH_ANY_PROVIDER_LABEL, AUTH_INDIVIDUAL_PROVIDER_LABEL, AUTH_PROVIDER_LABEL, @@ -129,8 +129,7 @@ export const AttributeSelector: React.FC = ({ {attributeName === 'role' ? ( { + it('renders with new', () => { + const wrapper = shallow(); + + expect(wrapper).toMatchInlineSnapshot(` + + + + + + User deactivated + + + + + This user is not currently active, and access has been temporarily revoked. Users can be re-activated via the User Management area of the Kibana console. + + + + `); + }); + + it('renders with existing', () => { + const wrapper = shallow(); + + expect(wrapper).toMatchInlineSnapshot(` + + + + + + + User deactivated + + + + + This user is not currently active, and access has been temporarily revoked. Users can be re-activated via the User Management area of the Kibana console. + + + + `); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/deactivated_user_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/deactivated_user_callout.tsx new file mode 100644 index 00000000000000..5b69420d169ce1 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/deactivated_user_callout.tsx @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { EuiSpacer, EuiText, EuiIcon } from '@elastic/eui'; + +interface Props { + isNew: boolean; +} + +import { DEACTIVATED_USER_CALLOUT_LABEL, DEACTIVATED_USER_CALLOUT_DESCRIPTION } from './constants'; + +export const DeactivatedUserCallout: React.FC = ({ isNew }) => ( + <> + {!isNew && } + + {DEACTIVATED_USER_CALLOUT_LABEL} + + + {DEACTIVATED_USER_CALLOUT_DESCRIPTION} + + +); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/index.ts index 8096b86939ff33..c10fc5c9d8242d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/index.ts @@ -5,7 +5,9 @@ * 2.0. */ +export * from './types'; export { AttributeSelector } from './attribute_selector'; +export { DeactivatedUserCallout } from './deactivated_user_callout'; export { RolesEmptyPrompt } from './roles_empty_prompt'; export { RoleMappingsTable } from './role_mappings_table'; export { RoleOptionLabel } from './role_option_label'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.test.tsx index ffcf5508233fcf..651a46f5df85e2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.test.tsx @@ -26,6 +26,7 @@ describe('RoleMappingFlyout', () => { const props = { isNew: true, disabled: false, + formLoading: false, closeUsersAndRolesFlyout, handleSaveMapping, }; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.tsx index 4416a2de28011a..bcaf26ccf2cfc6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.tsx @@ -36,6 +36,7 @@ interface Props { children: React.ReactNode; isNew: boolean; disabled: boolean; + formLoading: boolean; closeUsersAndRolesFlyout(): void; handleSaveMapping(): void; } @@ -44,6 +45,7 @@ export const RoleMappingFlyout: React.FC = ({ children, isNew, disabled, + formLoading, closeUsersAndRolesFlyout, handleSaveMapping, }) => ( @@ -78,6 +80,7 @@ export const RoleMappingFlyout: React.FC = ({ { isNew: true, isComplete: false, disabled: false, + formLoading: false, closeUserFlyout, handleSaveUser, }; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_flyout.tsx index a3be5e295ddfeb..8741a2b4517d3d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_flyout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_flyout.tsx @@ -28,6 +28,7 @@ interface Props { isNew: boolean; isComplete: boolean; disabled: boolean; + formLoading: boolean; closeUserFlyout(): void; handleSaveUser(): void; } @@ -49,6 +50,7 @@ export const UserFlyout: React.FC = ({ isNew, isComplete, disabled, + formLoading, closeUserFlyout, handleSaveUser, }) => { @@ -75,7 +77,7 @@ export const UserFlyout: React.FC = ({ {CANCEL_BUTTON_LABEL} - + {isNew ? ADD_USER_LABEL : UPDATE_USER_LABEL} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.test.tsx index 60bac97d09835b..0aea55a51040c5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.test.tsx @@ -34,6 +34,7 @@ describe('UserSelector', () => { const props = { isNewUser: true, + smtpSettingsPresent: false, userFormUserIsExisting: true, elasticsearchUsers, elasticsearchUser: elasticsearchUsers[0], @@ -101,7 +102,7 @@ describe('UserSelector', () => { {...props} userFormUserIsExisting={false} elasticsearchUsers={[]} - elasticsearchUser={{ email: '', username: '' }} + elasticsearchUser={{ email: '', username: '', enabled: true }} /> ); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.tsx index d65f97265f6a3e..25aff5077c680a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/user_selector.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { EuiFieldText, + EuiLink, EuiRadio, EuiFormRow, EuiSelect, @@ -21,6 +22,9 @@ import { ElasticsearchUser } from '../../shared/types'; import { Role as WSRole } from '../../workplace_search/types'; import { USERNAME_LABEL, EMAIL_LABEL } from '../constants'; +import { docLinks } from '../doc_links'; + +const SMTP_URL = `${docLinks.enterpriseSearchBase}/mailer-configuration.html`; import { NEW_USER_LABEL, @@ -28,12 +32,15 @@ import { USERNAME_NO_USERS_TEXT, REQUIRED_LABEL, ROLE_LABEL, + SMTP_CALLOUT_LABEL, + SMTP_LINK_LABEL, } from './constants'; type SharedRole = WSRole | ASRole; interface Props { isNewUser: boolean; + smtpSettingsPresent: boolean; userFormUserIsExisting: boolean; elasticsearchUsers: ElasticsearchUser[]; elasticsearchUser: ElasticsearchUser; @@ -48,6 +55,7 @@ interface Props { export const UserSelector: React.FC = ({ isNewUser, + smtpSettingsPresent, userFormUserIsExisting, elasticsearchUsers, elasticsearchUser, @@ -66,6 +74,14 @@ export const UserSelector: React.FC = ({ })); const hasElasticsearchUsers = elasticsearchUsers.length > 0; const showNewUserExistingUserControls = userFormUserIsExisting && hasElasticsearchUsers; + const smptHelpText = !smtpSettingsPresent && ( + <> + {SMTP_CALLOUT_LABEL}{' '} + + {SMTP_LINK_LABEL} + + + ); const roleSelect = ( @@ -80,7 +96,7 @@ export const UserSelector: React.FC = ({ ); const emailInput = ( - + { singleUserRoleMappings: [wsSingleUserRoleMapping], initializeSingleUserRoleMapping, handleDeleteMapping, + enabled: true, }; it('renders', () => { @@ -55,6 +56,7 @@ describe('UsersTable', () => { elasticsearchUser: { email: null, username: 'foo', + enabled: true, }, }; const wrapper = mount(); @@ -97,4 +99,20 @@ describe('UsersTable', () => { `${engines[0].name}, ${engines[1].name} + 1` ); }); + + it('renders deactivatedBadge', () => { + const disabledUser = { + ...wsSingleUserRoleMapping, + elasticsearchUser: { + email: 'email@user.com', + username: 'foo', + enabled: false, + }, + invitation: null, + }; + const wrapper = mount(); + const cell = wrapper.find('[data-test-subj="UsernameCell"]'); + + expect(cell.find(EuiBadge)).toHaveLength(1); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_table.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_table.tsx index 674796775b1d32..25a9eee38f93f8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/users_table.tsx @@ -15,6 +15,7 @@ import { WSRoleMapping } from '../../workplace_search/types'; import { INVITATION_PENDING_LABEL, + DEACTIVATED_LABEL, ALL_LABEL, FILTER_USERS_LABEL, NO_USERS_LABEL, @@ -37,6 +38,7 @@ interface SharedUser extends SingleUserRoleMapping—; const invitationBadge = {INVITATION_PENDING_LABEL}; +const deactivatedBadge = {DEACTIVATED_LABEL}; export const UsersTable: React.FC = ({ accessItemKey, @@ -63,6 +66,7 @@ export const UsersTable: React.FC = ({ const users = ((singleUserRoleMappings as SharedUser[]).map((user) => ({ username: user.elasticsearchUser.username, email: user.elasticsearchUser.email, + enabled: user.elasticsearchUser.enabled, roleType: user.roleMapping.roleType, id: user.roleMapping.id, accessItems: (user.roleMapping as SharedRoleMapping)[accessItemKey], @@ -73,7 +77,11 @@ export const UsersTable: React.FC = ({ { field: 'username', name: USERNAME_LABEL, - render: (_, { username }: SharedUser) => username, + render: (_, { username, invitation, enabled }: SharedUser) => ( +
+ {username} {!invitation && !enabled && deactivatedBadge} +
+ ), }, { field: 'email', diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts index e6d2c67d1baf83..5a90dd2c4a6bb6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts @@ -49,10 +49,11 @@ export interface Invitation { export interface ElasticsearchUser { email: string | null; username: string; + enabled: boolean; } export interface SingleUserRoleMapping { - invitation: Invitation; + invitation: Invitation | null; elasticsearchUser: ElasticsearchUser; roleMapping: T; } diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mapping.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mapping.tsx index 20211d40d7010b..734716af9f6275 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mapping.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mapping.tsx @@ -59,6 +59,7 @@ export const RoleMapping: React.FC = () => { selectedAuthProviders, roleMapping, roleMappingErrors, + formLoading, } = useValues(RoleMappingsLogic); const isNew = !roleMapping; @@ -69,6 +70,7 @@ export const RoleMapping: React.FC = () => { return ( { userCreated: false, userFormIsNewUser: true, userFormUserIsExisting: true, + smtpSettingsPresent: false, + formLoading: false, }; const roleGroup = { id: '123', @@ -76,6 +78,7 @@ describe('RoleMappingsLogic', () => { elasticsearchRoles: [], singleUserRoleMappings: [wsSingleUserRoleMapping], elasticsearchUsers, + smtpSettingsPresent: false, }; beforeEach(() => { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts index 7f26c8738786c8..6e7104964cdb78 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts @@ -7,14 +7,17 @@ import { kea, MakeLogicType } from 'kea'; -import { EuiComboBoxOptionOption } from '@elastic/eui'; - import { clearFlashMessages, flashAPIErrors, setSuccessMessage, } from '../../../shared/flash_messages'; import { HttpLogic } from '../../../shared/http'; +import { + RoleMappingsBaseServerDetails, + RoleMappingsBaseActions, + RoleMappingsBaseValues, +} from '../../../shared/role_mapping'; import { ANY_AUTH_PROVIDER } from '../../../shared/role_mapping/constants'; import { AttributeName, SingleUserRoleMapping, ElasticsearchUser } from '../../../shared/types'; import { RoleGroup, WSRoleMapping, Role } from '../../types'; @@ -28,14 +31,9 @@ import { type UserMapping = SingleUserRoleMapping; -interface RoleMappingsServerDetails { +interface RoleMappingsServerDetails extends RoleMappingsBaseServerDetails { roleMappings: WSRoleMapping[]; - attributes: string[]; - authProviders: string[]; availableGroups: RoleGroup[]; - elasticsearchUsers: ElasticsearchUser[]; - elasticsearchRoles: string[]; - multipleAuthProvidersConfig: boolean; singleUserRoleMappings: UserMapping[]; } @@ -45,24 +43,8 @@ const getFirstAttributeValue = (roleMapping: WSRoleMapping): string => Object.entries(roleMapping.rules)[0][1] as string; const emptyUser = { username: '', email: '' } as ElasticsearchUser; -interface RoleMappingsActions { - handleAllGroupsSelectionChange(selected: boolean): { selected: boolean }; - handleAuthProviderChange(value: string[]): { value: string[] }; - handleAttributeSelectorChange( - value: AttributeName, - firstElasticsearchRole: string - ): { value: AttributeName; firstElasticsearchRole: string }; - handleAttributeValueChange(value: string): { value: string }; - handleDeleteMapping(roleMappingId: string): { roleMappingId: string }; - handleGroupSelectionChange(groupIds: string[]): { groupIds: string[] }; - handleRoleChange(roleType: Role): { roleType: Role }; - handleUsernameSelectChange(username: string): { username: string }; - handleSaveMapping(): void; - handleSaveUser(): void; - initializeRoleMapping(roleMappingId?: string): { roleMappingId?: string }; - initializeSingleUserRoleMapping(roleMappingId?: string): { roleMappingId?: string }; - initializeRoleMappings(): void; - resetState(): void; +interface RoleMappingsActions extends RoleMappingsBaseActions { + setDefaultGroup(availableGroups: RoleGroup[]): { availableGroups: RoleGroup[] }; setRoleMapping(roleMapping: WSRoleMapping): { roleMapping: WSRoleMapping }; setSingleUserRoleMapping(data?: UserMapping): { singleUserRoleMapping: UserMapping }; setRoleMappings({ @@ -71,34 +53,14 @@ interface RoleMappingsActions { roleMappings: WSRoleMapping[]; }): { roleMappings: WSRoleMapping[] }; setRoleMappingsData(data: RoleMappingsServerDetails): RoleMappingsServerDetails; - setElasticsearchUser( - elasticsearchUser?: ElasticsearchUser - ): { elasticsearchUser: ElasticsearchUser }; - setDefaultGroup(availableGroups: RoleGroup[]): { availableGroups: RoleGroup[] }; - openRoleMappingFlyout(): void; - openSingleUserRoleMappingFlyout(): void; - closeUsersAndRolesFlyout(): void; - setRoleMappingErrors(errors: string[]): { errors: string[] }; - enableRoleBasedAccess(): void; - setUserExistingRadioValue(userFormUserIsExisting: boolean): { userFormUserIsExisting: boolean }; - setElasticsearchUsernameValue(username: string): { username: string }; - setElasticsearchEmailValue(email: string): { email: string }; - setUserCreated(): void; - setUserFormIsNewUser(userFormIsNewUser: boolean): { userFormIsNewUser: boolean }; + handleAllGroupsSelectionChange(selected: boolean): { selected: boolean }; + handleGroupSelectionChange(groupIds: string[]): { groupIds: string[] }; + handleRoleChange(roleType: Role): { roleType: Role }; } -interface RoleMappingsValues { +interface RoleMappingsValues extends RoleMappingsBaseValues { includeInAllGroups: boolean; - attributeName: AttributeName; - attributeValue: string; - attributes: string[]; - availableAuthProviders: string[]; availableGroups: RoleGroup[]; - dataLoading: boolean; - elasticsearchRoles: string[]; - elasticsearchUsers: ElasticsearchUser[]; - elasticsearchUser: ElasticsearchUser; - multipleAuthProvidersConfig: boolean; roleMapping: WSRoleMapping | null; roleMappings: WSRoleMapping[]; singleUserRoleMapping: UserMapping | null; @@ -106,13 +68,6 @@ interface RoleMappingsValues { roleType: Role; selectedAuthProviders: string[]; selectedGroups: Set; - roleMappingFlyoutOpen: boolean; - singleUserRoleMappingFlyoutOpen: boolean; - selectedOptions: EuiComboBoxOptionOption[]; - roleMappingErrors: string[]; - userFormUserIsExisting: boolean; - userCreated: boolean; - userFormIsNewUser: boolean; } export const RoleMappingsLogic = kea>({ @@ -369,6 +324,21 @@ export const RoleMappingsLogic = kea userFormIsNewUser, }, ], + smtpSettingsPresent: [ + false, + { + setRoleMappingsData: (_, { smtpSettingsPresent }) => smtpSettingsPresent, + }, + ], + formLoading: [ + false, + { + handleSaveMapping: () => true, + handleSaveUser: () => true, + initializeRoleMappings: () => false, + setRoleMappingErrors: () => false, + }, + ], }, selectors: ({ selectors }) => ({ selectedOptions: [ diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/user.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/user.test.tsx index 32ee1a7f22875f..d8e1fc160901fa 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/user.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/user.test.tsx @@ -14,7 +14,12 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { UserFlyout, UserAddedInfo, UserInvitationCallout } from '../../../shared/role_mapping'; +import { + UserFlyout, + UserAddedInfo, + UserInvitationCallout, + DeactivatedUserCallout, +} from '../../../shared/role_mapping'; import { elasticsearchUsers } from '../../../shared/role_mapping/__mocks__/elasticsearch_users'; import { wsSingleUserRoleMapping } from '../../../shared/role_mapping/__mocks__/roles'; @@ -90,6 +95,23 @@ describe('User', () => { expect(wrapper.find(UserAddedInfo)).toHaveLength(1); }); + it('renders DeactivatedUserCallout', () => { + setMockValues({ + ...mockValues, + singleUserRoleMapping: { + ...wsSingleUserRoleMapping, + invitation: null, + elasticsearchUser: { + ...wsSingleUserRoleMapping.elasticsearchUser, + enabled: false, + }, + }, + }); + const wrapper = shallow(); + + expect(wrapper.find(DeactivatedUserCallout)).toHaveLength(1); + }); + it('disables form when username value not present', () => { setMockValues({ ...mockValues, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/user.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/user.tsx index bfb32ee31c121d..6447f43e6ed4d8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/user.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/user.tsx @@ -17,6 +17,7 @@ import { UserSelector, UserAddedInfo, UserInvitationCallout, + DeactivatedUserCallout, } from '../../../shared/role_mapping'; import { Role } from '../../types'; @@ -46,12 +47,19 @@ export const User: React.FC = () => { roleMappingErrors, userCreated, userFormIsNewUser, + smtpSettingsPresent, + formLoading, } = useValues(RoleMappingsLogic); const showGroupAssignmentSelector = availableGroups.length > 0; const hasAvailableUsers = elasticsearchUsers.length > 0; const flyoutDisabled = (!userFormUserIsExisting || !hasAvailableUsers) && !elasticsearchUser.username; + const userIsDeactivated = !!( + singleUserRoleMapping && + !singleUserRoleMapping.invitation && + !singleUserRoleMapping.elasticsearchUser.enabled + ); const userAddedInfo = singleUserRoleMapping && ( { 0} error={roleMappingErrors}> { return ( { > {userCreated ? userAddedInfo : createUserForm} {userInvitationCallout} + {userIsDeactivated && } ); };