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

[7.x] [Enterprise Search] Add notices for deactivated users and SMTP callout (#103285) #103376

Merged
merged 1 commit into from
Jun 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
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: 'user1@user.com',
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