regression: show roles on user info view (#5154)
* create usersRoles reducer * add usersRoles test * fix usersRoles reducer * fetchUsersRoles on login * use new roles * add test * fix roles when the user has permission to see other user roles * use role name --------- Co-authored-by: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com>
This commit is contained in:
parent
cbbea73374
commit
b23df26d42
|
@ -85,6 +85,7 @@ export const ENCRYPTION = createRequestTypes('ENCRYPTION', ['INIT', 'STOP', 'DEC
|
|||
|
||||
export const PERMISSIONS = createRequestTypes('PERMISSIONS', ['SET', 'UPDATE']);
|
||||
export const ROLES = createRequestTypes('ROLES', ['SET', 'UPDATE', 'REMOVE']);
|
||||
export const USERS_ROLES = createRequestTypes('USERS_ROLES', ['SET']);
|
||||
export const VIDEO_CONF = createRequestTypes('VIDEO_CONF', [
|
||||
'HANDLE_INCOMING_WEBSOCKET_MESSAGES',
|
||||
'SET',
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import { Action } from 'redux';
|
||||
|
||||
import { TUsersRoles } from '../reducers/usersRoles';
|
||||
import { USERS_ROLES } from './actionsTypes';
|
||||
|
||||
export type TActionUsersRoles = Action & { usersRoles: TUsersRoles };
|
||||
|
||||
export function setUsersRoles(usersRoles: TUsersRoles): Action & { usersRoles: TUsersRoles } {
|
||||
return {
|
||||
type: USERS_ROLES.SET,
|
||||
usersRoles
|
||||
};
|
||||
}
|
|
@ -36,6 +36,8 @@ import { IInquiry } from '../../ee/omnichannel/reducers/inquiry';
|
|||
import { IPermissionsState } from '../../reducers/permissions';
|
||||
import { IEnterpriseModules } from '../../reducers/enterpriseModules';
|
||||
import { IVideoConf } from '../../reducers/videoConf';
|
||||
import { TActionUsersRoles } from '../../actions/usersRoles';
|
||||
import { TUsersRoles } from '../../reducers/usersRoles';
|
||||
|
||||
export interface IApplicationState {
|
||||
settings: TSettingsState;
|
||||
|
@ -60,6 +62,7 @@ export interface IApplicationState {
|
|||
permissions: IPermissionsState;
|
||||
roles: IRoles;
|
||||
videoConf: IVideoConf;
|
||||
usersRoles: TUsersRoles;
|
||||
}
|
||||
|
||||
export type TApplicationActions = TActionActiveUsers &
|
||||
|
@ -79,4 +82,5 @@ export type TApplicationActions = TActionActiveUsers &
|
|||
TActionInquiry &
|
||||
TActionPermissions &
|
||||
TActionEnterpriseModules &
|
||||
TActionVideoConf;
|
||||
TActionVideoConf &
|
||||
TActionUsersRoles;
|
||||
|
|
|
@ -14,7 +14,7 @@ export async function setRoles(): Promise<void> {
|
|||
const db = database.active;
|
||||
const rolesCollection = db.get('roles');
|
||||
const allRoles = await rolesCollection.query().fetch();
|
||||
const parsed = allRoles.reduce((acc, item) => ({ ...acc, [item.id]: item.description || item.id }), {});
|
||||
const parsed = allRoles.reduce((acc, item) => ({ ...acc, [item.id]: item.description || item.name || item.id }), {});
|
||||
reduxStore.dispatch(setRolesAction(parsed));
|
||||
}
|
||||
|
||||
|
|
|
@ -973,3 +973,5 @@ export const postMessage = (roomId: string, text: string) => sdk.post('chat.post
|
|||
|
||||
export const notifyUser = (type: string, params: Record<string, any>): Promise<boolean> =>
|
||||
sdk.methodCall('stream-notify-user', type, params);
|
||||
|
||||
export const getUsersRoles = (): Promise<boolean> => sdk.methodCall('getUserRoles');
|
||||
|
|
|
@ -22,6 +22,7 @@ import encryption from './encryption';
|
|||
import permissions from './permissions';
|
||||
import roles from './roles';
|
||||
import videoConf from './videoConf';
|
||||
import usersRoles from './usersRoles';
|
||||
|
||||
export default combineReducers({
|
||||
settings,
|
||||
|
@ -45,5 +46,6 @@ export default combineReducers({
|
|||
encryption,
|
||||
permissions,
|
||||
roles,
|
||||
videoConf
|
||||
videoConf,
|
||||
usersRoles
|
||||
});
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import { setUsersRoles } from '../actions/usersRoles';
|
||||
import { mockedStore } from './mockedStore';
|
||||
import { TUsersRoles, initialState } from './usersRoles';
|
||||
|
||||
describe('test userRoles reducer', () => {
|
||||
it('should return initial state', () => {
|
||||
const state = mockedStore.getState().usersRoles;
|
||||
expect(state).toEqual(initialState);
|
||||
});
|
||||
|
||||
it('should return correctly value after call setUserRoles action', () => {
|
||||
const usersRoles: TUsersRoles = [{ _id: '1', roles: ['admin'], username: 'admin' }];
|
||||
mockedStore.dispatch(setUsersRoles(usersRoles));
|
||||
const state = mockedStore.getState().usersRoles;
|
||||
expect(state).toEqual(usersRoles);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,21 @@
|
|||
import { USERS_ROLES } from '../actions/actionsTypes';
|
||||
import { TApplicationActions } from '../definitions';
|
||||
|
||||
type TUserRole = {
|
||||
_id: string;
|
||||
roles: string[];
|
||||
username: string;
|
||||
};
|
||||
|
||||
export type TUsersRoles = TUserRole[];
|
||||
|
||||
export const initialState: TUsersRoles = [];
|
||||
|
||||
export default (state = initialState, action: TApplicationActions): TUsersRoles => {
|
||||
switch (action.type) {
|
||||
case USERS_ROLES.SET:
|
||||
return action.usersRoles;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
|
@ -36,6 +36,7 @@ import {
|
|||
subscribeUsersPresence
|
||||
} from '../lib/methods';
|
||||
import { Services } from '../lib/services';
|
||||
import { setUsersRoles } from '../actions/usersRoles';
|
||||
|
||||
const getServer = state => state.server.server;
|
||||
const loginWithPasswordCall = args => Services.loginWithPassword(args);
|
||||
|
@ -141,6 +142,13 @@ const fetchRoomsFork = function* fetchRoomsFork() {
|
|||
yield put(roomsRequest());
|
||||
};
|
||||
|
||||
const fetchUsersRoles = function* fetchRoomsFork() {
|
||||
const roles = yield Services.getUsersRoles();
|
||||
if (roles.length) {
|
||||
yield put(setUsersRoles(roles));
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoginSuccess = function* handleLoginSuccess({ user }) {
|
||||
try {
|
||||
getUserPresence(user.id);
|
||||
|
@ -156,6 +164,7 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
|
|||
yield fork(fetchEnterpriseModulesFork, { user });
|
||||
yield fork(subscribeSettingsFork);
|
||||
yield put(encryptionInit());
|
||||
yield fork(fetchUsersRoles);
|
||||
|
||||
setLanguage(user?.language);
|
||||
|
||||
|
|
|
@ -1,36 +1,43 @@
|
|||
import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
|
||||
import { themes } from '../../lib/constants';
|
||||
import { IUserParsed } from '.';
|
||||
import I18n from '../../i18n';
|
||||
import { useTheme } from '../../theme';
|
||||
import Timezone from './Timezone';
|
||||
import CustomFields from './CustomFields';
|
||||
import Timezone from './Timezone';
|
||||
import styles from './styles';
|
||||
import { IUserParsed } from '.';
|
||||
|
||||
const Roles = ({ roles }: { roles?: string[] }) => {
|
||||
const { theme } = useTheme();
|
||||
const { colors } = useTheme();
|
||||
|
||||
if (roles && roles.length) {
|
||||
<View style={styles.item}>
|
||||
<Text style={[styles.itemLabel, { color: themes[theme].titleText }]}>{I18n.t('Roles')}</Text>
|
||||
<View style={styles.rolesContainer}>
|
||||
{roles.map(role =>
|
||||
role ? (
|
||||
<View style={[styles.roleBadge, { backgroundColor: themes[theme].chatComponentBackground }]} key={role}>
|
||||
<Text style={[styles.role, { color: themes[theme].titleText }]}>{role}</Text>
|
||||
</View>
|
||||
) : null
|
||||
)}
|
||||
if (roles?.length) {
|
||||
return (
|
||||
<View style={styles.item}>
|
||||
<Text testID='user-roles' style={[styles.itemLabel, { color: colors.titleText }]}>
|
||||
{I18n.t('Roles')}
|
||||
</Text>
|
||||
<View style={styles.rolesContainer}>
|
||||
{roles.map(role =>
|
||||
role ? (
|
||||
<View
|
||||
testID={`user-role-${role.replace(/ /g, '-')}`}
|
||||
style={[styles.roleBadge, { backgroundColor: colors.chatComponentBackground }]}
|
||||
key={role}
|
||||
>
|
||||
<Text style={[styles.role, { color: colors.titleText }]}>{role}</Text>
|
||||
</View>
|
||||
) : null
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>;
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const Direct = ({ roomUser }: { roomUser: IUserParsed }) => (
|
||||
const Direct = ({ roomUser }: { roomUser: IUserParsed }): React.ReactElement => (
|
||||
<>
|
||||
<Roles roles={roomUser.parsedRoles} />
|
||||
<Timezone utcOffset={roomUser.utcOffset} />
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { CompositeNavigationProp, RouteProp } from '@react-navigation/native';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import { uniq } from 'lodash';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import React from 'react';
|
||||
import { ScrollView, Text, View } from 'react-native';
|
||||
|
@ -11,12 +12,12 @@ import UAParser from 'ua-parser-js';
|
|||
import { AvatarWithEdit } from '../../containers/Avatar';
|
||||
import { CustomIcon, TIconsName } from '../../containers/CustomIcon';
|
||||
import * as HeaderButton from '../../containers/HeaderButton';
|
||||
import { MarkdownPreview } from '../../containers/markdown';
|
||||
import RoomTypeIcon from '../../containers/RoomTypeIcon';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
import Status from '../../containers/Status';
|
||||
import StatusBar from '../../containers/StatusBar';
|
||||
import { LISTENER } from '../../containers/Toast';
|
||||
import { MarkdownPreview } from '../../containers/markdown';
|
||||
import { IApplicationState, ISubscription, IUser, SubscriptionType, TSubscriptionModel } from '../../definitions';
|
||||
import { ILivechatVisitor } from '../../definitions/ILivechatVisitor';
|
||||
import I18n from '../../i18n';
|
||||
|
@ -29,14 +30,15 @@ import { handleIgnore } from '../../lib/methods/helpers/handleIgnore';
|
|||
import log, { events, logEvent } from '../../lib/methods/helpers/log';
|
||||
import Navigation from '../../lib/navigation/appNavigation';
|
||||
import { Services } from '../../lib/services';
|
||||
import { TUsersRoles } from '../../reducers/usersRoles';
|
||||
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
|
||||
import { ChatsStackParamList } from '../../stacks/types';
|
||||
import { TSupportedThemes, withTheme } from '../../theme';
|
||||
import sharedStyles from '../Styles';
|
||||
import Channel from './Channel';
|
||||
import { CallButton } from './components/UserInfoButton';
|
||||
import Direct from './Direct';
|
||||
import Livechat from './Livechat';
|
||||
import { CallButton } from './components/UserInfoButton';
|
||||
import styles from './styles';
|
||||
|
||||
interface IGetRoomTitle {
|
||||
|
@ -95,6 +97,7 @@ interface IRoomInfoViewProps {
|
|||
editOmnichannelContact?: string[];
|
||||
editLivechatRoomCustomfields?: string[];
|
||||
roles: { [key: string]: string };
|
||||
usersRoles: TUsersRoles;
|
||||
}
|
||||
|
||||
export interface IUserParsed extends IUser {
|
||||
|
@ -245,6 +248,23 @@ class RoomInfoView extends React.Component<IRoomInfoViewProps, IRoomInfoViewStat
|
|||
})
|
||||
);
|
||||
|
||||
setUser = async (user: IUser) => {
|
||||
const roles = (() => {
|
||||
const { usersRoles } = this.props;
|
||||
const userRoles = usersRoles.find(u => u?.username === user.username);
|
||||
let r: string[] = [];
|
||||
if (userRoles?.roles?.length) r = userRoles.roles;
|
||||
if (user.roles?.length) r = [...r, ...user.roles];
|
||||
return uniq(r);
|
||||
})();
|
||||
if (roles.length) {
|
||||
const parsedRoles = await this.parseRoles(roles);
|
||||
this.setState({ roomUser: { ...user, parsedRoles } });
|
||||
} else {
|
||||
this.setState({ roomUser: user });
|
||||
}
|
||||
};
|
||||
|
||||
loadUser = async () => {
|
||||
const { room, roomUser } = this.state;
|
||||
|
||||
|
@ -254,29 +274,13 @@ class RoomInfoView extends React.Component<IRoomInfoViewProps, IRoomInfoViewStat
|
|||
const result = await Services.getUserInfo(roomUserId);
|
||||
if (result.success) {
|
||||
const { user } = result;
|
||||
const { roles } = user;
|
||||
const parsedRoles: { parsedRoles?: string[] } = {};
|
||||
if (roles && roles.length) {
|
||||
parsedRoles.parsedRoles = await this.parseRoles(roles);
|
||||
}
|
||||
|
||||
this.setState({ roomUser: { ...user, ...parsedRoles } as IUserParsed });
|
||||
this.setUser(user as IUser);
|
||||
}
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
const { roles } = roomUser as IUserParsed;
|
||||
if (roles && roles.length) {
|
||||
const parsedRoles = await this.parseRoles(roles);
|
||||
this.setState({ roomUser: { ...roomUser, parsedRoles } });
|
||||
} else {
|
||||
this.setState({ roomUser });
|
||||
}
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
this.setUser(roomUser as IUser);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -523,7 +527,8 @@ const mapStateToProps = (state: IApplicationState) => ({
|
|||
editRoomPermission: state.permissions['edit-room'],
|
||||
editOmnichannelContact: state.permissions['edit-omnichannel-contact'],
|
||||
editLivechatRoomCustomfields: state.permissions['edit-livechat-room-customfields'],
|
||||
roles: state.roles
|
||||
roles: state.roles,
|
||||
usersRoles: state.usersRoles
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(withTheme(RoomInfoView));
|
||||
|
|
|
@ -256,5 +256,17 @@ describe('Room info screen', () => {
|
|||
.withTimeout(60000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Navigate to random user', () => {
|
||||
it('should see user role correctly', async () => {
|
||||
await navigateToRoomInfo('roles-test');
|
||||
await waitFor(element(by.id(`user-roles`)))
|
||||
.toBeVisible()
|
||||
.withTimeout(10000);
|
||||
await waitFor(element(by.id(`user-role-Livechat-Agent`)))
|
||||
.toBeVisible()
|
||||
.withTimeout(10000);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue