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

N21-1506-sharing-ctl-tools #5353

Merged
merged 41 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
32aef24
migration script
IgorCapCoder Nov 22, 2024
3e4d3ea
N21-1506 extended logic for sharing course across diff. schools
GordonNicholasCap Nov 22, 2024
76370a8
N21-1506 extended logic for sharing course across diff. schools
GordonNicholasCap Nov 22, 2024
8554cb5
N21-1506 unit tests
GordonNicholasCap Nov 25, 2024
20ad625
Merge remote-tracking branch 'origin/N21-1506-sharing-ctl-tools' into…
GordonNicholasCap Nov 25, 2024
52a8d58
Merge branch 'refs/heads/main' into N21-1506-sharing-ctl-tools
GordonNicholasCap Nov 25, 2024
f87cc5d
eslint ignore
IgorCapCoder Nov 26, 2024
315a01a
N21-1506 api tests, small fixes
GordonNicholasCap Nov 26, 2024
8327b45
Merge branch 'main' into N21-1506-sharing-ctl-tools
GordonNicholasCap Nov 26, 2024
4b98df7
Merge remote-tracking branch 'origin/N21-1506-sharing-ctl-tools' into…
GordonNicholasCap Nov 26, 2024
25720a7
N21-1506 fix attempt for failing api tests
GordonNicholasCap Nov 27, 2024
9cb3d16
Merge branch 'main' into N21-1506-sharing-ctl-tools
GordonNicholasCap Nov 27, 2024
6922dba
N21-1506 fix missing course tool id
GordonNicholasCap Nov 27, 2024
2edd0f6
attempt 2 fix api tests
GordonNicholasCap Nov 27, 2024
3b10069
N21-1506 fix wrong configs
GordonNicholasCap Nov 27, 2024
9f19f78
N21-1506 fix factory, adjust tests
GordonNicholasCap Nov 27, 2024
a185814
Merge branch 'main' into N21-1506-sharing-ctl-tools
GordonNicholasCap Nov 27, 2024
164c274
make sonarcloud happy
GordonNicholasCap Nov 27, 2024
ba0a388
requested changes
IgorCapCoder Nov 29, 2024
d10d4ad
Merge branch 'main' into N21-1506-sharing-ctl-tools
IgorCapCoder Nov 29, 2024
d976719
Merge branch 'main' into N21-1506-sharing-ctl-tools
IgorCapCoder Dec 1, 2024
fb18fec
requested changes
IgorCapCoder Dec 1, 2024
7962991
N21-1506 review changes
GordonNicholasCap Dec 2, 2024
bb9d434
Merge remote-tracking branch 'origin/N21-1506-sharing-ctl-tools' into…
GordonNicholasCap Dec 2, 2024
7d582db
Merge branch 'main' into N21-1506-sharing-ctl-tools
GordonNicholasCap Dec 2, 2024
f76d8cf
requested changes
IgorCapCoder Dec 2, 2024
c9f2bcf
Merge remote-tracking branch 'origin/N21-1506-sharing-ctl-tools' into…
IgorCapCoder Dec 2, 2024
885bbff
Merge branch 'main' into N21-1506-sharing-ctl-tools
GordonNicholasCap Dec 2, 2024
5ab2b01
Merge remote-tracking branch 'refs/remotes/origin/main' into N21-1506…
GordonNicholasCap Dec 2, 2024
9927a35
requested change remove reference
IgorCapCoder Dec 4, 2024
acff21a
N21-1506 show copy failed board tool elements in report
GordonNicholasCap Dec 4, 2024
7df8029
N21-1506 show copy failed report for course tools
GordonNicholasCap Dec 5, 2024
ef1df44
Merge branch 'refs/heads/main' into N21-1506-sharing-ctl-tools
GordonNicholasCap Dec 5, 2024
861b62e
N21-1506 fix tests from merge conflicts
GordonNicholasCap Dec 5, 2024
b652f19
Merge branch 'main' into N21-1506-sharing-ctl-tools
GordonNicholasCap Dec 5, 2024
805cfe1
N21-1506 fix tests
GordonNicholasCap Dec 5, 2024
b0c0edb
Merge remote-tracking branch 'origin/N21-1506-sharing-ctl-tools' into…
GordonNicholasCap Dec 5, 2024
da4a06f
N21-1506 fix eslint err
GordonNicholasCap Dec 5, 2024
c135eea
N21-1506 fix sonarcloud issue
GordonNicholasCap Dec 5, 2024
f10bcfb
Merge branch 'main' into N21-1506-sharing-ctl-tools
GordonNicholasCap Dec 6, 2024
2151b1d
Merge branch 'main' into N21-1506-sharing-ctl-tools
GordonNicholasCap Dec 6, 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
65 changes: 65 additions & 0 deletions apps/server/src/migrations/mikro-orm/Migration20241120100616.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Migration } from '@mikro-orm/migrations-mongodb';
import { ObjectId } from '@mikro-orm/mongodb';

export class Migration20241120100616 extends Migration {
async up(): Promise<void> {
const cursor = this.getCollection<{ contextType: string; contextId: ObjectId; schoolTool: ObjectId }>(
'context-external-tools'
).find({
$or: [{ contextType: 'course' }, { contextType: 'boardElement' }],
});

let numberOfDeletedTools = 0;
let numberOfDeletedElements = 0;
for await (const tool of cursor) {
let courseId: ObjectId | undefined;
if (tool.contextType === 'course') {
courseId = tool.contextId;
} else if (tool.contextType === 'boardElement') {
const element = await this.getCollection<{ path: string }>('boardnodes').findOne({
_id: tool.contextId,
});

if (element) {
const boardId = new ObjectId(element.path.split(',')[1]);

const board = await this.getCollection<{ context: ObjectId }>('boardnodes').findOne({
_id: boardId,
});

if (board) {
courseId = board.context;
}
}
}

if (courseId) {
const course = await this.getCollection<{ schoolId: ObjectId }>('courses').findOne({ _id: courseId });

const schoolTool = await this.getCollection<{ school: ObjectId }>('school-external-tools').findOne({
_id: tool.schoolTool,
});

if (!course || !schoolTool || course.schoolId.toString() !== schoolTool.school.toString()) {
await this.driver.nativeDelete('context-external-tools', { _id: tool._id });
console.info(`deleted context external tool: ${tool._id.toString()}`);
numberOfDeletedTools += 1;
if (tool.contextType === 'boardElement') {
await this.driver.nativeDelete('boardnodes', { _id: tool.contextId });
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
console.info(`deleted boardnode: ${tool.contextId}`);
numberOfDeletedElements += 1;
}
}
}
}
console.info(
`Deleted ${numberOfDeletedTools} context external tools and ${numberOfDeletedElements} external tool elements.`
);
}

// eslint-disable-next-line @typescript-eslint/require-await
async down(): Promise<void> {
console.info('Unfortunately the deleted documents cannot be restored. Use a backup.');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export class BoardController {
@Param() urlParams: BoardUrlParams,
@CurrentUser() currentUser: ICurrentUser
): Promise<CopyApiResponse> {
const copyStatus = await this.boardUc.copyBoard(currentUser.userId, urlParams.boardId);
const copyStatus = await this.boardUc.copyBoard(currentUser.userId, urlParams.boardId, currentUser.schoolId);
const dto = CopyMapper.mapToResponse(copyStatus);
return dto;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { Test, TestingModule } from '@nestjs/testing';
import { EntityId } from '@shared/domain/types';
import { StorageLocation } from '@src/modules/files-storage/interface';
import { ObjectId } from '@mikro-orm/mongodb';
import { CopyElementType, CopyStatus, CopyStatusEnum } from '../../copy-helper';
import { BoardExternalReference, BoardExternalReferenceType, ColumnBoard } from '../domain';
import { BoardNodeRepo } from '../repo';
Expand Down Expand Up @@ -114,6 +115,7 @@ describe('ColumnBoardService', () => {
sourceStorageLocationReference: { id: '1', type: StorageLocation.SCHOOL },
targetStorageLocationReference: { id: '1', type: StorageLocation.SCHOOL },
userId: '1',
targetSchoolId: new ObjectId().toHexString(),
});

expect(result).toEqual(copyStatus);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe(BoardNodeCopyContext.name, () => {
targetStorageLocationReference: { id: new ObjectId().toHexString(), type: StorageLocation.SCHOOL },
userId: new ObjectId().toHexString(),
filesStorageClientAdapterService: createMock<FilesStorageClientAdapterService>(),
targetSchoolId: new ObjectId().toHexString(),
};

const copyContext = new BoardNodeCopyContext(contextProps);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,15 @@ export type BoardNodeCopyContextProps = {
targetStorageLocationReference: StorageLocationReference;
userId: EntityId;
filesStorageClientAdapterService: FilesStorageClientAdapterService;
targetSchoolId: EntityId;
};

export class BoardNodeCopyContext implements CopyContext {
constructor(private readonly props: BoardNodeCopyContextProps) {}
readonly targetSchoolId: EntityId;

constructor(private readonly props: BoardNodeCopyContextProps) {
this.targetSchoolId = props.targetSchoolId;
}

copyFilesOfParent(sourceParentId: EntityId, targetParentId: EntityId): Promise<CopyFileDto[]> {
return this.props.filesStorageClientAdapterService.copyFilesOfParent({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createMock } from '@golevelup/ts-jest';
import { ObjectId } from '@mikro-orm/mongodb';
import { CopyElementType, CopyHelperService, CopyStatus, CopyStatusEnum } from '@modules/copy-helper';
import { StorageLocation } from '@modules/files-storage/interface';
import { SchoolExternalToolService } from '@modules/tool/school-external-tool/service';
import { ContextExternalToolService } from '@modules/tool/context-external-tool/service';
import { ToolConfig } from '@modules/tool/tool-config';
import { ConfigService } from '@nestjs/config';
Expand Down Expand Up @@ -48,6 +49,10 @@ describe(BoardNodeCopyService.name, () => {
provide: CopyHelperService,
useValue: createMock<CopyHelperService>(),
},
{
provide: SchoolExternalToolService,
useValue: createMock<SchoolExternalToolService>(),
},
],
}).compile();

Expand All @@ -70,6 +75,7 @@ describe(BoardNodeCopyService.name, () => {
targetStorageLocationReference: { id: new ObjectId().toHexString(), type: StorageLocation.SCHOOL },
userId: new ObjectId().toHexString(),
filesStorageClientAdapterService: createMock<FilesStorageClientAdapterService>(),
targetSchoolId: new ObjectId().toHexString(),
};

const copyContext = new BoardNodeCopyContext(contextProps);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import { CopyElementType, CopyHelperService, CopyStatus, CopyStatusEnum } from '
import { StorageLocation } from '@modules/files-storage/interface';
import { ContextExternalToolService } from '@modules/tool/context-external-tool/service';
import { ToolConfig } from '@modules/tool/tool-config';
import { copyContextExternalToolRejectDataFactory } from '@modules/tool/context-external-tool/testing';
import { CopyContextExternalToolRejectData } from '@modules/tool/context-external-tool/domain';
import { ConfigService } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
import { setupEntities } from '@shared/testing';
import { FilesStorageClientAdapterService } from '@src/modules/files-storage-client';
import { CopyFileDto } from '@src/modules/files-storage-client/dto';
import { contextExternalToolFactory } from '@src/modules/tool/context-external-tool/testing';

import {
Card,
CollaborativeTextEditorElement,
Expand Down Expand Up @@ -103,6 +106,7 @@ describe(BoardNodeCopyService.name, () => {
targetStorageLocationReference: { id: new ObjectId().toHexString(), type: StorageLocation.SCHOOL },
userId: new ObjectId().toHexString(),
filesStorageClientAdapterService: createMock<FilesStorageClientAdapterService>(),
targetSchoolId: new ObjectId().toHexString(),
};

const copyContext = new BoardNodeCopyContext(contextProps);
Expand Down Expand Up @@ -420,23 +424,68 @@ describe(BoardNodeCopyService.name, () => {
const setupToolElement = () => {
const { copyContext, externalToolElement } = setupCopyEnabled();

const tool = contextExternalToolFactory.build();
const toolCopy = contextExternalToolFactory.build();
contextExternalToolService.findById.mockResolvedValueOnce(tool);
contextExternalToolService.copyContextExternalTool.mockResolvedValueOnce(toolCopy);
externalToolElement.contextExternalToolId = tool.id;
const contextTool = contextExternalToolFactory.build();
contextExternalToolService.findById.mockResolvedValueOnce(contextTool);
externalToolElement.contextExternalToolId = contextTool.id;

return { copyContext, externalToolElement, tool, toolCopy };
return { copyContext, externalToolElement, contextTool };
};

it('should copy the external tool', async () => {
const { copyContext, externalToolElement, tool, toolCopy } = setupToolElement();
describe('when the copying of context external tool is successful', () => {
const setupCopySuccess = () => {
const { copyContext, externalToolElement, contextTool } = setupToolElement();

const result = await service.copyExternalToolElement(externalToolElement, copyContext);
const copiedTool = contextExternalToolFactory.build();
contextExternalToolService.copyContextExternalTool.mockResolvedValue(copiedTool);

return { copyContext, externalToolElement, contextTool, copiedTool };
};

it('should return the copied entity as ExternalTool', async () => {
const { copyContext, externalToolElement, contextTool, copiedTool } = setupCopySuccess();

const result = await service.copyExternalToolElement(externalToolElement, copyContext);

expect(contextExternalToolService.copyContextExternalTool).toHaveBeenCalledWith(
contextTool,
result.copyEntity?.id,
copyContext.targetSchoolId
);
expect(result.copyEntity instanceof ExternalToolElement).toEqual(true);
expect((result.copyEntity as ExternalToolElement).contextExternalToolId).toEqual(copiedTool.id);
expect(result.type).toEqual(CopyElementType.EXTERNAL_TOOL_ELEMENT);
expect(result.status).toEqual(CopyStatusEnum.SUCCESS);
});
});

expect(contextExternalToolService.findById).toHaveBeenCalledWith(tool.id);
expect(contextExternalToolService.copyContextExternalTool).toHaveBeenCalledWith(tool, result.copyEntity?.id);
expect((result.copyEntity as ExternalToolElement).contextExternalToolId).toEqual(toolCopy.id);
describe('when the copying of context external tool is rejected', () => {
const setupCopyRejected = () => {
const { copyContext, externalToolElement, contextTool } = setupToolElement();

const copyRejectData = copyContextExternalToolRejectDataFactory.build();
const mockWithCorrectType = Object.create(
CopyContextExternalToolRejectData.prototype
) as CopyContextExternalToolRejectData;
Object.assign(mockWithCorrectType, { ...copyRejectData });
contextExternalToolService.copyContextExternalTool.mockResolvedValue(mockWithCorrectType);

return { copyContext, externalToolElement, contextTool };
};

it('should return the copied entity as DeletedElement', async () => {
const { externalToolElement, copyContext, contextTool } = setupCopyRejected();

const result = await service.copyExternalToolElement(externalToolElement, copyContext);

expect(contextExternalToolService.copyContextExternalTool).toHaveBeenCalledWith(
contextTool,
expect.any(String),
copyContext.targetSchoolId
);
expect(result.copyEntity instanceof DeletedElement).toEqual(true);
expect(result.type).toEqual(CopyElementType.EXTERNAL_TOOL_ELEMENT);
expect(result.status).toEqual(CopyStatusEnum.FAIL);
});
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ObjectId } from '@mikro-orm/mongodb';
import { CopyElementType, CopyHelperService, CopyStatus, CopyStatusEnum } from '@modules/copy-helper';
import { CopyFileDto } from '@modules/files-storage-client/dto';
import { ContextExternalToolService } from '@modules/tool/context-external-tool';
import { ContextExternalTool } from '@modules/tool/context-external-tool/domain';
import { ContextExternalTool, CopyContextExternalToolRejectData } from '@modules/tool/context-external-tool/domain';
import { ToolConfig } from '@modules/tool/tool-config';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
Expand All @@ -14,6 +14,7 @@ import {
CollaborativeTextEditorElement,
Column,
ColumnBoard,
ContentElementType,
DeletedElement,
DrawingElement,
ExternalToolElement,
Expand All @@ -30,6 +31,7 @@ import {
} from '../../domain';

export interface CopyContext {
targetSchoolId: EntityId;
copyFilesOfParent(sourceParentId: EntityId, targetParentId: EntityId): Promise<CopyFileDto[]>;
}

Expand Down Expand Up @@ -283,35 +285,60 @@ export class BoardNodeCopyService {
return Promise.resolve(result);
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
async copyExternalToolElement(original: ExternalToolElement, context: CopyContext): Promise<CopyStatus> {
const copy = new ExternalToolElement({
let copy: ExternalToolElement | DeletedElement;
copy = new ExternalToolElement({
...original.getProps(),
...this.buildSpecificProps([]),
});

let status: CopyStatusEnum;
if (this.configService.get('FEATURE_CTL_TOOLS_COPY_ENABLED') && original.contextExternalToolId) {
const linkedTool = await this.contextExternalToolService.findById(original.contextExternalToolId);
if (!this.configService.get('FEATURE_CTL_TOOLS_COPY_ENABLED') || !original.contextExternalToolId) {
const copyStatus: CopyStatus = {
copyEntity: copy,
type: CopyElementType.EXTERNAL_TOOL_ELEMENT,
status: CopyStatusEnum.SUCCESS,
};

if (linkedTool) {
const contextExternalToolCopy: ContextExternalTool =
await this.contextExternalToolService.copyContextExternalTool(linkedTool, copy.id);
return Promise.resolve(copyStatus);
}

copy.contextExternalToolId = contextExternalToolCopy.id;
const linkedTool = await this.contextExternalToolService.findById(original.contextExternalToolId);
if (!linkedTool) {
const copyStatus: CopyStatus = {
copyEntity: copy,
type: CopyElementType.EXTERNAL_TOOL_ELEMENT,
status: CopyStatusEnum.FAIL,
};

status = CopyStatusEnum.SUCCESS;
} else {
status = CopyStatusEnum.FAIL;
}
return copyStatus;
}

const contextToolCopyResult: ContextExternalTool | CopyContextExternalToolRejectData =
await this.contextExternalToolService.copyContextExternalTool(linkedTool, copy.id, context.targetSchoolId);

let copyStatus: CopyStatusEnum = CopyStatusEnum.SUCCESS;
if (contextToolCopyResult instanceof CopyContextExternalToolRejectData) {
copy = new DeletedElement({
id: new ObjectId().toHexString(),
path: copy.path,
level: copy.level,
position: copy.position,
children: [],
createdAt: new Date(),
updatedAt: new Date(),
deletedElementType: ContentElementType.EXTERNAL_TOOL,
title: contextToolCopyResult.externalToolName,
});

copyStatus = CopyStatusEnum.FAIL;
} else {
status = CopyStatusEnum.SUCCESS;
copy.contextExternalToolId = contextToolCopyResult.id;
}

const result: CopyStatus = {
copyEntity: copy,
type: CopyElementType.EXTERNAL_TOOL_ELEMENT,
MarvinOehlerkingCap marked this conversation as resolved.
Show resolved Hide resolved
status,
status: copyStatus,
};

return Promise.resolve(result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ describe(ColumnBoardCopyService.name, () => {
describe('copyColumnBoard', () => {
const setup = () => {
const userId = new ObjectId().toHexString();
const targetSchoolId = new ObjectId().toHexString();
const course = courseFactory.buildWithId();
const originalBoard = columnBoardFactory.build({
context: { id: course.id, type: BoardExternalReferenceType.Course },
Expand All @@ -86,6 +87,7 @@ describe(ColumnBoardCopyService.name, () => {
targetStorageLocationReference: { id: course.school.id, type: StorageLocation.SCHOOL },
userId,
copyTitle: 'Board Copy',
targetSchoolId,
};

return { originalBoard, userId, copyParams };
Expand Down Expand Up @@ -165,6 +167,7 @@ describe(ColumnBoardCopyService.name, () => {
describe('when the copy response is not a ColumnBoard', () => {
const setup = () => {
const userId = new ObjectId().toHexString();
const targetSchoolId = new ObjectId().toHexString();
const course = courseFactory.buildWithId();
const originalBoard = columnBoardFactory.build({
context: { id: course.id, type: BoardExternalReferenceType.Course },
Expand All @@ -178,6 +181,7 @@ describe(ColumnBoardCopyService.name, () => {
targetStorageLocationReference: { id: course.school.id, type: StorageLocation.SCHOOL },
userId,
copyTitle: 'Board Copy',
targetSchoolId,
};

return { originalBoard, userId, copyParams };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type CopyColumnBoardParams = {
targetStorageLocationReference: StorageLocationReference;
userId: EntityId;
copyTitle?: string;
targetSchoolId: EntityId;
};

@Injectable()
Expand All @@ -36,6 +37,7 @@ export class ColumnBoardCopyService {
targetStorageLocationReference: params.targetStorageLocationReference,
userId: params.userId,
filesStorageClientAdapterService: this.filesStorageClientAdapterService,
targetSchoolId: params.targetSchoolId,
});

const copyStatus = await this.boardNodeCopyService.copy(originalBoard, copyContext);
Expand Down
Loading
Loading