Skip to content

Commit

Permalink
EW-876: Adjust the course import to use multiple column boards. (#5045)
Browse files Browse the repository at this point in the history
  • Loading branch information
mkreuzkam-cap authored Jun 7, 2024
1 parent 576ba99 commit 0d47762
Show file tree
Hide file tree
Showing 7 changed files with 217 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export type CommonCartridgeFileParserOptions = {
};

export const DEFAULT_FILE_PARSER_OPTIONS: CommonCartridgeFileParserOptions = {
maxSearchDepth: 3,
maxSearchDepth: 5,
pathSeparator: '/',
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export class CommonCartridgeOrganizationVisitor {
}

private visit(element: SearchElement, queue: SearchElement[]): void {
element.element.querySelectorAll('item').forEach((child) => {
element.element.querySelectorAll(':scope > item').forEach((child) => {
queue.push({
depth: element.depth + 1,
path: `${element.path}${this.options.pathSeparator}${this.getElementIdentifier(child)}`,
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,40 @@ describe('CommonCartridgeImportMapper', () => {
});
});
});

describe('when organization is provided and withTitle is false', () => {
const setup = () => setupOrganization();

it('should set the title to an empty string', () => {
const { organization } = setup();

const result = sut.mapOrganizationToCard(organization, false);

expect(result).toEqual<CardInitProps>({
title: '',
height: 150,
});
});
});
});

// AI next 17 lines
describe('mapOrganizationToTextElement', () => {
describe('when organization is provided', () => {
const setup = () => setupOrganization();

it('should map organization to text element', () => {
const { organization } = setup();

const result = sut.mapOrganizationToTextElement(organization);

expect(result).toBeInstanceOf(RichTextContentBody);
expect(result).toEqual<RichTextContentBody>({
text: `<b>${organization.title}</b>`,
inputFormat: InputFormat.RICH_TEXT_CK5_SIMPLE,
});
});
});
});

describe('mapResourceTypeToContentElementType', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,21 @@ export class CommonCartridgeImportMapper {
};
}

public mapOrganizationToCard(organization: CommonCartridgeOrganizationProps): CardInitProps {
public mapOrganizationToCard(organization: CommonCartridgeOrganizationProps, withTitle = true): CardInitProps {
return {
title: organization.title,
title: withTitle ? organization.title : '',
height: 150,
};
}

public mapOrganizationToTextElement(organization: CommonCartridgeOrganizationProps): AnyElementContentBody {
const body = new RichTextContentBody();
body.text = `<b>${organization.title}</b>`;
body.inputFormat = InputFormat.RICH_TEXT_CK5_SIMPLE;

return body;
}

public mapResourceTypeToContentElementType(
resourceType: CommonCartridgeResourceTypeV1P1 | undefined
): ContentElementType | undefined {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,62 @@ describe('CommonCartridgeImportService', () => {
expect(sut).toBeDefined();
});

const setupEnvironment = async (filePath: string) => {
const user = userFactory.buildWithId();
const buffer = await readFile(filePath);

return { user, buffer };
};

describe('importFile', () => {
describe('when the common cartridge is valid', () => {
const setup = async () => {
const user = userFactory.buildWithId();
const buffer = await readFile(
'./apps/server/src/modules/common-cartridge/testing/assets/us_history_since_1877.imscc'
);
const setup = async () =>
setupEnvironment('./apps/server/src/modules/common-cartridge/testing/assets/us_history_since_1877.imscc');

it('should create a course', async () => {
const { user, buffer } = await setup();

await sut.importFile(user, buffer);

expect(courseServiceMock.create).toHaveBeenCalledTimes(1);
});

it('should create a column board', async () => {
const { user, buffer } = await setup();

await sut.importFile(user, buffer);

expect(columnBoardServiceMock.create).toHaveBeenCalledTimes(14);
});

it('should create columns', async () => {
const { user, buffer } = await setup();

await sut.importFile(user, buffer);

expect(columnServiceMock.create).toHaveBeenCalledTimes(103);
});

it('should create cards', async () => {
const { user, buffer } = await setup();

await sut.importFile(user, buffer);

expect(cardServiceMock.create).toHaveBeenCalled();
});

it('should create elements', async () => {
const { user, buffer } = await setup();

await sut.importFile(user, buffer);

expect(contentElementServiceMock.create).toHaveBeenCalled();
});
});

return { user, buffer };
};
describe('when the common cartridge is a valid dbc course', () => {
const setup = async () =>
setupEnvironment('./apps/server/src/modules/common-cartridge/testing/assets/dbc_course.imscc');

it('should create a course', async () => {
const { user, buffer } = await setup();
Expand All @@ -92,15 +138,15 @@ describe('CommonCartridgeImportService', () => {

await sut.importFile(user, buffer);

expect(columnBoardServiceMock.create).toHaveBeenCalledTimes(1);
expect(columnBoardServiceMock.create).toHaveBeenCalledTimes(3);
});

it('should create columns', async () => {
const { user, buffer } = await setup();

await sut.importFile(user, buffer);

expect(columnServiceMock.create).toHaveBeenCalledTimes(14);
expect(columnServiceMock.create).toHaveBeenCalledTimes(6);
});

it('should create cards', async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { Injectable } from '@nestjs/common';
import { BoardExternalReferenceType, BoardLayout, Column, ColumnBoard } from '@shared/domain/domainobject';
import {
BoardExternalReferenceType,
BoardLayout,
Card,
Column,
ColumnBoard,
ContentElementType,
} from '@shared/domain/domainobject';
import { Course, User } from '@shared/domain/entity';
import { CardService, ColumnBoardService, ColumnService, ContentElementService } from '@src/modules/board';
import {
Expand All @@ -22,36 +29,72 @@ export class CommonCartridgeImportService {
) {}

public async importFile(user: User, file: Buffer): Promise<void> {
const parser = new CommonCartridgeFileParser(file, {
maxSearchDepth: 1,
pathSeparator: DEFAULT_FILE_PARSER_OPTIONS.pathSeparator,
});
const parser = new CommonCartridgeFileParser(file, DEFAULT_FILE_PARSER_OPTIONS);
const course = new Course({ teachers: [user], school: user.school, name: parser.getTitle() });

await this.courseService.create(course);
await this.createColumnBoard(parser, course);
await this.createColumnBoards(parser, course);
}

private async createColumnBoard(parser: CommonCartridgeFileParser, course: Course): Promise<void> {
private async createColumnBoards(parser: CommonCartridgeFileParser, course: Course): Promise<void> {
const organizations = parser.getOrganizations();
const boards = organizations.filter((organization) => organization.pathDepth === 0);

for await (const board of boards) {
await this.createColumnBoard(parser, course, board, organizations);
}
}

private async createColumnBoard(
parser: CommonCartridgeFileParser,
course: Course,
boardProps: CommonCartridgeOrganizationProps,
organizations: CommonCartridgeOrganizationProps[]
): Promise<void> {
const columnBoard = await this.columnBoardService.create(
{
type: BoardExternalReferenceType.Course,
id: course.id,
},
BoardLayout.COLUMNS,
parser.getTitle() || ''
boardProps.title || ''
);

await this.createColumns(parser, columnBoard);
await this.createColumns(parser, columnBoard, boardProps, organizations);
}

private async createColumns(parser: CommonCartridgeFileParser, columnBoard: ColumnBoard): Promise<void> {
const organizations = parser.getOrganizations();
const columns = organizations.filter((organization) => organization.pathDepth === 0);
private async createColumns(
parser: CommonCartridgeFileParser,
columnBoard: ColumnBoard,
boardProps: CommonCartridgeOrganizationProps,
organizations: CommonCartridgeOrganizationProps[]
): Promise<void> {
const columnsWithResource = organizations.filter(
(organization) =>
organization.pathDepth === 1 && organization.path.startsWith(boardProps.identifier) && organization.isResource
);

for await (const column of columns) {
await this.createColumn(parser, columnBoard, column, organizations);
for await (const columnWithResource of columnsWithResource) {
await this.createColumnWithResource(parser, columnBoard, columnWithResource);
}

const columnsWithoutResource = organizations.filter(
(organization) =>
organization.pathDepth === 1 && organization.path.startsWith(boardProps.identifier) && !organization.isResource
);

for await (const columnWithoutResource of columnsWithoutResource) {
await this.createColumn(parser, columnBoard, columnWithoutResource, organizations);
}
}

private async createColumnWithResource(
parser: CommonCartridgeFileParser,
columnBoard: ColumnBoard,
columnProps: CommonCartridgeOrganizationProps
): Promise<void> {
const column = await this.columnService.create(columnBoard, this.mapper.mapOrganizationToColumn(columnProps));
await this.createCardWithElement(parser, column, columnProps, false);
}

private async createColumn(
Expand All @@ -62,21 +105,33 @@ export class CommonCartridgeImportService {
): Promise<void> {
const column = await this.columnService.create(columnBoard, this.mapper.mapOrganizationToColumn(columnProps));
const cards = organizations.filter(
(organization) =>
organization.pathDepth === 1 && organization.path.startsWith(columnProps.identifier) && organization.isResource
(organization) => organization.pathDepth === 2 && organization.path.startsWith(columnProps.path)
);

for await (const card of cards) {
await this.createCard(parser, column, card);
const cardsWithResource = cards.filter((card) => card.isResource);

for await (const card of cardsWithResource) {
await this.createCardWithElement(parser, column, card, true);
}

const cardsWithoutResource = cards.filter((card) => !card.isResource);

for await (const card of cardsWithoutResource) {
await this.createCard(parser, column, card, organizations);
}
}

private async createCard(
private async createCardWithElement(
parser: CommonCartridgeFileParser,
column: Column,
cardProps: CommonCartridgeOrganizationProps
cardProps: CommonCartridgeOrganizationProps,
withTitle = true
): Promise<void> {
const card = await this.cardService.create(column, undefined, this.mapper.mapOrganizationToCard(cardProps));
const card = await this.cardService.create(
column,
undefined,
this.mapper.mapOrganizationToCard(cardProps, withTitle)
);
const resource = parser.getResource(cardProps);
const contentElementType = this.mapper.mapResourceTypeToContentElementType(resource?.type);

Expand All @@ -87,4 +142,44 @@ export class CommonCartridgeImportService {
await this.contentElementService.update(contentElement, contentElementBody);
}
}

private async createCard(
parser: CommonCartridgeFileParser,
column: Column,
cardProps: CommonCartridgeOrganizationProps,
organizations: CommonCartridgeOrganizationProps[]
) {
const card = await this.cardService.create(column, undefined, this.mapper.mapOrganizationToCard(cardProps, true));

const cardElements = organizations.filter(
(organization) => organization.pathDepth >= 3 && organization.path.startsWith(cardProps.path)
);

for await (const cardElement of cardElements) {
await this.createCardElement(parser, card, cardElement);
}
}

private async createCardElement(
parser: CommonCartridgeFileParser,
card: Card,
cardElementProps: CommonCartridgeOrganizationProps
) {
if (cardElementProps.isResource) {
const resource = parser.getResource(cardElementProps);
const contentElementType = this.mapper.mapResourceTypeToContentElementType(resource?.type);

if (resource && contentElementType) {
const contentElement = await this.contentElementService.create(card, contentElementType);
const contentElementBody = this.mapper.mapResourceToContentElementBody(resource);

await this.contentElementService.update(contentElement, contentElementBody);
}
} else {
const contentElement = await this.contentElementService.create(card, ContentElementType.RICH_TEXT);
const contentElementBody = this.mapper.mapOrganizationToTextElement(cardElementProps);

await this.contentElementService.update(contentElement, contentElementBody);
}
}
}

0 comments on commit 0d47762

Please sign in to comment.