Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Enterprise Search] Add notices for deactivated users and SMTP callout #103285

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const RoleMapping: React.FC = () => {
selectedEngines,
selectedAuthProviders,
roleMappingErrors,
formLoading,
} = useValues(RoleMappingsLogic);

const isNew = !roleMapping;
Expand All @@ -67,6 +68,7 @@ export const RoleMapping: React.FC = () => {
return (
<RoleMappingFlyout
disabled={attributeValueInvalid || !hasEngineAssignment}
formLoading={formLoading}
isNew={isNew}
closeUsersAndRolesFlyout={closeUsersAndRolesFlyout}
handleSaveMapping={handleSaveMapping}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ describe('RoleMappingsLogic', () => {
userCreated: false,
userFormIsNewUser: true,
userFormUserIsExisting: true,
smtpSettingsPresent: false,
formLoading: false,
};

const mappingsServerProps = {
Expand All @@ -70,6 +72,7 @@ describe('RoleMappingsLogic', () => {
hasAdvancedRoles: false,
singleUserRoleMappings: [asSingleUserRoleMapping],
elasticsearchUsers,
smtpSettingsPresent: false,
};

beforeEach(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -29,16 +32,11 @@ import {

type UserMapping = SingleUserRoleMapping<ASRoleMapping>;

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) =>
Expand All @@ -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({
Expand All @@ -73,48 +54,22 @@ 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;
singleUserRoleMappings: UserMapping[];
roleType: RoleTypes;
selectedAuthProviders: string[];
selectedEngines: Set<string>;
roleMappingFlyoutOpen: boolean;
singleUserRoleMappingFlyoutOpen: boolean;
selectedOptions: EuiComboBoxOptionOption[];
roleMappingErrors: string[];
userFormUserIsExisting: boolean;
userCreated: boolean;
userFormIsNewUser: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super awesome DRY cleanup here! 💯

hasAdvancedRoles: boolean;
}

export const RoleMappingsLogic = kea<MakeLogicType<RoleMappingsValues, RoleMappingsActions>>({
Expand Down Expand Up @@ -369,6 +324,21 @@ export const RoleMappingsLogic = kea<MakeLogicType<RoleMappingsValues, RoleMappi
setUserFormIsNewUser: (_, { userFormIsNewUser }) => userFormIsNewUser,
},
],
smtpSettingsPresent: [
false,
{
setRoleMappingsData: (_, { smtpSettingsPresent }) => smtpSettingsPresent,
},
],
formLoading: [
false,
{
handleSaveMapping: () => true,
handleSaveUser: () => true,
initializeRoleMappings: () => false,
setRoleMappingErrors: () => false,
},
],
},
selectors: ({ selectors }) => ({
selectedOptions: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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(<User />);

expect(wrapper.find(DeactivatedUserCallout)).toHaveLength(1);
});

it('disables form when username value not present', () => {
setMockValues({
...mockValues,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
UserSelector,
UserAddedInfo,
UserInvitationCallout,
DeactivatedUserCallout,
} from '../../../shared/role_mapping';
import { RoleTypes } from '../../types';

Expand Down Expand Up @@ -48,13 +49,20 @@ export const User: React.FC = () => {
roleMappingErrors,
userCreated,
userFormIsNewUser,
smtpSettingsPresent,
formLoading,
} = useValues(RoleMappingsLogic);

const roleTypes = hasAdvancedRoles ? [...standardRoles, ...advancedRoles] : standardRoles;
const hasEngines = availableEngines.length > 0;
const showEngineAssignmentSelector = hasEngines && hasAdvancedRoles;
const flyoutDisabled =
!userFormUserIsExisting && (!elasticsearchUser.email || !elasticsearchUser.username);
const userIsDeactivated = !!(
singleUserRoleMapping &&
!singleUserRoleMapping.invitation &&
!singleUserRoleMapping.elasticsearchUser.enabled
);

const userAddedInfo = singleUserRoleMapping && (
<UserAddedInfo
Expand All @@ -76,6 +84,7 @@ export const User: React.FC = () => {
<EuiForm isInvalid={roleMappingErrors.length > 0} error={roleMappingErrors}>
<UserSelector
isNewUser={userFormIsNewUser}
smtpSettingsPresent={smtpSettingsPresent}
elasticsearchUsers={elasticsearchUsers}
handleRoleChange={handleRoleChange}
elasticsearchUser={elasticsearchUser}
Expand All @@ -94,13 +103,15 @@ export const User: React.FC = () => {
return (
<UserFlyout
disabled={flyoutDisabled}
formLoading={formLoading}
isComplete={userCreated}
isNew={userFormIsNewUser}
closeUserFlyout={closeUsersAndRolesFlyout}
handleSaveUser={handleSaveUser}
>
{userCreated ? userAddedInfo : createUserForm}
{userInvitationCallout}
{userIsDeactivated && <DeactivatedUserCallout isNew={userFormIsNewUser} />}
</UserFlyout>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ export const elasticsearchUsers = [
{
email: '[email protected]',
username: 'user1',
enabled: true,
},
];
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -166,5 +166,12 @@ describe('AttributeSelector', () => {
baseProps.elasticsearchRoles[0]
);
});

it('shows helpText when attributeValueInvalid', () => {
const wrapper = shallow(<AttributeSelector {...baseProps} attributeValueInvalid />);
const formRow = wrapper.find(EuiFormRow).at(2);

expect(formRow.prop('helpText')).toEqual(REQUIRED_LABEL);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -129,8 +129,7 @@ export const AttributeSelector: React.FC<Props> = ({
<EuiFormRow
label={ATTRIBUTE_VALUE_LABEL}
fullWidth
isInvalid={attributeValueInvalid}
error={[ATTRIBUTE_VALUE_ERROR]}
helpText={attributeValueInvalid && REQUIRED_LABEL}
>
{attributeName === 'role' ? (
<EuiSelect
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,6 @@ export const ATTRIBUTE_VALUE_LABEL = i18n.translate(
}
);

export const ATTRIBUTE_VALUE_ERROR = i18n.translate(
'xpack.enterpriseSearch.roleMapping.attributeValueError',
{
defaultMessage: 'Attribute value is required',
}
);

export const REMOVE_ROLE_MAPPING_TITLE = i18n.translate(
'xpack.enterpriseSearch.roleMapping.removeRoleMappingTitle',
{
Expand Down Expand Up @@ -373,6 +366,13 @@ export const UPDATE_USER_DESCRIPTION = i18n.translate(
}
);

export const DEACTIVATED_LABEL = i18n.translate(
'xpack.enterpriseSearch.roleMapping.deactivatedLabel',
{
defaultMessage: 'Deactivated',
}
);

export const INVITATION_PENDING_LABEL = i18n.translate(
'xpack.enterpriseSearch.roleMapping.invitationPendingLabel',
{
Expand Down Expand Up @@ -422,3 +422,29 @@ export const AUTH_PROVIDER_TOOLTIP = i18n.translate(
'Provider-specific role mapping is still applied, but configuration is now deprecated.',
}
);

export const DEACTIVATED_USER_CALLOUT_LABEL = i18n.translate(
'xpack.enterpriseSearch.roleMapping.deactivatedUserCalloutLabel',
{
defaultMessage: 'User deactivated',
}
);

export const DEACTIVATED_USER_CALLOUT_DESCRIPTION = i18n.translate(
'xpack.enterpriseSearch.roleMapping.deactivatedUserCalloutDescription',
{
defaultMessage:
'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.',
}
);

export const SMTP_CALLOUT_LABEL = i18n.translate(
'xpack.enterpriseSearch.roleMapping.smtpCalloutLabel',
{
defaultMessage: 'Personalized invitations will be automatically sent when an Enterprise Search',
}
);

export const SMTP_LINK_LABEL = i18n.translate('xpack.enterpriseSearch.roleMapping.smtpLinkLabel', {
defaultMessage: 'SMTP configuration is provided',
});
Loading