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 PERMISSIONS = createRequestTypes('PERMISSIONS', ['SET', 'UPDATE']);
|
||||||
export const ROLES = createRequestTypes('ROLES', ['SET', 'UPDATE', 'REMOVE']);
|
export const ROLES = createRequestTypes('ROLES', ['SET', 'UPDATE', 'REMOVE']);
|
||||||
|
export const USERS_ROLES = createRequestTypes('USERS_ROLES', ['SET']);
|
||||||
export const VIDEO_CONF = createRequestTypes('VIDEO_CONF', [
|
export const VIDEO_CONF = createRequestTypes('VIDEO_CONF', [
|
||||||
'HANDLE_INCOMING_WEBSOCKET_MESSAGES',
|
'HANDLE_INCOMING_WEBSOCKET_MESSAGES',
|
||||||
'SET',
|
'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 { IPermissionsState } from '../../reducers/permissions';
|
||||||
import { IEnterpriseModules } from '../../reducers/enterpriseModules';
|
import { IEnterpriseModules } from '../../reducers/enterpriseModules';
|
||||||
import { IVideoConf } from '../../reducers/videoConf';
|
import { IVideoConf } from '../../reducers/videoConf';
|
||||||
|
import { TActionUsersRoles } from '../../actions/usersRoles';
|
||||||
|
import { TUsersRoles } from '../../reducers/usersRoles';
|
||||||
|
|
||||||
export interface IApplicationState {
|
export interface IApplicationState {
|
||||||
settings: TSettingsState;
|
settings: TSettingsState;
|
||||||
|
@ -60,6 +62,7 @@ export interface IApplicationState {
|
||||||
permissions: IPermissionsState;
|
permissions: IPermissionsState;
|
||||||
roles: IRoles;
|
roles: IRoles;
|
||||||
videoConf: IVideoConf;
|
videoConf: IVideoConf;
|
||||||
|
usersRoles: TUsersRoles;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TApplicationActions = TActionActiveUsers &
|
export type TApplicationActions = TActionActiveUsers &
|
||||||
|
@ -79,4 +82,5 @@ export type TApplicationActions = TActionActiveUsers &
|
||||||
TActionInquiry &
|
TActionInquiry &
|
||||||
TActionPermissions &
|
TActionPermissions &
|
||||||
TActionEnterpriseModules &
|
TActionEnterpriseModules &
|
||||||
TActionVideoConf;
|
TActionVideoConf &
|
||||||
|
TActionUsersRoles;
|
||||||
|
|
|
@ -14,7 +14,7 @@ export async function setRoles(): Promise<void> {
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
const rolesCollection = db.get('roles');
|
const rolesCollection = db.get('roles');
|
||||||
const allRoles = await rolesCollection.query().fetch();
|
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));
|
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> =>
|
export const notifyUser = (type: string, params: Record<string, any>): Promise<boolean> =>
|
||||||
sdk.methodCall('stream-notify-user', type, params);
|
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 permissions from './permissions';
|
||||||
import roles from './roles';
|
import roles from './roles';
|
||||||
import videoConf from './videoConf';
|
import videoConf from './videoConf';
|
||||||
|
import usersRoles from './usersRoles';
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
settings,
|
settings,
|
||||||
|
@ -45,5 +46,6 @@ export default combineReducers({
|
||||||
encryption,
|
encryption,
|
||||||
permissions,
|
permissions,
|
||||||
roles,
|
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
|
subscribeUsersPresence
|
||||||
} from '../lib/methods';
|
} from '../lib/methods';
|
||||||
import { Services } from '../lib/services';
|
import { Services } from '../lib/services';
|
||||||
|
import { setUsersRoles } from '../actions/usersRoles';
|
||||||
|
|
||||||
const getServer = state => state.server.server;
|
const getServer = state => state.server.server;
|
||||||
const loginWithPasswordCall = args => Services.loginWithPassword(args);
|
const loginWithPasswordCall = args => Services.loginWithPassword(args);
|
||||||
|
@ -141,6 +142,13 @@ const fetchRoomsFork = function* fetchRoomsFork() {
|
||||||
yield put(roomsRequest());
|
yield put(roomsRequest());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchUsersRoles = function* fetchRoomsFork() {
|
||||||
|
const roles = yield Services.getUsersRoles();
|
||||||
|
if (roles.length) {
|
||||||
|
yield put(setUsersRoles(roles));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleLoginSuccess = function* handleLoginSuccess({ user }) {
|
const handleLoginSuccess = function* handleLoginSuccess({ user }) {
|
||||||
try {
|
try {
|
||||||
getUserPresence(user.id);
|
getUserPresence(user.id);
|
||||||
|
@ -156,6 +164,7 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
|
||||||
yield fork(fetchEnterpriseModulesFork, { user });
|
yield fork(fetchEnterpriseModulesFork, { user });
|
||||||
yield fork(subscribeSettingsFork);
|
yield fork(subscribeSettingsFork);
|
||||||
yield put(encryptionInit());
|
yield put(encryptionInit());
|
||||||
|
yield fork(fetchUsersRoles);
|
||||||
|
|
||||||
setLanguage(user?.language);
|
setLanguage(user?.language);
|
||||||
|
|
||||||
|
|
|
@ -1,36 +1,43 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Text, View } from 'react-native';
|
import { Text, View } from 'react-native';
|
||||||
|
|
||||||
import { themes } from '../../lib/constants';
|
import { IUserParsed } from '.';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { useTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
import Timezone from './Timezone';
|
|
||||||
import CustomFields from './CustomFields';
|
import CustomFields from './CustomFields';
|
||||||
|
import Timezone from './Timezone';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { IUserParsed } from '.';
|
|
||||||
|
|
||||||
const Roles = ({ roles }: { roles?: string[] }) => {
|
const Roles = ({ roles }: { roles?: string[] }) => {
|
||||||
const { theme } = useTheme();
|
const { colors } = useTheme();
|
||||||
|
|
||||||
if (roles && roles.length) {
|
if (roles?.length) {
|
||||||
|
return (
|
||||||
<View style={styles.item}>
|
<View style={styles.item}>
|
||||||
<Text style={[styles.itemLabel, { color: themes[theme].titleText }]}>{I18n.t('Roles')}</Text>
|
<Text testID='user-roles' style={[styles.itemLabel, { color: colors.titleText }]}>
|
||||||
|
{I18n.t('Roles')}
|
||||||
|
</Text>
|
||||||
<View style={styles.rolesContainer}>
|
<View style={styles.rolesContainer}>
|
||||||
{roles.map(role =>
|
{roles.map(role =>
|
||||||
role ? (
|
role ? (
|
||||||
<View style={[styles.roleBadge, { backgroundColor: themes[theme].chatComponentBackground }]} key={role}>
|
<View
|
||||||
<Text style={[styles.role, { color: themes[theme].titleText }]}>{role}</Text>
|
testID={`user-role-${role.replace(/ /g, '-')}`}
|
||||||
|
style={[styles.roleBadge, { backgroundColor: colors.chatComponentBackground }]}
|
||||||
|
key={role}
|
||||||
|
>
|
||||||
|
<Text style={[styles.role, { color: colors.titleText }]}>{role}</Text>
|
||||||
</View>
|
</View>
|
||||||
) : null
|
) : null
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</View>;
|
</View>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Direct = ({ roomUser }: { roomUser: IUserParsed }) => (
|
const Direct = ({ roomUser }: { roomUser: IUserParsed }): React.ReactElement => (
|
||||||
<>
|
<>
|
||||||
<Roles roles={roomUser.parsedRoles} />
|
<Roles roles={roomUser.parsedRoles} />
|
||||||
<Timezone utcOffset={roomUser.utcOffset} />
|
<Timezone utcOffset={roomUser.utcOffset} />
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { CompositeNavigationProp, RouteProp } from '@react-navigation/native';
|
import { CompositeNavigationProp, RouteProp } from '@react-navigation/native';
|
||||||
import { StackNavigationProp } from '@react-navigation/stack';
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
|
import { uniq } from 'lodash';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ScrollView, Text, View } from 'react-native';
|
import { ScrollView, Text, View } from 'react-native';
|
||||||
|
@ -11,12 +12,12 @@ import UAParser from 'ua-parser-js';
|
||||||
import { AvatarWithEdit } from '../../containers/Avatar';
|
import { AvatarWithEdit } from '../../containers/Avatar';
|
||||||
import { CustomIcon, TIconsName } from '../../containers/CustomIcon';
|
import { CustomIcon, TIconsName } from '../../containers/CustomIcon';
|
||||||
import * as HeaderButton from '../../containers/HeaderButton';
|
import * as HeaderButton from '../../containers/HeaderButton';
|
||||||
import { MarkdownPreview } from '../../containers/markdown';
|
|
||||||
import RoomTypeIcon from '../../containers/RoomTypeIcon';
|
import RoomTypeIcon from '../../containers/RoomTypeIcon';
|
||||||
import SafeAreaView from '../../containers/SafeAreaView';
|
import SafeAreaView from '../../containers/SafeAreaView';
|
||||||
import Status from '../../containers/Status';
|
import Status from '../../containers/Status';
|
||||||
import StatusBar from '../../containers/StatusBar';
|
import StatusBar from '../../containers/StatusBar';
|
||||||
import { LISTENER } from '../../containers/Toast';
|
import { LISTENER } from '../../containers/Toast';
|
||||||
|
import { MarkdownPreview } from '../../containers/markdown';
|
||||||
import { IApplicationState, ISubscription, IUser, SubscriptionType, TSubscriptionModel } from '../../definitions';
|
import { IApplicationState, ISubscription, IUser, SubscriptionType, TSubscriptionModel } from '../../definitions';
|
||||||
import { ILivechatVisitor } from '../../definitions/ILivechatVisitor';
|
import { ILivechatVisitor } from '../../definitions/ILivechatVisitor';
|
||||||
import I18n from '../../i18n';
|
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 log, { events, logEvent } from '../../lib/methods/helpers/log';
|
||||||
import Navigation from '../../lib/navigation/appNavigation';
|
import Navigation from '../../lib/navigation/appNavigation';
|
||||||
import { Services } from '../../lib/services';
|
import { Services } from '../../lib/services';
|
||||||
|
import { TUsersRoles } from '../../reducers/usersRoles';
|
||||||
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
|
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
|
||||||
import { ChatsStackParamList } from '../../stacks/types';
|
import { ChatsStackParamList } from '../../stacks/types';
|
||||||
import { TSupportedThemes, withTheme } from '../../theme';
|
import { TSupportedThemes, withTheme } from '../../theme';
|
||||||
import sharedStyles from '../Styles';
|
import sharedStyles from '../Styles';
|
||||||
import Channel from './Channel';
|
import Channel from './Channel';
|
||||||
import { CallButton } from './components/UserInfoButton';
|
|
||||||
import Direct from './Direct';
|
import Direct from './Direct';
|
||||||
import Livechat from './Livechat';
|
import Livechat from './Livechat';
|
||||||
|
import { CallButton } from './components/UserInfoButton';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
interface IGetRoomTitle {
|
interface IGetRoomTitle {
|
||||||
|
@ -95,6 +97,7 @@ interface IRoomInfoViewProps {
|
||||||
editOmnichannelContact?: string[];
|
editOmnichannelContact?: string[];
|
||||||
editLivechatRoomCustomfields?: string[];
|
editLivechatRoomCustomfields?: string[];
|
||||||
roles: { [key: string]: string };
|
roles: { [key: string]: string };
|
||||||
|
usersRoles: TUsersRoles;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IUserParsed extends IUser {
|
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 () => {
|
loadUser = async () => {
|
||||||
const { room, roomUser } = this.state;
|
const { room, roomUser } = this.state;
|
||||||
|
|
||||||
|
@ -254,29 +274,13 @@ class RoomInfoView extends React.Component<IRoomInfoViewProps, IRoomInfoViewStat
|
||||||
const result = await Services.getUserInfo(roomUserId);
|
const result = await Services.getUserInfo(roomUserId);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
const { user } = result;
|
const { user } = result;
|
||||||
const { roles } = user;
|
this.setUser(user as IUser);
|
||||||
const parsedRoles: { parsedRoles?: string[] } = {};
|
|
||||||
if (roles && roles.length) {
|
|
||||||
parsedRoles.parsedRoles = await this.parseRoles(roles);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ roomUser: { ...user, ...parsedRoles } as IUserParsed });
|
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
this.setUser(roomUser as IUser);
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -523,7 +527,8 @@ const mapStateToProps = (state: IApplicationState) => ({
|
||||||
editRoomPermission: state.permissions['edit-room'],
|
editRoomPermission: state.permissions['edit-room'],
|
||||||
editOmnichannelContact: state.permissions['edit-omnichannel-contact'],
|
editOmnichannelContact: state.permissions['edit-omnichannel-contact'],
|
||||||
editLivechatRoomCustomfields: state.permissions['edit-livechat-room-customfields'],
|
editLivechatRoomCustomfields: state.permissions['edit-livechat-room-customfields'],
|
||||||
roles: state.roles
|
roles: state.roles,
|
||||||
|
usersRoles: state.usersRoles
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps)(withTheme(RoomInfoView));
|
export default connect(mapStateToProps)(withTheme(RoomInfoView));
|
||||||
|
|
|
@ -256,5 +256,17 @@ describe('Room info screen', () => {
|
||||||
.withTimeout(60000);
|
.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