Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BC-8193 - Expanding and registering BBB #5354

Merged
merged 32 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
c0095fc
implementing board videoconf in VC service with tests
MartinSchuhmacher Nov 25, 2024
0ddbb76
recent state for implementing board authorization in VC
MartinSchuhmacher Nov 25, 2024
777e81b
implementing VC element and room + VC element scope
MartinSchuhmacher Nov 26, 2024
1e2e125
adjusting VC scope to board element
MartinSchuhmacher Nov 27, 2024
8bd7dba
Merge branch 'main' into BC-8193-start-bbb
MartinSchuhmacher Nov 27, 2024
23be7be
adding VC response and node
MartinSchuhmacher Nov 28, 2024
2a5dd3c
Merge branch 'main' into BC-8193-start-bbb
MartinSchuhmacher Dec 2, 2024
7ffc545
adding more tests
MartinSchuhmacher Dec 3, 2024
e110ae3
Merge branch 'main' into BC-8193-start-bbb
MartinSchuhmacher Dec 3, 2024
633d4af
adjusting VC service
MartinSchuhmacher Dec 4, 2024
0dea59c
adding api tests for VC scopes
MartinSchuhmacher Dec 4, 2024
232ab4e
Merge branch 'main' into BC-8193-start-bbb
MartinSchuhmacher Dec 4, 2024
d8b752a
fixing rename issues
MartinSchuhmacher Dec 4, 2024
b27c068
adding unit tests for coverage
MartinSchuhmacher Dec 5, 2024
647dbd2
Merge branch 'main' into BC-8193-start-bbb
MartinSchuhmacher Dec 5, 2024
802ad7a
add school id indexes
uidp Dec 5, 2024
ab4ea5a
Merge branch 'main' into BC-8193-start-bbb
MartinSchuhmacher Dec 6, 2024
48c38b7
adding yellow to room color selector
MartinSchuhmacher Dec 10, 2024
d42ec95
repositioning of yellow color in room color picker
MartinSchuhmacher Dec 10, 2024
1d536ed
adjusting after review feedback
MartinSchuhmacher Dec 10, 2024
dc7c768
Merge branch 'main' into BC-8193-start-bbb
MartinSchuhmacher Dec 10, 2024
66b7a98
removing url property from VC element
MartinSchuhmacher Dec 11, 2024
d830df8
implementing review feedback
MartinSchuhmacher Dec 11, 2024
07a775e
Merge branch 'main' into BC-8193-start-bbb
MartinSchuhmacher Dec 11, 2024
b20cc2d
fixing merge conflicts
MartinSchuhmacher Dec 11, 2024
9c7ed6e
adding more tests for coverage
MartinSchuhmacher Dec 13, 2024
013c5f6
seting type alias for better maintainability
MartinSchuhmacher Dec 13, 2024
7b305ce
Merge branch 'main' into BC-8193-start-bbb
MartinSchuhmacher Dec 13, 2024
594e25e
implementing FEATURE_COLUMN_BOARD_VIDEOCONFERENCE_ENABLED for FE usage
MartinSchuhmacher Dec 13, 2024
59c42f5
adding missing dev activation for FE env
MartinSchuhmacher Dec 13, 2024
b177a9c
fixing typo
MartinSchuhmacher Dec 13, 2024
1c69034
Merge branch 'main' into BC-8193-start-bbb
MartinSchuhmacher Dec 16, 2024
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 apps/server/src/modules/board/domain/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ export * from './submission-item.do';
export * from './path-utils';
export * from './types';
export * from './type-mapping';
export * from './video-conference-element.do';
export * from './deleted-element.do';
2 changes: 2 additions & 0 deletions apps/server/src/modules/board/domain/type-mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { SubmissionContainerElement } from './submission-container-element.do';
import { SubmissionItem } from './submission-item.do';
import type { AnyBoardNode } from './types/any-board-node';
import { BoardNodeType } from './types/board-node-type.enum';
import { VideoConferenceElement } from './video-conference-element.do';

// register node types
const BoardNodeTypeToConstructor = {
Expand All @@ -31,6 +32,7 @@ const BoardNodeTypeToConstructor = {
[BoardNodeType.RICH_TEXT_ELEMENT]: RichTextElement,
[BoardNodeType.SUBMISSION_CONTAINER_ELEMENT]: SubmissionContainerElement,
[BoardNodeType.SUBMISSION_ITEM]: SubmissionItem,
[BoardNodeType.VIDEO_CONFERENCE_ELEMENT]: VideoConferenceElement,
[BoardNodeType.DELETED_ELEMENT]: DeletedElement,
} as const;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { type FileElement, isFileElement } from '../file-element.do';
import { isLinkElement, type LinkElement } from '../link-element.do';
import { isRichTextElement, type RichTextElement } from '../rich-text-element.do';
import { isSubmissionContainerElement, type SubmissionContainerElement } from '../submission-container-element.do';
import { isVideoConferenceElement, VideoConferenceElement } from '../video-conference-element.do';
import { type AnyBoardNode } from './any-board-node';

export type AnyContentElement =
Expand All @@ -16,7 +17,8 @@ export type AnyContentElement =
| LinkElement
| RichTextElement
| SubmissionContainerElement
| DeletedElement;
| DeletedElement
| VideoConferenceElement;

export const isContentElement = (boardNode: AnyBoardNode): boardNode is AnyContentElement => {
const result =
Expand All @@ -27,7 +29,8 @@ export const isContentElement = (boardNode: AnyBoardNode): boardNode is AnyConte
isLinkElement(boardNode) ||
isRichTextElement(boardNode) ||
isSubmissionContainerElement(boardNode) ||
isDeletedElement(boardNode);
isDeletedElement(boardNode) ||
isVideoConferenceElement(boardNode);

return result;
};
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ export interface SubmissionItemProps extends BoardNodeProps {
userId: EntityId;
}

export interface VideoConferenceElementProps extends BoardNodeProps {
title: string;
url?: string;
}

export interface DeletedElementProps extends BoardNodeProps {
title: string;
deletedElementType: ContentElementType;
Expand Down Expand Up @@ -104,4 +109,5 @@ export type AnyBoardNodeProps =
| RichTextElementProps
| SubmissionContainerElementProps
| SubmissionItemProps
| VideoConferenceElementProps
| MediaBoardNodeProps;
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export enum BoardNodeType {
EXTERNAL_TOOL = 'external-tool',
COLLABORATIVE_TEXT_EDITOR = 'collaborative-text-editor',
DELETED_ELEMENT = 'deleted-element',
VIDEO_CONFERENCE_ELEMENT = 'video-conference-element',

MEDIA_BOARD = 'media-board',
MEDIA_LINE = 'media-line',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { VideoConferenceElement, isVideoConferenceElement } from './video-conference-element.do';
import { BoardNodeProps } from './types/board-node-props';

describe('VideoConferenceElement', () => {
let videoConferenceElement: VideoConferenceElement;

const boardNodeProps: BoardNodeProps = {
id: '1',
path: '',
level: 1,
position: 1,
children: [],
createdAt: new Date(),
updatedAt: new Date(),
};

beforeEach(() => {
videoConferenceElement = new VideoConferenceElement({
...boardNodeProps,
url: 'https://example.com',
title: 'Example',
});
});

it('should be instance of VideoConferenceElement', () => {
expect(isVideoConferenceElement(videoConferenceElement)).toBe(true);
});

it('should not be instance of VideoConferenceElement', () => {
expect(isVideoConferenceElement({})).toBe(false);
});

it('should return url', () => {
expect(videoConferenceElement.url).toBe('https://example.com');
});

it('should set url', () => {
videoConferenceElement.url = 'https://newurl.com';
expect(videoConferenceElement.url).toBe('https://newurl.com');
});

it('should return title', () => {
expect(videoConferenceElement.title).toBe('Example');
});

it('should set title', () => {
videoConferenceElement.title = 'New title';
expect(videoConferenceElement.title).toBe('New title');
});

it('should not have child', () => {
expect(videoConferenceElement.canHaveChild()).toBe(false);
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need a fallback for url and title ... ?? ''.

Is there a reason not to trust the type system?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, agree, if we use types we should trust on that

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { BoardNode } from './board-node.do';
import type { VideoConferenceElementProps } from './types';

export class VideoConferenceElement extends BoardNode<VideoConferenceElementProps> {
get url(): string {
return this.props.url ?? '';
}

set url(value: string) {
MartinSchuhmacher marked this conversation as resolved.
Show resolved Hide resolved
this.props.url = value;
}

get title(): string {
return this.props.title ?? '';
}

set title(value: string) {
this.props.title = value;
}

canHaveChild(): boolean {
return false;
}
}

export const isVideoConferenceElement = (reference: unknown): reference is VideoConferenceElement =>
reference instanceof VideoConferenceElement;
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
richTextElementFactory,
submissionContainerElementFactory,
submissionItemFactory,
videoConferenceElementFactory,
} from '../../testing';
import { BoardNodeCopyContext, BoardNodeCopyContextProps } from './board-node-copy-context';
import { BoardNodeCopyService } from './board-node-copy.service';
Expand Down Expand Up @@ -96,6 +97,7 @@ describe(BoardNodeCopyService.name, () => {
jest.spyOn(service, 'copyMediaLine').mockResolvedValue(mockStatus);
jest.spyOn(service, 'copyMediaExternalToolElement').mockResolvedValue(mockStatus);
jest.spyOn(service, 'copyDeletedElement').mockResolvedValue(mockStatus);
jest.spyOn(service, 'copyVideoConferenceElement').mockResolvedValue(mockStatus);

return { copyContext, mockStatus };
};
Expand Down Expand Up @@ -279,6 +281,18 @@ describe(BoardNodeCopyService.name, () => {
expect(result).toEqual(mockStatus);
});
});

describe('when called with video conference element', () => {
it('should copy deleted element', async () => {
const { copyContext, mockStatus } = setup();
const node = videoConferenceElementFactory.build();

const result = await service.copy(node, copyContext);

expect(service.copyVideoConferenceElement).toHaveBeenCalledWith(node, copyContext);
expect(result).toEqual(mockStatus);
});
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
RichTextElement,
SubmissionContainerElement,
SubmissionItem,
VideoConferenceElement,
} from '../../domain';

export interface CopyContext {
Expand Down Expand Up @@ -80,6 +81,9 @@ export class BoardNodeCopyService {
case BoardNodeType.COLLABORATIVE_TEXT_EDITOR:
result = await this.copyCollaborativeTextEditorElement(boardNode as CollaborativeTextEditorElement, context);
break;
case BoardNodeType.VIDEO_CONFERENCE_ELEMENT:
result = await this.copyVideoConferenceElement(boardNode as VideoConferenceElement, context);
break;
case BoardNodeType.DELETED_ELEMENT:
result = await this.copyDeletedElement(boardNode as DeletedElement, context);
break;
Expand Down Expand Up @@ -335,6 +339,16 @@ export class BoardNodeCopyService {
return Promise.resolve(result);
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
async copyVideoConferenceElement(original: VideoConferenceElement, context: CopyContext): Promise<CopyStatus> {
const result: CopyStatus = {
type: CopyElementType.VIDEO_CONFERENCE_ELEMENT,
status: CopyStatusEnum.NOT_DOING,
};

return Promise.resolve(result);
}

async copyMediaBoard(original: MediaBoard, context: CopyContext): Promise<CopyStatus> {
const childrenResults = await this.copyChildrenOf(original, context);

Expand Down
1 change: 1 addition & 0 deletions apps/server/src/modules/board/testing/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ export * from './media-line.factory';
export * from './rich-text-element.factory';
export * from './submission-container-element.factory';
export * from './submission-item.factory';
export * from './video-conference-element.factory';
export * from './deleted-element.factory';
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ObjectId } from '@mikro-orm/mongodb';
import { BaseFactory } from '@shared/testing';
import { ROOT_PATH, VideoConferenceElement, VideoConferenceElementProps } from '../domain';

export const videoConferenceElementFactory = BaseFactory.define<VideoConferenceElement, VideoConferenceElementProps>(
VideoConferenceElement,
({ sequence }) => {
return {
id: new ObjectId().toHexString(),
path: ROOT_PATH,
level: 0,
position: 0,
children: [],
createdAt: new Date(),
updatedAt: new Date(),
title: `video conference element #${sequence}`,
url: `url #${sequence}`,
};
}
);
1 change: 1 addition & 0 deletions apps/server/src/modules/copy-helper/types/copy.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export enum CopyElementType {
TASK_GROUP = 'TASK_GROUP',
TIME_GROUP = 'TIME_GROUP',
USER_GROUP = 'USER_GROUP',
VIDEO_CONFERENCE_ELEMENT = 'VIDEO_CONFERENCE_ELEMENT',
}

export enum CopyStatusEnum {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ describe('VideoConferenceController (API)', () => {
});
});

describe('when user has the required permission', () => {
describe('when user has the required permission in course scope', () => {
const setup = async () => {
const school: SchoolEntity = schoolEntityFactory.buildWithId({ features: [SchoolFeature.VIDEOCONFERENCE] });

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ import { courseFactory, roleFactory, setupEntities, userDoFactory, userFactory }
import { teamFactory } from '@shared/testing/factory/team.factory';
import { teamUserFactory } from '@shared/testing/factory/teamuser.factory';
import { videoConferenceDOFactory } from '@shared/testing/factory/video-conference.do.factory';
import { BoardNodeAuthorizableService, BoardNodeService, ColumnBoardService } from '@src/modules/board';
import { RoleService } from '@src/modules/role';
import { RoomService } from '@src/modules/room';
import { RoomMemberService } from '@src/modules/room-member';
import { BBBRole } from '../bbb';
import { ErrorStatus } from '../error';
import { VideoConferenceOptions } from '../interface';
Expand All @@ -27,9 +31,21 @@ import { VideoConferenceService } from './video-conference.service';

describe(VideoConferenceService.name, () => {
let service: DeepMocked<VideoConferenceService>;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let boardNodeAuthorizableService: DeepMocked<BoardNodeAuthorizableService>;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let boardNodeService: DeepMocked<BoardNodeService>;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let columnBoardService: DeepMocked<ColumnBoardService>;
let courseService: DeepMocked<CourseService>;
let calendarService: DeepMocked<CalendarService>;
let authorizationService: DeepMocked<AuthorizationService>;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let roleService: DeepMocked<RoleService>;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let roomMemberService: DeepMocked<RoomMemberService>;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let roomService: DeepMocked<RoomService>;
let schoolService: DeepMocked<LegacySchoolService>;
let teamsRepo: DeepMocked<TeamsRepo>;
let userService: DeepMocked<UserService>;
Expand All @@ -40,10 +56,22 @@ describe(VideoConferenceService.name, () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
VideoConferenceService,
{
provide: BoardNodeAuthorizableService,
useValue: createMock<BoardNodeAuthorizableService>(),
},
{
provide: BoardNodeService,
useValue: createMock<BoardNodeService>(),
},
{
provide: ConfigService,
useValue: createMock<ConfigService<VideoConferenceConfig, true>>(),
},
{
provide: ColumnBoardService,
useValue: createMock<ColumnBoardService>(),
},
{
provide: CourseService,
useValue: createMock<CourseService>(),
Expand All @@ -60,6 +88,18 @@ describe(VideoConferenceService.name, () => {
provide: LegacySchoolService,
useValue: createMock<LegacySchoolService>(),
},
{
provide: RoleService,
useValue: createMock<RoleService>(),
},
{
provide: RoomMemberService,
useValue: createMock<RoomMemberService>(),
},
{
provide: RoomService,
useValue: createMock<RoomService>(),
},
{
provide: TeamsRepo,
useValue: createMock<TeamsRepo>(),
Expand All @@ -76,9 +116,15 @@ describe(VideoConferenceService.name, () => {
}).compile();

service = module.get(VideoConferenceService);
boardNodeAuthorizableService = module.get(BoardNodeAuthorizableService);
boardNodeService = module.get(BoardNodeService);
columnBoardService = module.get(ColumnBoardService);
courseService = module.get(CourseService);
calendarService = module.get(CalendarService);
authorizationService = module.get(AuthorizationService);
roleService = module.get(RoleService);
roomMemberService = module.get(RoomMemberService);
roomService = module.get(RoomService);
schoolService = module.get(LegacySchoolService);
teamsRepo = module.get(TeamsRepo);
userService = module.get(UserService);
Expand Down Expand Up @@ -602,7 +648,7 @@ describe(VideoConferenceService.name, () => {
});
});

describe('getUserRoleAndGuestStatusByUserId', () => {
describe('getUserRoleAndGuestStatusByUserIdForBbb', () => {
const setup = (conferenceScope: VideoConferenceScope) => {
const user: UserDO = userDoFactory.buildWithId();
const userId = user.id as EntityId;
Expand Down
Loading
Loading