# Feature: Email thread members visibility For this feature we implemented a chip and a dropdown menu that allows users to check which workspace members can see an email thread, as depicted on issue (#4199). ## Implementations - create a new database table (messageThreadMember) - relations between `messageThreadMembers` and the relevant existing tables (`MessageThread` and `WorkspaceMembers`) - added a new column to the `MessageThread table`: `everyone` - to indicate that all workspace members can see the email thread - create a new repository for the new table, including new queries - edit the queries so that the new fields could be fetched from the frontend - created a component `MultiChip`, that shows a group of user avatars, instead of just one - created a component, `ShareDropdownMenu`, that shows up once the `EmailThreadMembersChip` is clicked. On this menu you can see which workspace members can view the email thread. ## Screenshots Here are some screenshots of the frontend components that were created: Chip with everyone in the workspace being part of the message thread:  Chip with just one member of the workspace (the owner) being part of the message thread:  Chip with some members of the workspace being part of the message thread:  How the chip looks in a message thread:  Dropdown that opens when you click on the chip:  ## Testing and Mock data We also added mock data (TypeORM seeds), focusing on adding mock data related to message thread members. ## Conclusion As some of the changes that we needed to do, regarding the change of visibility of the message thread, were not covered by the existing documentation, we were told to open a PR and ask for feedback on this part of the implementation. Right now, our implementation is focused on displaying who is part of an email thread. Feel free to let us know which steps we should follow next :) --------- Co-authored-by: Simão Sanguinho <simao.sanguinho@tecnico.ulisboa.pt> Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@ -0,0 +1,58 @@
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator';
|
||||
import { WorkspaceGate } from 'src/engine/twenty-orm/decorators/workspace-gate.decorator';
|
||||
import { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.decorator';
|
||||
import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator';
|
||||
import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator';
|
||||
import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator';
|
||||
import { MESSAGE_THREAD_SUBSCRIBER_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
||||
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
||||
import { MessageThreadWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-thread.workspace-entity';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
|
||||
@WorkspaceEntity({
|
||||
standardId: STANDARD_OBJECT_IDS.messageThreadSubscriber,
|
||||
namePlural: 'messageThreadSubscriber',
|
||||
labelSingular: 'Message Thread Subscriber',
|
||||
labelPlural: 'Message Threads Subscribers',
|
||||
description: 'Message Thread Subscribers',
|
||||
icon: 'IconPerson',
|
||||
})
|
||||
@WorkspaceIsNotAuditLogged()
|
||||
@WorkspaceIsSystem()
|
||||
@WorkspaceGate({
|
||||
featureFlag: FeatureFlagKey.IsMessageThreadSubscriberEnabled,
|
||||
})
|
||||
export class MessageThreadSubscriberWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
@WorkspaceRelation({
|
||||
standardId: MESSAGE_THREAD_SUBSCRIBER_STANDARD_FIELD_IDS.messageThread,
|
||||
type: RelationMetadataType.MANY_TO_ONE,
|
||||
label: 'Message Thread',
|
||||
description: 'Message Thread',
|
||||
icon: 'IconMessage',
|
||||
inverseSideFieldKey: 'subscribers',
|
||||
inverseSideTarget: () => MessageThreadWorkspaceEntity,
|
||||
})
|
||||
messageThread: Relation<MessageThreadWorkspaceEntity>;
|
||||
|
||||
@WorkspaceJoinColumn('messageThread')
|
||||
messageThreadId: string;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: MESSAGE_THREAD_SUBSCRIBER_STANDARD_FIELD_IDS.workspaceMember,
|
||||
type: RelationMetadataType.MANY_TO_ONE,
|
||||
label: 'Workspace Member',
|
||||
description: 'Workspace Member that is part of the message thread',
|
||||
icon: 'IconCircleUser',
|
||||
inverseSideFieldKey: 'messageThreadSubscribers',
|
||||
inverseSideTarget: () => WorkspaceMemberWorkspaceEntity,
|
||||
})
|
||||
workspaceMember: Relation<WorkspaceMemberWorkspaceEntity>;
|
||||
|
||||
@WorkspaceJoinColumn('workspaceMember')
|
||||
workspaceMemberId: string;
|
||||
}
|
||||
@ -1,18 +1,21 @@
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import {
|
||||
RelationMetadataType,
|
||||
RelationOnDeleteAction,
|
||||
} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||
import { MESSAGE_THREAD_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
||||
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator';
|
||||
import { WorkspaceGate } from 'src/engine/twenty-orm/decorators/workspace-gate.decorator';
|
||||
import { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.decorator';
|
||||
import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator';
|
||||
import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator';
|
||||
import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator';
|
||||
import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator';
|
||||
import { MESSAGE_THREAD_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
||||
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
||||
import { MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity';
|
||||
import { MessageThreadSubscriberWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-thread-subscriber.workspace-entity';
|
||||
import { MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity';
|
||||
|
||||
@WorkspaceEntity({
|
||||
@ -38,6 +41,20 @@ export class MessageThreadWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
@WorkspaceIsNullable()
|
||||
messages: Relation<MessageWorkspaceEntity[]>;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: MESSAGE_THREAD_STANDARD_FIELD_IDS.messageThreadSubscribers,
|
||||
type: RelationMetadataType.ONE_TO_MANY,
|
||||
label: 'Message Thread Subscribers',
|
||||
description: 'Message Thread Subscribers',
|
||||
icon: 'IconMessage',
|
||||
inverseSideTarget: () => MessageThreadSubscriberWorkspaceEntity,
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
@WorkspaceGate({
|
||||
featureFlag: FeatureFlagKey.IsMessageThreadSubscriberEnabled,
|
||||
})
|
||||
subscribers: Relation<MessageThreadSubscriberWorkspaceEntity[]>;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId:
|
||||
MESSAGE_THREAD_STANDARD_FIELD_IDS.messageChannelMessageAssociations,
|
||||
|
||||
@ -4,16 +4,19 @@ import { EntityManager } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
||||
import { MessageChannelMessageAssociationRepository } from 'src/modules/messaging/common/repositories/message-channel-message-association.repository';
|
||||
import { MessageThreadRepository } from 'src/modules/messaging/common/repositories/message-thread.repository';
|
||||
import { MessageRepository } from 'src/modules/messaging/common/repositories/message.repository';
|
||||
import { MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity';
|
||||
import { MessageThreadSubscriberWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-thread-subscriber.workspace-entity';
|
||||
import { MessageThreadWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-thread.workspace-entity';
|
||||
import { MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity';
|
||||
|
||||
@Injectable()
|
||||
export class MessagingMessageThreadService {
|
||||
constructor(
|
||||
private readonly twentyORMManager: TwentyORMManager,
|
||||
@InjectObjectMetadataRepository(
|
||||
MessageChannelMessageAssociationWorkspaceEntity,
|
||||
)
|
||||
@ -24,6 +27,24 @@ export class MessagingMessageThreadService {
|
||||
private readonly messageThreadRepository: MessageThreadRepository,
|
||||
) {}
|
||||
|
||||
public async saveMessageThreadMember(
|
||||
messageThreadId: string,
|
||||
workspaceMemberId: string,
|
||||
) {
|
||||
const id = v4();
|
||||
|
||||
const messageThreadSubscriberRepository =
|
||||
await this.twentyORMManager.getRepository<MessageThreadSubscriberWorkspaceEntity>(
|
||||
'messageThreadSubscriber',
|
||||
);
|
||||
|
||||
await messageThreadSubscriberRepository.insert({
|
||||
id,
|
||||
messageThreadId,
|
||||
workspaceMemberId,
|
||||
});
|
||||
}
|
||||
|
||||
public async saveMessageThreadOrReturnExistingMessageThread(
|
||||
headerMessageId: string,
|
||||
messageThreadExternalId: string,
|
||||
|
||||
@ -2,6 +2,7 @@ import { registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FullNameMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/full-name.composite-type';
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import {
|
||||
@ -11,6 +12,7 @@ import {
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator';
|
||||
import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator';
|
||||
import { WorkspaceGate } from 'src/engine/twenty-orm/decorators/workspace-gate.decorator';
|
||||
import { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.decorator';
|
||||
import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator';
|
||||
import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator';
|
||||
@ -26,6 +28,7 @@ import { CompanyWorkspaceEntity } from 'src/modules/company/standard-objects/com
|
||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity';
|
||||
import { MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity';
|
||||
import { MessageThreadSubscriberWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-thread-subscriber.workspace-entity';
|
||||
import { TaskWorkspaceEntity } from 'src/modules/task/standard-objects/task.workspace-entity';
|
||||
import { AuditLogWorkspaceEntity } from 'src/modules/timeline/standard-objects/audit-log.workspace-entity';
|
||||
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
|
||||
@ -170,6 +173,20 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
})
|
||||
favorites: Relation<FavoriteWorkspaceEntity[]>;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: WORKSPACE_MEMBER_STANDARD_FIELD_IDS.messageThreadSubscribers,
|
||||
type: RelationMetadataType.ONE_TO_MANY,
|
||||
label: 'Message thread subscribers',
|
||||
description: 'Message thread subscribers for this workspace member',
|
||||
icon: 'IconMessage',
|
||||
inverseSideTarget: () => MessageThreadSubscriberWorkspaceEntity,
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
@WorkspaceGate({
|
||||
featureFlag: FeatureFlagKey.IsMessageThreadSubscriberEnabled,
|
||||
})
|
||||
messageThreadSubscribers: Relation<MessageThreadSubscriberWorkspaceEntity[]>;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: WORKSPACE_MEMBER_STANDARD_FIELD_IDS.accountOwnerForCompanies,
|
||||
type: RelationMetadataType.ONE_TO_MANY,
|
||||
|
||||
Reference in New Issue
Block a user