Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
1 change: 1 addition & 0 deletions app/actions/actionsTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
13 changes: 13 additions & 0 deletions app/actions/usersRoles.ts
Original file line number Diff line number Diff line change
@@ -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
};
}
6 changes: 5 additions & 1 deletion app/definitions/redux/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -60,6 +62,7 @@ export interface IApplicationState {
permissions: IPermissionsState;
roles: IRoles;
videoConf: IVideoConf;
usersRoles: TUsersRoles;
}

export type TApplicationActions = TActionActiveUsers &
Expand All @@ -79,4 +82,5 @@ export type TApplicationActions = TActionActiveUsers &
TActionInquiry &
TActionPermissions &
TActionEnterpriseModules &
TActionVideoConf;
TActionVideoConf &
TActionUsersRoles;
2 changes: 2 additions & 0 deletions app/lib/services/restApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
4 changes: 3 additions & 1 deletion app/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -45,5 +46,6 @@ export default combineReducers({
encryption,
permissions,
roles,
videoConf
videoConf,
usersRoles
});
17 changes: 17 additions & 0 deletions app/reducers/usersRoles.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
21 changes: 21 additions & 0 deletions app/reducers/usersRoles.ts
Original file line number Diff line number Diff line change
@@ -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;
}
};
9 changes: 9 additions & 0 deletions app/sagas/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);

Expand Down
41 changes: 24 additions & 17 deletions app/views/RoomInfoView/Direct.tsx
Original file line number Diff line number Diff line change
@@ -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} />
Expand Down
47 changes: 26 additions & 21 deletions app/views/RoomInfoView/index.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';
Expand All @@ -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 {
Expand Down Expand Up @@ -95,6 +97,7 @@ interface IRoomInfoViewProps {
editOmnichannelContact?: string[];
editLivechatRoomCustomfields?: string[];
roles: { [key: string]: string };
usersRoles: TUsersRoles;
}

export interface IUserParsed extends IUser {
Expand Down Expand Up @@ -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;

Expand All @@ -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);
}
};

Expand Down Expand Up @@ -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));
12 changes: 12 additions & 0 deletions e2e/tests/room/08-roominfo.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
});
});