Skip to content

Commit

Permalink
fix: automatically map user roles when team user list changes [WPB-19…
Browse files Browse the repository at this point in the history
…12] (#16166)
  • Loading branch information
atomrc committed Nov 8, 2023
1 parent dfa6dd7 commit 09475d2
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 79 deletions.
2 changes: 1 addition & 1 deletion src/script/main/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ export class App {
onProgress(10);
telemetry.timeStep(AppInitTimingsStep.INITIALIZED_CRYPTOGRAPHY);

const {members: teamMembers} = await teamRepository.initTeam(selfUser.teamId);
const teamMembers = await teamRepository.initTeam(selfUser.teamId);
telemetry.timeStep(AppInitTimingsStep.RECEIVED_USER_DATA);

const connections = await connectionRepository.getConnections();
Expand Down
5 changes: 1 addition & 4 deletions src/script/team/TeamEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import ko from 'knockout';

import {AssetRemoteData} from '../assets/AssetRemoteData';
import type {User} from '../entity/User';
import {assetV3} from '../util/ValidationUtil';

export class TeamEntity {
Expand All @@ -30,14 +29,12 @@ export class TeamEntity {
/** Team icon (asset key) */
iconKey?: string;
id?: string;
members: ko.ObservableArray<User>;
name: ko.Observable<string>;

constructor(id?: string) {
this.creator = undefined;
this.icon = '';
this.iconKey = undefined;
this.members = ko.observableArray<User>([]);
this.id = id;
this.name = ko.observable('');
}
Expand All @@ -46,7 +43,7 @@ export class TeamEntity {
let hasIcon = false;

try {
hasIcon = this.icon && assetV3(this.icon);
hasIcon = !!this.icon && assetV3(this.icon);
} catch (error) {}

if (hasIcon) {
Expand Down
11 changes: 0 additions & 11 deletions src/script/team/TeamMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,10 @@

import type {MemberData, TeamData} from '@wireapp/api-client/lib/team/';
import type {TeamUpdateData} from '@wireapp/api-client/lib/team/data/';
import type {PermissionsData} from '@wireapp/api-client/lib/team/member/PermissionsData';

import {TeamEntity} from './TeamEntity';
import {TeamMemberEntity} from './TeamMemberEntity';

import type {User} from '../entity/User';
import {roleFromTeamPermissions} from '../user/UserPermission';

export class TeamMapper {
mapTeamFromObject(data: TeamData, teamEntity?: TeamEntity): TeamEntity {
return this.updateTeamFromObject(data, teamEntity);
Expand Down Expand Up @@ -81,11 +77,4 @@ export class TeamMapper {

return member;
}

mapRole(userEntity: User, permissions?: PermissionsData): void {
if (permissions) {
const teamRole = roleFromTeamPermissions(permissions);
userEntity.teamRole(teamRole);
}
}
}
101 changes: 42 additions & 59 deletions src/script/team/TeamRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import type {
} from '@wireapp/api-client/lib/event';
import {TEAM_EVENT} from '@wireapp/api-client/lib/event/TeamEvent';
import {FeatureStatus, FeatureList} from '@wireapp/api-client/lib/team/feature/';
import type {PermissionsData} from '@wireapp/api-client/lib/team/member/PermissionsData';
import type {TeamData} from '@wireapp/api-client/lib/team/team/TeamData';
import {QualifiedId} from '@wireapp/api-client/lib/user';
import {amplify} from 'amplify';
Expand Down Expand Up @@ -102,44 +103,44 @@ export class TeamRepository extends TypedEventEmitter<Events> {
this.userRepository = userRepository;

this.userRepository.getTeamMembersFromUsers = this.getTeamMembersFromUsers;
this.teamState.teamMembers.subscribe(() => this.userRepository.mapGuestStatus());

this.isSelfConnectedTo = userId => {
return (
this.teamState.memberRoles()[userId] !== ROLE.PARTNER ||
this.teamState.memberInviters()[userId] === this.userState.self().id
);
};

amplify.subscribe(WebAppEvents.TEAM.EVENT_FROM_BACKEND, this.onTeamEvent);
amplify.subscribe(WebAppEvents.EVENT.NOTIFICATION_HANDLING_STATE, this.updateTeamConfig);
amplify.subscribe(WebAppEvents.TEAM.UPDATE_INFO, this.sendAccountInfo.bind(this));
}

readonly getRoleBadge = (userId: string): string => {
getRoleBadge(userId: string): string {
return this.teamState.isExternal(userId) ? t('rolePartner') : '';
};
}

readonly isSelfConnectedTo = (userId: string): boolean => {
isSelfConnectedTo(userId: string): boolean {
return (
this.teamState.memberRoles()[userId] !== ROLE.PARTNER ||
this.teamState.memberInviters()[userId] === this.userState.self().id
);
};
}

initTeam = async (
teamId?: string,
): Promise<{team: TeamEntity; members: QualifiedId[]} | {team: undefined; members: never[]}> => {
async initTeam(teamId?: string): Promise<QualifiedId[]> {
const team = await this.getTeam();
// get the fresh feature config from backend
await this.updateFeatureConfig();
if (!teamId) {
return {team: undefined, members: []};
return [];
}
this.teamState.teamMembers.subscribe(members => {
// Subscribe to team members change and update the user role and guest status
this.userRepository.mapGuestStatus(members);
const roles = this.teamState.memberRoles();
members.forEach(user => {
if (roles[user.id]) {
user.teamRole(roles[user.id]);
}
});
});
const members = await this.loadTeamMembers(team);
this.scheduleTeamRefresh();
return {team, members};
};
return members;
}

private async updateFeatureConfig(): Promise<{newFeatureList: FeatureList; prevFeatureList?: FeatureList}> {
const prevFeatureList = this.teamState.teamFeatures();
Expand Down Expand Up @@ -185,7 +186,7 @@ export class TeamRepository extends TypedEventEmitter<Events> {

async getSelfMember(teamId: string): Promise<TeamMemberEntity> {
const memberEntity = await this.getTeamMember(teamId, this.userState.self().id);
this.teamMapper.mapRole(this.userState.self(), memberEntity.permissions);
this.updateUserRole(this.userState.self(), memberEntity.permissions);
return memberEntity;
}

Expand All @@ -201,7 +202,7 @@ export class TeamRepository extends TypedEventEmitter<Events> {
return this.teamService.conversationHasGuestLink(conversationId);
}

getTeamMembersFromUsers = async (users: User[]): Promise<void> => {
private getTeamMembersFromUsers = async (users: User[]): Promise<void> => {
const selfTeamId = this.userState.self().teamId;
if (!selfTeamId) {
return;
Expand Down Expand Up @@ -337,39 +338,21 @@ export class TeamRepository extends TypedEventEmitter<Events> {
this.teamState.memberRoles({});
this.teamState.memberInviters({});
}
const userEntities = await this.userRepository.getUsersById(
memberIds.map(memberId => ({domain: this.teamState.teamDomain(), id: memberId})),
);

if (append) {
const knownUserIds = teamEntity.members().map(({id}) => id);
const newUserEntities = userEntities.filter(({id}) => !knownUserIds.includes(id));
teamEntity.members.push(...newUserEntities);
} else {
teamEntity.members(userEntities);
}
this.updateMemberRoles(teamEntity, mappedMembers);
this.updateMemberRoles(mappedMembers);
}

private async loadTeamMembers(teamEntity: TeamEntity): Promise<QualifiedId[]> {
const teamMembers = await this.getAllTeamMembers(teamEntity.id);
this.teamState.memberRoles({});
this.teamState.memberInviters({});

this.updateMemberRoles(teamEntity, teamMembers);
this.updateMemberRoles(teamMembers);
return teamMembers
.filter(({userId}) => userId !== this.userState.self().id)
.map(memberEntity => ({domain: this.teamState.teamDomain() ?? '', id: memberEntity.userId}));
}

private addUserToTeam(userEntity: User): void {
const members = this.teamState.team().members;

if (!members().find(member => member.id === userEntity.id)) {
members.push(userEntity);
}
}

private getTeamById(teamId: string): Promise<TeamData> {
return this.teamService.getTeamById(teamId);
}
Expand All @@ -391,7 +374,7 @@ export class TeamRepository extends TypedEventEmitter<Events> {
amplify.publish(WebAppEvents.CONVERSATION.DELETE, {domain: '', id: conversationId});
}

private _onMemberJoin(eventJson: TeamMemberJoinEvent): void {
private async _onMemberJoin(eventJson: TeamMemberJoinEvent) {
const {
data: {user: userId},
team: teamId,
Expand All @@ -400,10 +383,9 @@ export class TeamRepository extends TypedEventEmitter<Events> {
const isOtherUser = this.userState.self().id !== userId;

if (isLocalTeam && isOtherUser) {
this.userRepository
.getUserById({domain: this.userState.self().domain, id: userId})
.then(userEntity => this.addUserToTeam(userEntity));
this.getTeamMember(teamId, userId).then(member => this.updateMemberRoles(this.teamState.team(), [member]));
await this.userRepository.getUserById({domain: this.userState.self().domain, id: userId});
const member = await this.getTeamMember(teamId, userId);
this.updateMemberRoles([member]);
}
}

Expand Down Expand Up @@ -443,7 +425,6 @@ export class TeamRepository extends TypedEventEmitter<Events> {
return this.onDelete(eventJson);
}

this.teamState.team().members.remove(member => member.id === userId);
amplify.publish(WebAppEvents.TEAM.MEMBER_LEAVE, teamId, {domain: '', id: userId}, new Date(time).toISOString());
}
}
Expand All @@ -454,34 +435,36 @@ export class TeamRepository extends TypedEventEmitter<Events> {
team: teamId,
} = eventJson;
const isLocalTeam = this.teamState.team().id === teamId;
if (!isLocalTeam) {
return;
}

const isSelfUser = this.userState.self().id === userId;

if (isLocalTeam && isSelfUser) {
if (isSelfUser) {
const memberEntity = permissions ? {permissions} : await this.getTeamMember(teamId, userId);
this.teamMapper.mapRole(this.userState.self(), memberEntity.permissions);
this.updateUserRole(this.userState.self(), memberEntity.permissions);
await this.sendAccountInfo();
}
if (isLocalTeam && !isSelfUser) {
} else {
const member = await this.getTeamMember(teamId, userId);
this.updateMemberRoles(this.teamState.team(), [member]);
this.updateMemberRoles([member]);
}
}

private updateMemberRoles(team: TeamEntity, members: TeamMemberEntity[] = []): void {
members.forEach(member => {
const user = team.members().find(({id}) => member.userId === id);
if (user) {
this.teamMapper.mapRole(user, member.permissions);
}
});
private updateUserRole(user: User, permissions: PermissionsData): void {
user.teamRole(roleFromTeamPermissions(permissions));
}

private updateMemberRoles(members: TeamMemberEntity[] = []): void {
const memberRoles = members.reduce((accumulator, member) => {
accumulator[member.userId] = member.permissions ? roleFromTeamPermissions(member.permissions) : ROLE.INVALID;
return accumulator;
}, this.teamState.memberRoles());

const memberInvites = members.reduce((accumulator, member) => {
accumulator[member.userId] = member.invitedBy;
if (member.invitedBy) {
accumulator[member.userId] = member.invitedBy;
}
return accumulator;
}, this.teamState.memberInviters());

Expand Down
8 changes: 4 additions & 4 deletions src/script/team/TeamState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ import {UserState} from '../user/UserState';
@singleton()
export class TeamState {
public readonly isTeamDeleted: ko.Observable<boolean>;
public readonly memberInviters: ko.Observable<any>;
public readonly memberRoles: ko.Observable<any>;
public readonly memberInviters: ko.Observable<Record<string, string>>;
public readonly memberRoles: ko.Observable<Record<string, ROLE>>;
public readonly supportsLegalHold: ko.Observable<boolean>;
public readonly teamName: ko.PureComputed<string>;
public readonly teamFeatures: ko.Observable<FeatureList | undefined>;
Expand Down Expand Up @@ -138,7 +138,7 @@ export class TeamState {
return !!team.id && entity.domain === this.teamDomain() && entity.teamId === team.id;
}

readonly isExternal = (userId: string): boolean => {
isExternal(userId: string): boolean {
return this.memberRoles()[userId] === ROLE.PARTNER;
};
}
}

0 comments on commit 09475d2

Please sign in to comment.