Skip to content

Commit

Permalink
#1934 Implement channel members state in frontend (#1940)
Browse files Browse the repository at this point in the history
* #1934 Move channel members types in features/channel-members/types

* #1934 Update channel-members-api-client.ts

* #1934 Add channel members state in features/channel-members

* #1934 Add use channel members hook in features/channel-members

* #1934 Implement channel members and pending emails state

* #1934 Add use channel guests hook

* #1927 Upgrade antd from 4.16.13 to 4.18.3

* #1928 Upgrade react-i18next from 11.12.0 to 11.15.3

* #1934 Implement Channel Members real time

Co-authored-by: Romaric Mourgues <rmourgues@linagora.com>
  • Loading branch information
stephanevieira75 and RomaricMourgues committed Feb 21, 2022
1 parent 10e139a commit 2905aed
Show file tree
Hide file tree
Showing 30 changed files with 739 additions and 447 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -378,13 +378,13 @@ export class ChannelCrudController
}>,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
reply: FastifyReply,
): Promise<ResourceCreateResponse<CreateResult<ChannelPendingEmails>>> {
): Promise<ResourceCreateResponse<ChannelPendingEmails>> {
const pendingEmail = await this.pendingEmails.create(
getChannelPendingEmailsInstance(request.body.resource),
getChannelPendingEmailsExecutionContext(request),
);
logger.debug("reqId: %s - save - PendingEmails input %o", request.id, pendingEmail.entity);
return { resource: pendingEmail };
return { resource: pendingEmail.entity };
}

async deletePendingEmails(
Expand Down
4 changes: 2 additions & 2 deletions twake/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
"@typescript-eslint/parser": "^4.30.0",
"anchorme": "1.1.2",
"animated-scroll-to": "^2.0.12",
"antd": "^4.16.5",
"antd": "^4.18.3",
"babel-cli": "^6.26.0",
"babel-core": "^6.26.3",
"babel-eslint": "^10.1.0",
Expand Down Expand Up @@ -187,7 +187,7 @@
"react-fastclick": "^3.0.2",
"react-feather": "^2.0.8",
"react-google-login": "^5.1.1",
"react-i18next": "^11.11.1",
"react-i18next": "^11.15.3",
"react-moment": "^0.9.7",
"react-outside-click-handler": "^1.3.0",
"react-perfect-scrollbar": "^1.5.8",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { CharacterMetadata, ContentBlock, ContentState, EditorState, Modifier } from 'draft-js';
import { getSelectedBlock } from 'draftjs-utils';

import { Mention } from './mention';
import MentionSuggestion from './mention-suggestion';
import { EditorSuggestionPlugin, SelectOrInsertOptions } from '../';
import AccessRightsService from 'app/features/workspace-members/services/workspace-members-access-rights-service';
import WorkspaceService from 'app/deprecated/workspaces/workspaces';
import Collections from 'app/deprecated/CollectionsReact/Collections';
import DepreciatedCollections from 'app/deprecated/CollectionsV1/Collections/Collections';
import { getChannelMembers } from 'app/deprecated/channels/ChannelCollectionPath';
import { UserType } from 'app/features/users/types/user';
import { ChannelMemberResource } from 'app/features/channels/types/channel';
import UserService from 'app/features/users/services/current-user-service';
import RouterService from 'app/features/router/services/router-service';
import UserAPIClient from 'app/features/users/api/user-api-client';
import { WorkspaceUserType } from 'app/features/workspaces/types/workspace';
import Strings from 'app/features/global/utils/strings';
import ChannelMembersAPIClient from 'app/features/channel-members/api/channel-members-api-client';
import { ChannelMemberType } from 'app/features/channel-members/types/channel-member-types';

import './style.scss';

Expand All @@ -36,7 +36,7 @@ const findMentionEntities = (
}, callback);
};

const resolver = (
const resolver = async (
text: string,
max: number,
callback: (mentions: MentionSuggestionType[]) => void,
Expand All @@ -45,20 +45,17 @@ const resolver = (
const { companyId, workspaceId, channelId } = RouterService.getStateFromRoute();

if (AccessRightsService.getCompanyLevel(WorkspaceService.currentGroupId) === 'guest') {
// user is guest, lookup for channel members only
const channelMembersCollection = Collections.get(
getChannelMembers(companyId, workspaceId, channelId),
ChannelMemberResource,
);
const users = channelMembersCollection
.find({})
.map(member => DepreciatedCollections.get('users').find(member.id))
.filter(
(user: UserType) =>
`${user.username} ${user.first_name} ${user.last_name} ${user.email}`
.toLocaleLowerCase()
.indexOf(text.toLocaleLowerCase()) >= 0,
);
const users =
companyId && workspaceId && channelId
? await ChannelMembersAPIClient.list({ companyId, workspaceId, channelId })
: ([] as ChannelMemberType[])
.map(member => DepreciatedCollections.get('users').find(member.user_id))
.filter(
(user: UserType) =>
`${user.username} ${user.first_name} ${user.last_name} ${user.email}`
.toLocaleLowerCase()
.indexOf(text.toLocaleLowerCase()) >= 0,
);

for (let j = 0; j < Math.min(max, users.length); j++) {
result[j] = { ...users[j], ...{ autocomplete_id: j } };
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,65 @@
import { ChannelMemberType } from 'app/features/channels/types/channel';
import Api from '../../global/framework/api-service';
import { TwakeService } from '../../global/framework/registry-decorator-service';
import {
ChannelMemberRole,
ChannelMemberType,
} from 'app/features/channel-members/types/channel-member-types';
import { WebsocketRoom } from 'app/features/global/types/websocket-types';

type ChannelMembersSaveRequest = { resource: Partial<ChannelMemberType> };
type ChannelMembersSaveResponse = { resource: ChannelMemberType };

@TwakeService('ChannelMembersAPIClientService')
class ChannelMembersAPIClient {
class ChannelMembersAPIClientService {
private readonly prefix = '/internal/services/channels/v1/companies';
private realtime: Map<
{ companyId: string; workspaceId: string; channelId: string },
WebsocketRoom[]
> = new Map();

async get(companyId: string, workspaceId: string, channelId: string) {
return Api.get<{ resources: ChannelMemberType[] }>(
`${this.prefix}/${companyId}/workspaces/${workspaceId}/channels/${channelId}/members`,
).then(result => result.resources);
websocket({
companyId,
workspaceId,
channelId,
}: {
companyId: string;
workspaceId: string;
channelId: string;
}): WebsocketRoom[] {
return this.realtime.get({ companyId, workspaceId, channelId }) || [];
}

async save(
/**
* Every member of the channel can list members of the channel.
* This returns a list of members in a channel.
*
* @param companyId string
* @param workspaceId string
* @param channelId string
* @returns ChannelMemberType[]
*/
async list(
context: { companyId: string; workspaceId: string; channelId: string },
filters?: { role: ChannelMemberRole },
) {
return Api.get<{ resources: ChannelMemberType[]; websockets: WebsocketRoom[] }>(
`${this.prefix}/${context.companyId}/workspaces/${context.workspaceId}/channels/${
context.channelId
}/members${filters?.role ? `?company_role=${filters.role}` : ''}?websockets=1`,
).then(result => {
result.websockets && this.realtime.set(context, result.websockets);
return result.resources;
});
}

/**
* Channel member can update their own preferences
*
* @param channelMember ChannelMemberType
* @param partialsToUpdate Partial ChannelMemberType
* @param context Execution context of the update
*/
async updateChannelMemberPreferences(
channelMember: ChannelMemberType,
partialsToUpdate: Partial<ChannelMemberType>,
context: { companyId: string; workspaceId: string; channelId: string; userId: string },
Expand All @@ -28,5 +72,6 @@ class ChannelMembersAPIClient {
).then(result => result.resource);
}
}
const ChannelMembersAPIClient = new ChannelMembersAPIClientService();

export default new ChannelMembersAPIClient();
export default ChannelMembersAPIClient;
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { useRecoilState } from 'recoil';

import {
AtomChannelMembersKey,
ChannelMemberType,
} from 'app/features/channel-members/types/channel-member-types';
import { ChannelGuestsState } from 'app/features/channel-members/state/channel-guests';
import { useGlobalEffect } from 'app/features/global/hooks/use-global-effect';
import { LoadingState } from 'app/features/global/state/atoms/Loading';
import ChannelMembersAPIClient from '../api/channel-members-api-client';

export const useChannelGuests = (
key: AtomChannelMembersKey,
): {
channelGuests: ChannelMemberType[];
loading: boolean;
refresh: () => Promise<void>;
} => {
const [channelGuests, _setChannelGuests] = useRecoilState(ChannelGuestsState(key));
const [loading, setLoading] = useRecoilState(LoadingState(`channel-guests-${key.channelId}`));

const refresh = async () => {
const { companyId, workspaceId, channelId } = key;
const channelGuestsUpdated = await ChannelMembersAPIClient.list(
{
companyId,
workspaceId,
channelId,
},
{ role: 'guest' },
);

if (channelGuestsUpdated) _setChannelGuests(channelGuestsUpdated);
};

useGlobalEffect(
'useChannelGuests',
async () => {
if (!channelGuests) setLoading(true);

await refresh();

setLoading(false);
},
[key, channelGuests],
);

return {
channelGuests,
loading,
refresh,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useRecoilState } from 'recoil';

import {
AtomChannelMembersKey,
ChannelMemberType,
} from 'app/features/channel-members/types/channel-member-types';
import { ChannelMembersState } from 'app/features/channel-members/state/channel-members';
import { useGlobalEffect } from 'app/features/global/hooks/use-global-effect';
import { LoadingState } from 'app/features/global/state/atoms/Loading';
import ChannelMembersAPIClient from '../api/channel-members-api-client';
import { useRealtimeRoom } from 'app/features/global/hooks/use-realtime';

export const useChannelMembers = (
key: AtomChannelMembersKey,
): {
channelMembers: ChannelMemberType[];
loading: boolean;
refresh: () => Promise<void>;
} => {
const [channelMembers, _setChannelMembers] = useRecoilState(ChannelMembersState(key));
const [loading, setLoading] = useRecoilState(LoadingState(`channel-members-${key.channelId}`));

const refresh = async () => {
const { companyId, workspaceId, channelId } = key;
const channelMembersUpdated = await ChannelMembersAPIClient.list({
companyId,
workspaceId,
channelId,
});

if (channelMembersUpdated) _setChannelMembers(channelMembersUpdated);
};

useGlobalEffect(
'useChannelMembers',
async () => {
if (!channelMembers) setLoading(true);

await refresh();

setLoading(false);
},
[key, channelMembers],
);

const room = ChannelMembersAPIClient.websocket(key)[0];

useRealtimeRoom<ChannelMemberType[]>(room, 'useChannelMembers', (_action, _resource) => {
refresh();
});

return {
channelMembers,
loading,
refresh,
};
};

0 comments on commit 2905aed

Please sign in to comment.