From 9a8caf7d63fd270c61be6a8e62ef6c89aa3ec628 Mon Sep 17 00:00:00 2001 From: MBergCap Date: Wed, 9 Oct 2024 12:19:28 +0200 Subject: [PATCH 01/23] add preferred tool endpoint --- .../response/prefered-tool-list.response.ts | 11 ++++++++ .../dto/response/prefered-tool.response.ts | 19 ++++++++++++++ .../tool-configuration.controller.ts | 25 +++++++++++++++++++ .../mapper/tool-configuration.mapper.ts | 22 ++++++++++++++++ 4 files changed, 77 insertions(+) create mode 100644 apps/server/src/modules/tool/external-tool/controller/dto/response/prefered-tool-list.response.ts create mode 100644 apps/server/src/modules/tool/external-tool/controller/dto/response/prefered-tool.response.ts diff --git a/apps/server/src/modules/tool/external-tool/controller/dto/response/prefered-tool-list.response.ts b/apps/server/src/modules/tool/external-tool/controller/dto/response/prefered-tool-list.response.ts new file mode 100644 index 00000000000..8e0693a5ab1 --- /dev/null +++ b/apps/server/src/modules/tool/external-tool/controller/dto/response/prefered-tool-list.response.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { PreferedToolResponse } from '@modules/tool/external-tool/controller/dto/response/prefered-tool.response'; + +export class PreferedToolListResponse { + @ApiProperty({ type: [PreferedToolResponse] }) + data: PreferedToolResponse[]; + + constructor(data: PreferedToolResponse[]) { + this.data = data; + } +} diff --git a/apps/server/src/modules/tool/external-tool/controller/dto/response/prefered-tool.response.ts b/apps/server/src/modules/tool/external-tool/controller/dto/response/prefered-tool.response.ts new file mode 100644 index 00000000000..08208992ea1 --- /dev/null +++ b/apps/server/src/modules/tool/external-tool/controller/dto/response/prefered-tool.response.ts @@ -0,0 +1,19 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { EntityId } from '@shared/domain/types'; + +export class PreferedToolResponse { + @ApiProperty() + schoolExternalToolId: EntityId; + + @ApiProperty() + name: string; + + @ApiProperty() + icon: string; + + constructor(configuration: PreferedToolResponse) { + this.schoolExternalToolId = configuration.schoolExternalToolId; + this.name = configuration.name; + this.icon = configuration.icon; + } +} diff --git a/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts b/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts index 7b90edab1df..4e5ca196d29 100644 --- a/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts +++ b/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts @@ -8,6 +8,7 @@ import { ApiTags, ApiUnauthorizedResponse, } from '@nestjs/swagger'; +import { PreferedToolListResponse } from '@modules/tool/external-tool/controller/dto/response/prefered-tool-list.response'; import { ToolContextType } from '../../common/enum'; import { ExternalTool } from '../domain'; import { ToolConfigurationMapper } from '../mapper/tool-configuration.mapper'; @@ -95,6 +96,30 @@ export class ToolConfigurationController { return mapped; } + @Get(':contextType/:contextId/preferred-tools') + @ApiForbiddenResponse() + @ApiOperation({ summary: 'Lists all preferred tools that can be added for a given context' }) + @ApiOkResponse({ + description: 'List of preferred tools for a context', + type: ContextExternalToolConfigurationTemplateListResponse, + }) + public async getPreferedTools( + @CurrentUser() currentUser: ICurrentUser, + @Param() params: ContextRefParams + ): Promise { + const prefferedTools: ContextExternalToolTemplateInfo[] = + await this.externalToolConfigurationUc.getPreferedToolsForContext( + currentUser.userId, + currentUser.schoolId, + params.contextId, + params.contextType + ); + + const mapped: PreferedToolListResponse = ToolConfigurationMapper.mapToPreferedToolListResponse(prefferedTools); + + return mapped; + } + @Get('school-external-tools/:schoolExternalToolId/configuration-template') @ApiUnauthorizedResponse() @ApiForbiddenResponse() diff --git a/apps/server/src/modules/tool/external-tool/mapper/tool-configuration.mapper.ts b/apps/server/src/modules/tool/external-tool/mapper/tool-configuration.mapper.ts index 37d67c810bc..5782e8a35f6 100644 --- a/apps/server/src/modules/tool/external-tool/mapper/tool-configuration.mapper.ts +++ b/apps/server/src/modules/tool/external-tool/mapper/tool-configuration.mapper.ts @@ -1,3 +1,5 @@ +import { PreferedToolListResponse } from '@modules/tool/external-tool/controller/dto/response/prefered-tool-list.response'; +import { PreferedToolResponse } from '@modules/tool/external-tool/controller/dto/response/prefered-tool.response'; import { ToolContextType } from '../../common/enum'; import { ContextExternalToolConfigurationTemplateListResponse, @@ -77,4 +79,24 @@ export class ToolConfigurationMapper { return mappedTypes; } + + static mapToPreferedToolListResponse(preferedTools: ContextExternalToolTemplateInfo[]): PreferedToolListResponse { + const mappedTools = preferedTools.map((tool): PreferedToolResponse => this.mapToPreferedToolResponse(tool)); + + const mapped = new PreferedToolListResponse(mappedTools); + + return mapped; + } + + static mapToPreferedToolResponse(preferedTool: ContextExternalToolTemplateInfo): PreferedToolResponse { + const { externalTool, schoolExternalTool } = preferedTool; + + const mapped = new PreferedToolResponse({ + schoolExternalToolId: schoolExternalTool.id ?? '', + name: externalTool.name, + icon: externalTool.iconName ?? '', + }); + + return mapped; + } } From 229f358d25631b78d939da9c4f51ddac28505ed0 Mon Sep 17 00:00:00 2001 From: MBergCap Date: Wed, 9 Oct 2024 12:19:45 +0200 Subject: [PATCH 02/23] add preferred tool uc --- .../uc/external-tool-configuration.uc.ts | 84 +++++++++++++++---- 1 file changed, 68 insertions(+), 16 deletions(-) diff --git a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts index 5937b9d9544..2ca63fbc43e 100644 --- a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts +++ b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts @@ -98,25 +98,12 @@ export class ExternalToolConfigurationUc { const context: AuthorizationContext = AuthorizationContextBuilder.read([Permission.CONTEXT_TOOL_ADMIN]); await this.ensureContextPermissions(user, contextExternalToolsInUse, context); - let availableToolsForContext: ContextExternalToolTemplateInfo[] = - this.externalToolConfigurationService.filterForAvailableExternalTools(externalTools.data, schoolExternalTools); - - availableToolsForContext = this.externalToolConfigurationService.filterForContextRestrictions( - availableToolsForContext, + const availableToolsForContext: ContextExternalToolTemplateInfo[] = this.prepareAvailableToolsForContext( + externalTools.data, + schoolExternalTools, contextType ); - availableToolsForContext.forEach((toolTemplateInfo) => { - this.externalToolConfigurationService.filterParametersForScope( - toolTemplateInfo.externalTool, - CustomParameterScope.CONTEXT - ); - }); - - availableToolsForContext.forEach((toolTemplateInfo) => { - toolTemplateInfo.externalTool.logoUrl = this.externalToolLogoService.buildLogoUrl(toolTemplateInfo.externalTool); - }); - return availableToolsForContext; } @@ -172,6 +159,71 @@ export class ExternalToolConfigurationUc { }; } + public async getPreferedToolsForContext( + userId: EntityId, + schoolId: EntityId, + contextId: EntityId, + contextType: ToolContextType + ): Promise { + const [externalTools, contextExternalToolsInUse]: [Page, ContextExternalTool[]] = await Promise.all([ + this.externalToolService.findExternalTools({ isPreferred: true }), + this.contextExternalToolService.findContextExternalTools({ + context: { id: contextId, type: contextType }, + }), + ]); + + const user: User = await this.authorizationService.getUserWithPermissions(userId); + + const context: AuthorizationContext = AuthorizationContextBuilder.read([Permission.CONTEXT_TOOL_ADMIN]); + await this.ensureContextPermissions(user, contextExternalToolsInUse, context); + + const schoolExternalTools: SchoolExternalTool[] = ( + await Promise.all( + externalTools.data.map((externalTool) => + this.schoolExternalToolService.findSchoolExternalTools({ + toolId: externalTool.id, + schoolId, + }) + ) + ) + ).flat(); + + const preferedTools: ContextExternalToolTemplateInfo[] = this.prepareAvailableToolsForContext( + externalTools.data, + schoolExternalTools, + contextType + ); + + return preferedTools; + } + + private prepareAvailableToolsForContext( + externalTools: ExternalTool[], + schoolExternalTools: SchoolExternalTool[], + contextType: ToolContextType + ): ContextExternalToolTemplateInfo[] { + let availableToolsForContext: ContextExternalToolTemplateInfo[] = + this.externalToolConfigurationService.filterForAvailableExternalTools(externalTools, schoolExternalTools); + + availableToolsForContext = this.externalToolConfigurationService.filterForContextRestrictions( + availableToolsForContext, + contextType + ); + + availableToolsForContext.forEach((toolTemplateInfo) => { + this.externalToolConfigurationService.filterParametersForScope( + toolTemplateInfo.externalTool, + CustomParameterScope.CONTEXT + ); + }); + + availableToolsForContext.forEach((toolTemplateInfo) => { + toolTemplateInfo.externalTool.logoUrl = this.externalToolLogoService.buildLogoUrl(toolTemplateInfo.externalTool); + }); + + return availableToolsForContext; + } + private async ensureSchoolPermissions( user: User, tools: SchoolExternalTool[], From a2cf75dd3a920d0e1784a0f6200783d152da584d Mon Sep 17 00:00:00 2001 From: MBergCap Date: Wed, 9 Oct 2024 12:41:47 +0200 Subject: [PATCH 03/23] add apiProperty description --- .../dto/response/prefered-tool.response.ts | 18 ++++++++++-------- .../tool-configuration.controller.ts | 4 ++-- .../mapper/tool-configuration.mapper.ts | 2 +- .../uc/external-tool-configuration.uc.ts | 4 ++-- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/apps/server/src/modules/tool/external-tool/controller/dto/response/prefered-tool.response.ts b/apps/server/src/modules/tool/external-tool/controller/dto/response/prefered-tool.response.ts index 08208992ea1..8c2be418404 100644 --- a/apps/server/src/modules/tool/external-tool/controller/dto/response/prefered-tool.response.ts +++ b/apps/server/src/modules/tool/external-tool/controller/dto/response/prefered-tool.response.ts @@ -1,19 +1,21 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { EntityId } from '@shared/domain/types'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; export class PreferedToolResponse { - @ApiProperty() - schoolExternalToolId: EntityId; + @ApiProperty({ type: String, description: 'Id of the school external tool' }) + schoolExternalToolId: string; - @ApiProperty() + @ApiProperty({ type: String, description: 'Name of the external tool' }) name: string; - @ApiProperty() - icon: string; + @ApiPropertyOptional({ + type: String, + description: 'Name of the icon to be rendered when displaying it as a preferred tool', + }) + iconName?: string; constructor(configuration: PreferedToolResponse) { this.schoolExternalToolId = configuration.schoolExternalToolId; this.name = configuration.name; - this.icon = configuration.icon; + this.iconName = configuration.iconName; } } diff --git a/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts b/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts index 4e5ca196d29..499ab6f16e1 100644 --- a/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts +++ b/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts @@ -101,9 +101,9 @@ export class ToolConfigurationController { @ApiOperation({ summary: 'Lists all preferred tools that can be added for a given context' }) @ApiOkResponse({ description: 'List of preferred tools for a context', - type: ContextExternalToolConfigurationTemplateListResponse, + type: PreferedToolListResponse, }) - public async getPreferedTools( + public async getPreferedToolsForContext( @CurrentUser() currentUser: ICurrentUser, @Param() params: ContextRefParams ): Promise { diff --git a/apps/server/src/modules/tool/external-tool/mapper/tool-configuration.mapper.ts b/apps/server/src/modules/tool/external-tool/mapper/tool-configuration.mapper.ts index 5782e8a35f6..83fe7a1f819 100644 --- a/apps/server/src/modules/tool/external-tool/mapper/tool-configuration.mapper.ts +++ b/apps/server/src/modules/tool/external-tool/mapper/tool-configuration.mapper.ts @@ -94,7 +94,7 @@ export class ToolConfigurationMapper { const mapped = new PreferedToolResponse({ schoolExternalToolId: schoolExternalTool.id ?? '', name: externalTool.name, - icon: externalTool.iconName ?? '', + iconName: externalTool.iconName, }); return mapped; diff --git a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts index 2ca63fbc43e..06e387d7d45 100644 --- a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts +++ b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts @@ -165,7 +165,7 @@ export class ExternalToolConfigurationUc { contextId: EntityId, contextType: ToolContextType ): Promise { - const [externalTools, contextExternalToolsInUse]: [Page, ContextExternalTool[]] = await Promise.all([ + const [externalTools, contextExternalTools]: [Page, ContextExternalTool[]] = await Promise.all([ this.externalToolService.findExternalTools({ isPreferred: true }), this.contextExternalToolService.findContextExternalTools({ context: { id: contextId, type: contextType }, @@ -175,7 +175,7 @@ export class ExternalToolConfigurationUc { const user: User = await this.authorizationService.getUserWithPermissions(userId); const context: AuthorizationContext = AuthorizationContextBuilder.read([Permission.CONTEXT_TOOL_ADMIN]); - await this.ensureContextPermissions(user, contextExternalToolsInUse, context); + await this.ensureContextPermissions(user, contextExternalTools, context); const schoolExternalTools: SchoolExternalTool[] = ( await Promise.all( From 319635294d15640293c3642d27d2bb4bb78a3d19 Mon Sep 17 00:00:00 2001 From: MBergCap Date: Wed, 9 Oct 2024 14:19:12 +0200 Subject: [PATCH 04/23] add seed data --- backup/setup/external-tools.json | 57 ++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/backup/setup/external-tools.json b/backup/setup/external-tools.json index 120dfa0f8dd..83ac0f44606 100644 --- a/backup/setup/external-tools.json +++ b/backup/setup/external-tools.json @@ -997,5 +997,62 @@ "board-element", "media-board" ] + }, + { + "_id": { + "$oid": "670670862800fa38ddff1227" + }, + "createdAt": { + "$date": "2023-04-27T09:51:15.592Z" + }, + "updatedAt": { + "$date": "2023-04-27T09:51:15.592Z" + }, + "name": "CY Test Tool Preferred", + "url": "https://google.de/", + "config_type": "basic", + "config_baseUrl": "https://google.de/", + "parameters": [], + "isHidden": false, + "openNewTab": true, + "version": 1, + "isDeactivated": false, + "restrictToContexts": [], + "isPreferred": true, + "iconName": "magnify" + }, + { + "_id": { + "$oid": "670670bc2800fa38ddff122a" + }, + "createdAt": { + "$date": "2023-04-27T09:51:15.592Z" + }, + "updatedAt": { + "$date": "2023-04-27T09:51:15.592Z" + }, + "name": "CY Test Tool Preferred With Param", + "url": "https://google.de/", + "config_type": "basic", + "config_baseUrl": "https://google.de/", + "parameters": [ + { + "name": "contextParam", + "displayName": "context parameter", + "description": "", + "scope": "context", + "location": "query", + "type": "string", + "isOptional": false, + "isProtected": false + } + ], + "isHidden": false, + "openNewTab": true, + "version": 1, + "isDeactivated": false, + "restrictToContexts": [], + "isPreferred": true, + "iconName": "magnify-plus-outline" } ] From 7ce331de0e2859896a8421abe6c057376742d220 Mon Sep 17 00:00:00 2001 From: MBergCap Date: Wed, 9 Oct 2024 15:59:37 +0200 Subject: [PATCH 05/23] add uc test --- .../testing/external-tool.factory.ts | 1 + .../uc/external-tool-configuration.uc.spec.ts | 175 +++++++++++++++++- 2 files changed, 174 insertions(+), 2 deletions(-) diff --git a/apps/server/src/modules/tool/external-tool/testing/external-tool.factory.ts b/apps/server/src/modules/tool/external-tool/testing/external-tool.factory.ts index d1bff9ba0ef..f1b07fb7d78 100644 --- a/apps/server/src/modules/tool/external-tool/testing/external-tool.factory.ts +++ b/apps/server/src/modules/tool/external-tool/testing/external-tool.factory.ts @@ -151,5 +151,6 @@ export const externalToolFactory = ExternalToolFactory.define(ExternalTool, ({ s createdAt: new Date(2020, 1, 1), restrictToContexts: undefined, isPreferred: false, + iconName: undefined, }; }); diff --git a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.spec.ts b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.spec.ts index 38af6c85ddb..b69b0dd33f9 100644 --- a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.spec.ts +++ b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.spec.ts @@ -237,7 +237,7 @@ describe('ExternalToolConfigurationUc', () => { }); }); - describe('when getting the list of external tools that can be added to a school', () => { + describe('when getting the list of external tools that can be added to a context', () => { const setup = () => { const user: User = userFactory.build(); const hiddenTool: ExternalTool = externalToolFactory.buildWithId({ isHidden: true }); @@ -409,7 +409,7 @@ describe('ExternalToolConfigurationUc', () => { }; }; - it('should allow to add one tool multiple times to a school', async () => { + it('should allow to add one tool multiple times to a context', async () => { const { usedTool, usedSchoolExternalTool } = setup(); const availableTools = await uc.getAvailableToolsForContext( @@ -731,4 +731,175 @@ describe('ExternalToolConfigurationUc', () => { }); }); }); + + describe('getPreferedToolsForContext', () => { + describe('when the user has insufficient permission', () => { + const setup = () => { + const user: User = userFactory.build(); + const contextExternalTool: ContextExternalTool = contextExternalToolFactory.build(); + + authorizationService.getUserWithPermissions.mockResolvedValue(user); + toolPermissionHelper.ensureContextPermissions.mockRejectedValue(new ForbiddenException()); + contextExternalToolService.findContextExternalTools.mockResolvedValue([contextExternalTool]); + + return { user, contextExternalTool }; + }; + + it('should fail when authorizationService throws ForbiddenException', async () => { + const { user, contextExternalTool } = setup(); + + const func = async () => + uc.getAvailableToolsForContext('userId', 'schoolId', 'contextId', ToolContextType.COURSE); + + await expect(func).rejects.toThrow(ForbiddenException); + expect(toolPermissionHelper.ensureContextPermissions).toHaveBeenCalledWith( + user, + contextExternalTool, + AuthorizationContextBuilder.read([Permission.CONTEXT_TOOL_ADMIN]) + ); + }); + }); + + describe('when getting the list of preferred external tools that can be added to a context', () => { + const setup = () => { + const user: User = userFactory.build(); + const hiddenExternalTool: ExternalTool = externalToolFactory.build({ isHidden: true, isPreferred: true }); + const usedExternalTool: ExternalTool = externalToolFactory.build({ isPreferred: true }); + const unusedExternalTool: ExternalTool = externalToolFactory.build({ isPreferred: true }); + const externalToolWithoutSchoolTool: ExternalTool = externalToolFactory.build({ isPreferred: true }); + + const usedSchoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.build({ + toolId: usedExternalTool.id, + }); + const unusedSchoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.build({ + toolId: unusedExternalTool.id, + }); + + const usedContextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId({ + schoolToolRef: { schoolToolId: usedSchoolExternalTool.id }, + }); + + externalToolService.findExternalTools.mockResolvedValue( + new Page( + [hiddenExternalTool, usedExternalTool, unusedExternalTool, externalToolWithoutSchoolTool], + 4 + ) + ); + schoolExternalToolService.findSchoolExternalTools.mockResolvedValue([ + usedSchoolExternalTool, + unusedSchoolExternalTool, + ]); + contextExternalToolService.findContextExternalTools.mockResolvedValue([usedContextExternalTool]); + authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); + + externalToolConfigurationService.filterForAvailableExternalTools.mockReturnValue([ + { externalTool: usedExternalTool, schoolExternalTool: usedSchoolExternalTool }, + ]); + externalToolConfigurationService.filterForContextRestrictions.mockReturnValue([ + { externalTool: usedExternalTool, schoolExternalTool: usedSchoolExternalTool }, + ]); + + return { + user, + usedExternalTool, + usedSchoolExternalTool, + usedContextExternalTool, + }; + }; + + it('should call the toolPermissionHelper with CONTEXT_TOOL_ADMIN permission', async () => { + const { user, usedContextExternalTool } = setup(); + + await uc.getAvailableToolsForContext('userId', 'schoolId', 'contextId', ToolContextType.BOARD_ELEMENT); + + expect(toolPermissionHelper.ensureContextPermissions).toHaveBeenCalledWith( + user, + usedContextExternalTool, + AuthorizationContextBuilder.read([Permission.CONTEXT_TOOL_ADMIN]) + ); + }); + + it('should call externalToolLogoService', async () => { + const { usedExternalTool } = setup(); + + await uc.getAvailableToolsForContext('userId', 'schoolId', 'contextId', ToolContextType.BOARD_ELEMENT); + + expect(logoService.buildLogoUrl).toHaveBeenCalledWith(usedExternalTool); + }); + + it('should filter for restricted contexts', async () => { + const { usedExternalTool, usedSchoolExternalTool } = setup(); + + await uc.getAvailableToolsForContext('userId', 'schoolId', 'contextId', ToolContextType.BOARD_ELEMENT); + + expect(externalToolConfigurationService.filterForContextRestrictions).toHaveBeenCalledWith( + [{ externalTool: usedExternalTool, schoolExternalTool: usedSchoolExternalTool }], + ToolContextType.BOARD_ELEMENT + ); + }); + + it('should call filterParametersForScope', async () => { + const { usedExternalTool } = setup(); + + await uc.getAvailableToolsForContext('userId', 'schoolId', 'contextId', ToolContextType.BOARD_ELEMENT); + + expect(externalToolConfigurationService.filterParametersForScope).toHaveBeenCalledWith( + usedExternalTool, + CustomParameterScope.CONTEXT + ); + }); + }); + + describe('when configuration of context external tools is enabled', () => { + const setup = () => { + const externalTool: ExternalTool = externalToolFactory.build({ + isPreferred: true, + iconName: 'iconName', + }); + + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.build({ + toolId: externalTool.id, + parameters: [], + }); + + const contextExternalTool: ContextExternalTool = contextExternalToolFactory.build({ + schoolToolRef: { schoolToolId: schoolExternalTool.id }, + }); + + externalToolService.findExternalTools.mockResolvedValue(new Page([externalTool], 1)); + schoolExternalToolService.findSchoolExternalTools.mockResolvedValue([schoolExternalTool]); + contextExternalToolService.findContextExternalTools.mockResolvedValue([contextExternalTool]); + + externalToolConfigurationService.filterForAvailableExternalTools.mockReturnValue([ + { externalTool, schoolExternalTool }, + ]); + externalToolConfigurationService.filterForContextRestrictions.mockReturnValue([ + { externalTool, schoolExternalTool }, + ]); + + return { + externalTool, + schoolExternalTool, + }; + }; + + it('should allow to add a preferred tool to a given context', async () => { + const { externalTool, schoolExternalTool } = setup(); + + const preferredTools = await uc.getPreferedToolsForContext( + 'userId', + 'schoolId', + 'contextId', + ToolContextType.BOARD_ELEMENT + ); + + expect(preferredTools).toEqual([ + { + externalTool, + schoolExternalTool, + }, + ]); + }); + }); + }); }); From fce2ea84200786bb32bcceff850ce4ee21bc1408 Mon Sep 17 00:00:00 2001 From: MBergCap Date: Wed, 9 Oct 2024 16:06:19 +0200 Subject: [PATCH 06/23] fix --- .../external-tool/uc/external-tool-configuration.uc.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.spec.ts b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.spec.ts index b69b0dd33f9..bbb1bf6663d 100644 --- a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.spec.ts +++ b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.spec.ts @@ -237,7 +237,7 @@ describe('ExternalToolConfigurationUc', () => { }); }); - describe('when getting the list of external tools that can be added to a context', () => { + describe('when getting the list of school external tools that can be added to a context', () => { const setup = () => { const user: User = userFactory.build(); const hiddenTool: ExternalTool = externalToolFactory.buildWithId({ isHidden: true }); From 501ded0eb983db7691b2fa4835366d350200f117 Mon Sep 17 00:00:00 2001 From: MBergCap Date: Wed, 9 Oct 2024 16:31:07 +0200 Subject: [PATCH 07/23] fix --- .../external-tool/controller/tool-configuration.controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts b/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts index 499ab6f16e1..252b41fd824 100644 --- a/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts +++ b/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts @@ -107,7 +107,7 @@ export class ToolConfigurationController { @CurrentUser() currentUser: ICurrentUser, @Param() params: ContextRefParams ): Promise { - const prefferedTools: ContextExternalToolTemplateInfo[] = + const preferedTools: ContextExternalToolTemplateInfo[] = await this.externalToolConfigurationUc.getPreferedToolsForContext( currentUser.userId, currentUser.schoolId, @@ -115,7 +115,7 @@ export class ToolConfigurationController { params.contextType ); - const mapped: PreferedToolListResponse = ToolConfigurationMapper.mapToPreferedToolListResponse(prefferedTools); + const mapped: PreferedToolListResponse = ToolConfigurationMapper.mapToPreferedToolListResponse(preferedTools); return mapped; } From 6193289f6f2bd583fcfc61bf5a0fb048c6db7aff Mon Sep 17 00:00:00 2001 From: MBergCap Date: Thu, 10 Oct 2024 10:23:57 +0200 Subject: [PATCH 08/23] add feature flag FEATURE_PREFERRED_CTL_TOOLS_ENABLED --- .../internal/board-node-copy-specific.service.spec.ts | 1 + apps/server/src/modules/server/admin-api-server.config.ts | 1 + apps/server/src/modules/server/api/dto/config.response.ts | 4 ++++ apps/server/src/modules/server/api/test/server.api.spec.ts | 1 + apps/server/src/modules/server/server.config.ts | 1 + apps/server/src/modules/tool/tool-config.ts | 1 + config/default.schema.json | 5 +++++ 7 files changed, 14 insertions(+) diff --git a/apps/server/src/modules/board/service/internal/board-node-copy-specific.service.spec.ts b/apps/server/src/modules/board/service/internal/board-node-copy-specific.service.spec.ts index 3db0d06fa2a..1a82f545a59 100644 --- a/apps/server/src/modules/board/service/internal/board-node-copy-specific.service.spec.ts +++ b/apps/server/src/modules/board/service/internal/board-node-copy-specific.service.spec.ts @@ -55,6 +55,7 @@ describe(BoardNodeCopyService.name, () => { CTL_TOOLS_RELOAD_TIME_MS: 0, FILES_STORAGE__SERVICE_BASE_URL: '', CTL_TOOLS__PREFERRED_TOOLS_LIMIT: 10, + FEATURE_PREFERRED_CTL_TOOLS_ENABLED: false, }; let contextExternalToolService: DeepMocked; let copyHelperService: DeepMocked; diff --git a/apps/server/src/modules/server/admin-api-server.config.ts b/apps/server/src/modules/server/admin-api-server.config.ts index 336d4deba6a..5976a27a271 100644 --- a/apps/server/src/modules/server/admin-api-server.config.ts +++ b/apps/server/src/modules/server/admin-api-server.config.ts @@ -66,6 +66,7 @@ const config: AdminApiServerConfig = { FEATURE_IDENTITY_MANAGEMENT_LOGIN_ENABLED: Configuration.get('FEATURE_IDENTITY_MANAGEMENT_LOGIN_ENABLED') as boolean, FEATURE_IDENTITY_MANAGEMENT_STORE_ENABLED: Configuration.get('FEATURE_IDENTITY_MANAGEMENT_STORE_ENABLED') as boolean, CTL_TOOLS__PREFERRED_TOOLS_LIMIT: Configuration.get('CTL_TOOLS__PREFERRED_TOOLS_LIMIT') as number, + FEATURE_PREFERRED_CTL_TOOLS_ENABLED: Configuration.get('FEATURE_PREFERRED_CTL_TOOLS_ENABLED') as boolean, }; export const adminApiServerConfig = () => config; diff --git a/apps/server/src/modules/server/api/dto/config.response.ts b/apps/server/src/modules/server/api/dto/config.response.ts index d92b29aed4d..7e9d3bab9db 100644 --- a/apps/server/src/modules/server/api/dto/config.response.ts +++ b/apps/server/src/modules/server/api/dto/config.response.ts @@ -38,6 +38,9 @@ export class ConfigResponse { @ApiProperty() FEATURE_CTL_TOOLS_COPY_ENABLED: boolean; + @ApiProperty() + FEATURE_PREFERRED_CTL_TOOLS_ENABLED: boolean; + @ApiProperty() FEATURE_SHOW_MIGRATION_WIZARD: boolean; @@ -288,6 +291,7 @@ export class ConfigResponse { this.FEATURE_SHOW_NEW_CLASS_VIEW_ENABLED = config.FEATURE_SHOW_NEW_CLASS_VIEW_ENABLED; this.FEATURE_SHOW_NEW_ROOMS_VIEW_ENABLED = config.FEATURE_SHOW_NEW_ROOMS_VIEW_ENABLED; this.FEATURE_CTL_TOOLS_COPY_ENABLED = config.FEATURE_CTL_TOOLS_COPY_ENABLED; + this.FEATURE_PREFERRED_CTL_TOOLS_ENABLED = config.FEATURE_PREFERRED_CTL_TOOLS_ENABLED; this.FEATURE_SHOW_MIGRATION_WIZARD = config.FEATURE_SHOW_MIGRATION_WIZARD; this.MIGRATION_WIZARD_DOCUMENTATION_LINK = config.MIGRATION_WIZARD_DOCUMENTATION_LINK; this.FEATURE_TLDRAW_ENABLED = config.FEATURE_TLDRAW_ENABLED; diff --git a/apps/server/src/modules/server/api/test/server.api.spec.ts b/apps/server/src/modules/server/api/test/server.api.spec.ts index 5627d34e17e..99c36f60eb4 100644 --- a/apps/server/src/modules/server/api/test/server.api.spec.ts +++ b/apps/server/src/modules/server/api/test/server.api.spec.ts @@ -55,6 +55,7 @@ describe('Server Controller (API)', () => { 'FEATURE_COURSE_SHARE', 'FEATURE_CTL_TOOLS_COPY_ENABLED', 'FEATURE_CTL_TOOLS_TAB_ENABLED', + 'FEATURE_PREFERRED_CTL_TOOLS_ENABLED', 'FEATURE_ENABLE_LDAP_SYNC_DURING_MIGRATION', 'FEATURE_ES_COLLECTIONS_ENABLED', 'FEATURE_EXTENSIONS_ENABLED', diff --git a/apps/server/src/modules/server/server.config.ts b/apps/server/src/modules/server/server.config.ts index ca5aa6f9d02..ef3e5fdb063 100644 --- a/apps/server/src/modules/server/server.config.ts +++ b/apps/server/src/modules/server/server.config.ts @@ -293,6 +293,7 @@ const config: ServerConfig = { ) as number, CTL_TOOLS_BACKEND_URL: Configuration.get('PUBLIC_BACKEND_URL') as string, FEATURE_CTL_TOOLS_COPY_ENABLED: Configuration.get('FEATURE_CTL_TOOLS_COPY_ENABLED') as boolean, + FEATURE_PREFERRED_CTL_TOOLS_ENABLED: Configuration.get('FEATURE_PREFERRED_CTL_TOOLS_ENABLED') as boolean, CTL_TOOLS_RELOAD_TIME_MS: Configuration.get('CTL_TOOLS_RELOAD_TIME_MS') as number, HOST: Configuration.get('HOST') as string, FILES_STORAGE__SERVICE_BASE_URL: Configuration.get('FILES_STORAGE__SERVICE_BASE_URL') as string, diff --git a/apps/server/src/modules/tool/tool-config.ts b/apps/server/src/modules/tool/tool-config.ts index 8e9755b553d..099a732ab0d 100644 --- a/apps/server/src/modules/tool/tool-config.ts +++ b/apps/server/src/modules/tool/tool-config.ts @@ -7,4 +7,5 @@ export interface ToolConfig { CTL_TOOLS_RELOAD_TIME_MS: number; FILES_STORAGE__SERVICE_BASE_URL: string; CTL_TOOLS__PREFERRED_TOOLS_LIMIT: number; + FEATURE_PREFERRED_CTL_TOOLS_ENABLED: boolean; } diff --git a/config/default.schema.json b/config/default.schema.json index 387e7331b1a..a62571df78f 100644 --- a/config/default.schema.json +++ b/config/default.schema.json @@ -1396,6 +1396,11 @@ "default": false, "description": "Enables external tools on the column board" }, + "FEATURE_PREFERRED_CTL_TOOLS_ENABLED": { + "type": "boolean", + "default": false, + "description": "Enables preferred external tools on the column board" + }, "CTL_TOOLS": { "type": "object", "description": "CTL Tools properties", From bf766f6dcf6e2e10fdacdc828679b46e695c7b6d Mon Sep 17 00:00:00 2001 From: MBergCap Date: Thu, 10 Oct 2024 11:23:51 +0200 Subject: [PATCH 09/23] fix typo --- .../dto/response/prefered-tool-list.response.ts | 11 ----------- .../dto/response/preferred-tool-list.response.ts | 11 +++++++++++ ...ol.response.ts => preferred-tool.response.ts} | 4 ++-- .../controller/tool-configuration.controller.ts | 10 +++++----- .../mapper/tool-configuration.mapper.ts | 16 ++++++++-------- 5 files changed, 26 insertions(+), 26 deletions(-) delete mode 100644 apps/server/src/modules/tool/external-tool/controller/dto/response/prefered-tool-list.response.ts create mode 100644 apps/server/src/modules/tool/external-tool/controller/dto/response/preferred-tool-list.response.ts rename apps/server/src/modules/tool/external-tool/controller/dto/response/{prefered-tool.response.ts => preferred-tool.response.ts} (86%) diff --git a/apps/server/src/modules/tool/external-tool/controller/dto/response/prefered-tool-list.response.ts b/apps/server/src/modules/tool/external-tool/controller/dto/response/prefered-tool-list.response.ts deleted file mode 100644 index 8e0693a5ab1..00000000000 --- a/apps/server/src/modules/tool/external-tool/controller/dto/response/prefered-tool-list.response.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { PreferedToolResponse } from '@modules/tool/external-tool/controller/dto/response/prefered-tool.response'; - -export class PreferedToolListResponse { - @ApiProperty({ type: [PreferedToolResponse] }) - data: PreferedToolResponse[]; - - constructor(data: PreferedToolResponse[]) { - this.data = data; - } -} diff --git a/apps/server/src/modules/tool/external-tool/controller/dto/response/preferred-tool-list.response.ts b/apps/server/src/modules/tool/external-tool/controller/dto/response/preferred-tool-list.response.ts new file mode 100644 index 00000000000..fed8815fb3a --- /dev/null +++ b/apps/server/src/modules/tool/external-tool/controller/dto/response/preferred-tool-list.response.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { PreferredToolResponse } from '@modules/tool/external-tool/controller/dto/response/preferred-tool.response'; + +export class PreferredToolListResponse { + @ApiProperty({ type: [PreferredToolResponse] }) + data: PreferredToolResponse[]; + + constructor(data: PreferredToolResponse[]) { + this.data = data; + } +} diff --git a/apps/server/src/modules/tool/external-tool/controller/dto/response/prefered-tool.response.ts b/apps/server/src/modules/tool/external-tool/controller/dto/response/preferred-tool.response.ts similarity index 86% rename from apps/server/src/modules/tool/external-tool/controller/dto/response/prefered-tool.response.ts rename to apps/server/src/modules/tool/external-tool/controller/dto/response/preferred-tool.response.ts index 8c2be418404..040958ac6e1 100644 --- a/apps/server/src/modules/tool/external-tool/controller/dto/response/prefered-tool.response.ts +++ b/apps/server/src/modules/tool/external-tool/controller/dto/response/preferred-tool.response.ts @@ -1,6 +1,6 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -export class PreferedToolResponse { +export class PreferredToolResponse { @ApiProperty({ type: String, description: 'Id of the school external tool' }) schoolExternalToolId: string; @@ -13,7 +13,7 @@ export class PreferedToolResponse { }) iconName?: string; - constructor(configuration: PreferedToolResponse) { + constructor(configuration: PreferredToolResponse) { this.schoolExternalToolId = configuration.schoolExternalToolId; this.name = configuration.name; this.iconName = configuration.iconName; diff --git a/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts b/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts index 252b41fd824..5ff63c3ed59 100644 --- a/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts +++ b/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts @@ -8,7 +8,7 @@ import { ApiTags, ApiUnauthorizedResponse, } from '@nestjs/swagger'; -import { PreferedToolListResponse } from '@modules/tool/external-tool/controller/dto/response/prefered-tool-list.response'; +import { PreferredToolListResponse } from '@modules/tool/external-tool/controller/dto/response/preferred-tool-list.response'; import { ToolContextType } from '../../common/enum'; import { ExternalTool } from '../domain'; import { ToolConfigurationMapper } from '../mapper/tool-configuration.mapper'; @@ -101,12 +101,12 @@ export class ToolConfigurationController { @ApiOperation({ summary: 'Lists all preferred tools that can be added for a given context' }) @ApiOkResponse({ description: 'List of preferred tools for a context', - type: PreferedToolListResponse, + type: PreferredToolListResponse, }) - public async getPreferedToolsForContext( + public async getPreferredToolsForContext( @CurrentUser() currentUser: ICurrentUser, @Param() params: ContextRefParams - ): Promise { + ): Promise { const preferedTools: ContextExternalToolTemplateInfo[] = await this.externalToolConfigurationUc.getPreferedToolsForContext( currentUser.userId, @@ -115,7 +115,7 @@ export class ToolConfigurationController { params.contextType ); - const mapped: PreferedToolListResponse = ToolConfigurationMapper.mapToPreferedToolListResponse(preferedTools); + const mapped: PreferredToolListResponse = ToolConfigurationMapper.mapToPreferredToolListResponse(preferedTools); return mapped; } diff --git a/apps/server/src/modules/tool/external-tool/mapper/tool-configuration.mapper.ts b/apps/server/src/modules/tool/external-tool/mapper/tool-configuration.mapper.ts index 83fe7a1f819..fc222f0446a 100644 --- a/apps/server/src/modules/tool/external-tool/mapper/tool-configuration.mapper.ts +++ b/apps/server/src/modules/tool/external-tool/mapper/tool-configuration.mapper.ts @@ -1,5 +1,5 @@ -import { PreferedToolListResponse } from '@modules/tool/external-tool/controller/dto/response/prefered-tool-list.response'; -import { PreferedToolResponse } from '@modules/tool/external-tool/controller/dto/response/prefered-tool.response'; +import { PreferredToolListResponse } from '@modules/tool/external-tool/controller/dto/response/preferred-tool-list.response'; +import { PreferredToolResponse } from '@modules/tool/external-tool/controller/dto/response/preferred-tool.response'; import { ToolContextType } from '../../common/enum'; import { ContextExternalToolConfigurationTemplateListResponse, @@ -80,18 +80,18 @@ export class ToolConfigurationMapper { return mappedTypes; } - static mapToPreferedToolListResponse(preferedTools: ContextExternalToolTemplateInfo[]): PreferedToolListResponse { - const mappedTools = preferedTools.map((tool): PreferedToolResponse => this.mapToPreferedToolResponse(tool)); + static mapToPreferredToolListResponse(preferedTools: ContextExternalToolTemplateInfo[]): PreferredToolListResponse { + const mappedTools = preferedTools.map((tool): PreferredToolResponse => this.mapToPreferredToolResponse(tool)); - const mapped = new PreferedToolListResponse(mappedTools); + const mapped = new PreferredToolListResponse(mappedTools); return mapped; } - static mapToPreferedToolResponse(preferedTool: ContextExternalToolTemplateInfo): PreferedToolResponse { - const { externalTool, schoolExternalTool } = preferedTool; + static mapToPreferredToolResponse(preferredTool: ContextExternalToolTemplateInfo): PreferredToolResponse { + const { externalTool, schoolExternalTool } = preferredTool; - const mapped = new PreferedToolResponse({ + const mapped = new PreferredToolResponse({ schoolExternalToolId: schoolExternalTool.id ?? '', name: externalTool.name, iconName: externalTool.iconName, From d2b41f942d10e11da0d074293941dc39f095a617 Mon Sep 17 00:00:00 2001 From: MBergCap Date: Thu, 10 Oct 2024 16:04:58 +0200 Subject: [PATCH 10/23] change query parameter --- .../dto/request/tool-context-type.params.ts | 15 +++++++++++ .../tool-configuration.controller.ts | 10 +++---- .../uc/external-tool-configuration.uc.ts | 26 +++++++------------ 3 files changed, 30 insertions(+), 21 deletions(-) create mode 100644 apps/server/src/modules/tool/external-tool/controller/dto/request/tool-context-type.params.ts diff --git a/apps/server/src/modules/tool/external-tool/controller/dto/request/tool-context-type.params.ts b/apps/server/src/modules/tool/external-tool/controller/dto/request/tool-context-type.params.ts new file mode 100644 index 00000000000..b8169fc412f --- /dev/null +++ b/apps/server/src/modules/tool/external-tool/controller/dto/request/tool-context-type.params.ts @@ -0,0 +1,15 @@ +import { IsEnum, IsOptional } from 'class-validator'; +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { ToolContextType } from '@modules/tool/common/enum'; + +export class ToolContextTypeParams { + @IsOptional() + @IsEnum(ToolContextType, { each: true }) + @ApiPropertyOptional({ + enum: ToolContextType, + enumName: 'ToolContextType', + isArray: true, + description: 'XXX', + }) + contextType?: ToolContextType; +} diff --git a/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts b/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts index 5ff63c3ed59..bac6f7f2e45 100644 --- a/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts +++ b/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts @@ -1,5 +1,5 @@ import { CurrentUser, ICurrentUser, JwtAuthentication } from '@infra/auth-guard'; -import { Controller, Get, Param } from '@nestjs/common'; +import { Controller, Get, Param, Query } from '@nestjs/common'; import { ApiForbiddenResponse, ApiFoundResponse, @@ -9,6 +9,7 @@ import { ApiUnauthorizedResponse, } from '@nestjs/swagger'; import { PreferredToolListResponse } from '@modules/tool/external-tool/controller/dto/response/preferred-tool-list.response'; +import { ToolContextTypeParams } from '@modules/tool/external-tool/controller/dto/request/tool-context-type.params'; import { ToolContextType } from '../../common/enum'; import { ExternalTool } from '../domain'; import { ToolConfigurationMapper } from '../mapper/tool-configuration.mapper'; @@ -96,7 +97,7 @@ export class ToolConfigurationController { return mapped; } - @Get(':contextType/:contextId/preferred-tools') + @Get('preferred-tools') @ApiForbiddenResponse() @ApiOperation({ summary: 'Lists all preferred tools that can be added for a given context' }) @ApiOkResponse({ @@ -105,14 +106,13 @@ export class ToolConfigurationController { }) public async getPreferredToolsForContext( @CurrentUser() currentUser: ICurrentUser, - @Param() params: ContextRefParams + @Query() context: ToolContextTypeParams ): Promise { const preferedTools: ContextExternalToolTemplateInfo[] = await this.externalToolConfigurationUc.getPreferedToolsForContext( currentUser.userId, currentUser.schoolId, - params.contextId, - params.contextType + context.contextType ); const mapped: PreferredToolListResponse = ToolConfigurationMapper.mapToPreferredToolListResponse(preferedTools); diff --git a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts index 06e387d7d45..b962d3dc48f 100644 --- a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts +++ b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts @@ -162,20 +162,12 @@ export class ExternalToolConfigurationUc { public async getPreferedToolsForContext( userId: EntityId, schoolId: EntityId, - contextId: EntityId, - contextType: ToolContextType + contextType?: ToolContextType ): Promise { - const [externalTools, contextExternalTools]: [Page, ContextExternalTool[]] = await Promise.all([ - this.externalToolService.findExternalTools({ isPreferred: true }), - this.contextExternalToolService.findContextExternalTools({ - context: { id: contextId, type: contextType }, - }), - ]); - const user: User = await this.authorizationService.getUserWithPermissions(userId); + this.authorizationService.hasAllPermissions(user, [Permission.CONTEXT_TOOL_ADMIN]); - const context: AuthorizationContext = AuthorizationContextBuilder.read([Permission.CONTEXT_TOOL_ADMIN]); - await this.ensureContextPermissions(user, contextExternalTools, context); + const externalTools: Page = await this.externalToolService.findExternalTools({ isPreferred: true }); const schoolExternalTools: SchoolExternalTool[] = ( await Promise.all( @@ -200,15 +192,17 @@ export class ExternalToolConfigurationUc { private prepareAvailableToolsForContext( externalTools: ExternalTool[], schoolExternalTools: SchoolExternalTool[], - contextType: ToolContextType + contextType?: ToolContextType ): ContextExternalToolTemplateInfo[] { let availableToolsForContext: ContextExternalToolTemplateInfo[] = this.externalToolConfigurationService.filterForAvailableExternalTools(externalTools, schoolExternalTools); - availableToolsForContext = this.externalToolConfigurationService.filterForContextRestrictions( - availableToolsForContext, - contextType - ); + if (contextType) { + availableToolsForContext = this.externalToolConfigurationService.filterForContextRestrictions( + availableToolsForContext, + contextType + ); + } availableToolsForContext.forEach((toolTemplateInfo) => { this.externalToolConfigurationService.filterParametersForScope( From f4b026e2f2ccc71fec4defa0a77a11d3fe10921a Mon Sep 17 00:00:00 2001 From: MBergCap Date: Fri, 11 Oct 2024 10:00:42 +0200 Subject: [PATCH 11/23] fix param --- .../controller/dto/request/tool-context-type.params.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/server/src/modules/tool/external-tool/controller/dto/request/tool-context-type.params.ts b/apps/server/src/modules/tool/external-tool/controller/dto/request/tool-context-type.params.ts index b8169fc412f..0926d961196 100644 --- a/apps/server/src/modules/tool/external-tool/controller/dto/request/tool-context-type.params.ts +++ b/apps/server/src/modules/tool/external-tool/controller/dto/request/tool-context-type.params.ts @@ -8,8 +8,7 @@ export class ToolContextTypeParams { @ApiPropertyOptional({ enum: ToolContextType, enumName: 'ToolContextType', - isArray: true, - description: 'XXX', + description: 'Context types for tools', }) contextType?: ToolContextType; } From 51a2c0e3265e0c404d81c023bec27bc8249f77fb Mon Sep 17 00:00:00 2001 From: MBergCap Date: Fri, 11 Oct 2024 12:32:12 +0200 Subject: [PATCH 12/23] fix uc test --- .../uc/external-tool-configuration.uc.spec.ts | 55 +++++-------------- 1 file changed, 15 insertions(+), 40 deletions(-) diff --git a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.spec.ts b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.spec.ts index bbb1bf6663d..c7ec709da1a 100644 --- a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.spec.ts +++ b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.spec.ts @@ -736,27 +736,22 @@ describe('ExternalToolConfigurationUc', () => { describe('when the user has insufficient permission', () => { const setup = () => { const user: User = userFactory.build(); - const contextExternalTool: ContextExternalTool = contextExternalToolFactory.build(); authorizationService.getUserWithPermissions.mockResolvedValue(user); - toolPermissionHelper.ensureContextPermissions.mockRejectedValue(new ForbiddenException()); - contextExternalToolService.findContextExternalTools.mockResolvedValue([contextExternalTool]); + authorizationService.hasAllPermissions.mockImplementation(() => { + throw new ForbiddenException(); + }); - return { user, contextExternalTool }; + return { user }; }; it('should fail when authorizationService throws ForbiddenException', async () => { - const { user, contextExternalTool } = setup(); + const { user } = setup(); - const func = async () => - uc.getAvailableToolsForContext('userId', 'schoolId', 'contextId', ToolContextType.COURSE); + const func = async () => uc.getPreferedToolsForContext('userId', 'schoolId', ToolContextType.BOARD_ELEMENT); await expect(func).rejects.toThrow(ForbiddenException); - expect(toolPermissionHelper.ensureContextPermissions).toHaveBeenCalledWith( - user, - contextExternalTool, - AuthorizationContextBuilder.read([Permission.CONTEXT_TOOL_ADMIN]) - ); + expect(authorizationService.hasAllPermissions).toHaveBeenCalledWith(user, [Permission.CONTEXT_TOOL_ADMIN]); }); }); @@ -775,10 +770,6 @@ describe('ExternalToolConfigurationUc', () => { toolId: unusedExternalTool.id, }); - const usedContextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId({ - schoolToolRef: { schoolToolId: usedSchoolExternalTool.id }, - }); - externalToolService.findExternalTools.mockResolvedValue( new Page( [hiddenExternalTool, usedExternalTool, unusedExternalTool, externalToolWithoutSchoolTool], @@ -789,7 +780,6 @@ describe('ExternalToolConfigurationUc', () => { usedSchoolExternalTool, unusedSchoolExternalTool, ]); - contextExternalToolService.findContextExternalTools.mockResolvedValue([usedContextExternalTool]); authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); externalToolConfigurationService.filterForAvailableExternalTools.mockReturnValue([ @@ -803,26 +793,21 @@ describe('ExternalToolConfigurationUc', () => { user, usedExternalTool, usedSchoolExternalTool, - usedContextExternalTool, }; }; - it('should call the toolPermissionHelper with CONTEXT_TOOL_ADMIN permission', async () => { - const { user, usedContextExternalTool } = setup(); + it('should call the authorizationService with CONTEXT_TOOL_ADMIN permission', async () => { + const { user } = setup(); - await uc.getAvailableToolsForContext('userId', 'schoolId', 'contextId', ToolContextType.BOARD_ELEMENT); + await uc.getPreferedToolsForContext('userId', 'schoolId', ToolContextType.BOARD_ELEMENT); - expect(toolPermissionHelper.ensureContextPermissions).toHaveBeenCalledWith( - user, - usedContextExternalTool, - AuthorizationContextBuilder.read([Permission.CONTEXT_TOOL_ADMIN]) - ); + expect(authorizationService.hasAllPermissions).toHaveBeenCalledWith(user, [Permission.CONTEXT_TOOL_ADMIN]); }); it('should call externalToolLogoService', async () => { const { usedExternalTool } = setup(); - await uc.getAvailableToolsForContext('userId', 'schoolId', 'contextId', ToolContextType.BOARD_ELEMENT); + await uc.getPreferedToolsForContext('userId', 'schoolId', ToolContextType.BOARD_ELEMENT); expect(logoService.buildLogoUrl).toHaveBeenCalledWith(usedExternalTool); }); @@ -830,7 +815,7 @@ describe('ExternalToolConfigurationUc', () => { it('should filter for restricted contexts', async () => { const { usedExternalTool, usedSchoolExternalTool } = setup(); - await uc.getAvailableToolsForContext('userId', 'schoolId', 'contextId', ToolContextType.BOARD_ELEMENT); + await uc.getPreferedToolsForContext('userId', 'schoolId', ToolContextType.BOARD_ELEMENT); expect(externalToolConfigurationService.filterForContextRestrictions).toHaveBeenCalledWith( [{ externalTool: usedExternalTool, schoolExternalTool: usedSchoolExternalTool }], @@ -841,7 +826,7 @@ describe('ExternalToolConfigurationUc', () => { it('should call filterParametersForScope', async () => { const { usedExternalTool } = setup(); - await uc.getAvailableToolsForContext('userId', 'schoolId', 'contextId', ToolContextType.BOARD_ELEMENT); + await uc.getPreferedToolsForContext('userId', 'schoolId', ToolContextType.BOARD_ELEMENT); expect(externalToolConfigurationService.filterParametersForScope).toHaveBeenCalledWith( usedExternalTool, @@ -862,13 +847,8 @@ describe('ExternalToolConfigurationUc', () => { parameters: [], }); - const contextExternalTool: ContextExternalTool = contextExternalToolFactory.build({ - schoolToolRef: { schoolToolId: schoolExternalTool.id }, - }); - externalToolService.findExternalTools.mockResolvedValue(new Page([externalTool], 1)); schoolExternalToolService.findSchoolExternalTools.mockResolvedValue([schoolExternalTool]); - contextExternalToolService.findContextExternalTools.mockResolvedValue([contextExternalTool]); externalToolConfigurationService.filterForAvailableExternalTools.mockReturnValue([ { externalTool, schoolExternalTool }, @@ -886,12 +866,7 @@ describe('ExternalToolConfigurationUc', () => { it('should allow to add a preferred tool to a given context', async () => { const { externalTool, schoolExternalTool } = setup(); - const preferredTools = await uc.getPreferedToolsForContext( - 'userId', - 'schoolId', - 'contextId', - ToolContextType.BOARD_ELEMENT - ); + const preferredTools = await uc.getPreferedToolsForContext('userId', 'schoolId', ToolContextType.BOARD_ELEMENT); expect(preferredTools).toEqual([ { From 41d2e542cc879114486cd801ea282104d61a0a30 Mon Sep 17 00:00:00 2001 From: MBergCap Date: Fri, 11 Oct 2024 14:18:51 +0200 Subject: [PATCH 13/23] add api test --- .../api-test/tool-configuration.api.spec.ts | 172 ++++++++++++++++++ .../uc/external-tool-configuration.uc.ts | 6 +- 2 files changed, 175 insertions(+), 3 deletions(-) diff --git a/apps/server/src/modules/tool/external-tool/controller/api-test/tool-configuration.api.spec.ts b/apps/server/src/modules/tool/external-tool/controller/api-test/tool-configuration.api.spec.ts index 20154bf1311..daf1125ea35 100644 --- a/apps/server/src/modules/tool/external-tool/controller/api-test/tool-configuration.api.spec.ts +++ b/apps/server/src/modules/tool/external-tool/controller/api-test/tool-configuration.api.spec.ts @@ -16,6 +16,7 @@ import { import { AccountEntity } from '@src/modules/account/domain/entity/account.entity'; import { accountFactory } from '@src/modules/account/testing'; import { Response } from 'supertest'; +import { PreferredToolListResponse } from '@modules/tool/external-tool/controller/dto/response/preferred-tool-list.response'; import { CustomParameterLocationParams, CustomParameterScopeTypeParams, @@ -743,4 +744,175 @@ describe('ToolConfigurationController (API)', () => { }); }); }); + + describe('[GET] tools/preferred-tools', () => { + describe('when the user is not authorized', () => { + const setup = async () => { + const school: SchoolEntity = schoolEntityFactory.buildWithId(); + + const { adminUser, adminAccount } = UserAndAccountTestFactory.buildAdmin(); + + const externalTool: ExternalToolEntity = externalToolEntityFactory.buildWithId({ + isPreferred: true, + iconName: 'iconName', + }); + + const schoolExternalTool: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId({ + school, + tool: externalTool, + }); + + await em.persistAndFlush([school, adminUser, adminAccount, externalTool, schoolExternalTool]); + em.clear(); + + const loggedInClient: TestApiClient = await testApiClient.login(adminAccount); + + return { + loggedInClient, + }; + }; + + it('should return a forbidden status', async () => { + const { loggedInClient } = await setup(); + + const response: Response = await loggedInClient.get(`/preferred-tools`); + + expect(response.status).toEqual(HttpStatus.UNAUTHORIZED); + }); + }); + + describe('when preferred tools are available for a context', () => { + const setup = async () => { + const school: SchoolEntity = schoolEntityFactory.buildWithId(); + + const { teacherUser, teacherAccount } = UserAndAccountTestFactory.buildTeacher({ school }, [ + Permission.CONTEXT_TOOL_ADMIN, + ]); + + const externalTool: ExternalToolEntity = externalToolEntityFactory.buildWithId({ + restrictToContexts: [], + isPreferred: true, + iconName: 'iconName', + }); + + const externalToolWithContextRestriction: ExternalToolEntity = externalToolEntityFactory.buildWithId({ + restrictToContexts: [ToolContextType.COURSE], + isPreferred: true, + iconName: 'iconName', + }); + + const schoolExternalTool: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId({ + school, + tool: externalTool, + }); + + const schoolExternalTool2: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId({ + school, + tool: externalToolWithContextRestriction, + }); + + await em.persistAndFlush([ + school, + teacherUser, + teacherAccount, + externalTool, + externalToolWithContextRestriction, + schoolExternalTool, + schoolExternalTool2, + ]); + em.clear(); + + const loggedInClient: TestApiClient = await testApiClient.login(teacherAccount); + + return { + externalTool, + externalToolWithContextRestriction, + schoolExternalTool, + schoolExternalTool2, + loggedInClient, + }; + }; + + it('should return an array of preferred tools', async () => { + const { + externalTool, + externalToolWithContextRestriction, + schoolExternalTool, + schoolExternalTool2, + loggedInClient, + } = await setup(); + + const response: Response = await loggedInClient.get('/preferred-tools'); + + expect(response.body).toEqual({ + data: [ + { + schoolExternalToolId: schoolExternalTool.id, + name: externalTool.name, + iconName: externalTool.iconName, + }, + { + schoolExternalToolId: schoolExternalTool2.id, + name: externalToolWithContextRestriction.name, + iconName: externalToolWithContextRestriction.iconName, + }, + ], + }); + }); + + it('should not return the context restricted tool', async () => { + const { loggedInClient, externalTool, schoolExternalTool } = await setup(); + + const response: Response = await loggedInClient.get('/preferred-tools').query({ contextType: 'board-element' }); + + expect(response.body).toEqual({ + data: [ + { + schoolExternalToolId: schoolExternalTool.id, + name: externalTool.name, + iconName: externalTool.iconName, + }, + ], + }); + }); + }); + + describe('when no preferred tools are available', () => { + const setup = async () => { + const school: SchoolEntity = schoolEntityFactory.buildWithId(); + + const { teacherUser, teacherAccount } = UserAndAccountTestFactory.buildTeacher({}, [ + Permission.CONTEXT_TOOL_ADMIN, + ]); + + const externalTool: ExternalToolEntity = externalToolEntityFactory.buildWithId({ + isPreferred: false, + }); + + const schoolExternalTool: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId({ + school, + tool: externalTool, + }); + + await em.persistAndFlush([teacherUser, school, teacherAccount, externalTool, schoolExternalTool]); + em.clear(); + + const loggedInClient: TestApiClient = await testApiClient.login(teacherAccount); + + return { + loggedInClient, + }; + }; + + it('should return an empty array', async () => { + const { loggedInClient } = await setup(); + + const response: Response = await loggedInClient.get('/preferred-tools'); + + expect(response.body).toEqual({ + data: [], + }); + }); + }); + }); }); diff --git a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts index b962d3dc48f..07ee8a6a090 100644 --- a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts +++ b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts @@ -164,11 +164,11 @@ export class ExternalToolConfigurationUc { schoolId: EntityId, contextType?: ToolContextType ): Promise { - const user: User = await this.authorizationService.getUserWithPermissions(userId); - this.authorizationService.hasAllPermissions(user, [Permission.CONTEXT_TOOL_ADMIN]); - const externalTools: Page = await this.externalToolService.findExternalTools({ isPreferred: true }); + const user: User = await this.authorizationService.getUserWithPermissions(userId); + this.authorizationService.checkAllPermissions(user, [Permission.CONTEXT_TOOL_ADMIN]); + const schoolExternalTools: SchoolExternalTool[] = ( await Promise.all( externalTools.data.map((externalTool) => From 6f7f80c46bebde6e4593c8ffeec77fdfb8b744e6 Mon Sep 17 00:00:00 2001 From: MBergCap Date: Fri, 11 Oct 2024 14:21:05 +0200 Subject: [PATCH 14/23] fix test --- .../controller/api-test/tool-configuration.api.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/modules/tool/external-tool/controller/api-test/tool-configuration.api.spec.ts b/apps/server/src/modules/tool/external-tool/controller/api-test/tool-configuration.api.spec.ts index daf1125ea35..5358f8423b6 100644 --- a/apps/server/src/modules/tool/external-tool/controller/api-test/tool-configuration.api.spec.ts +++ b/apps/server/src/modules/tool/external-tool/controller/api-test/tool-configuration.api.spec.ts @@ -772,7 +772,7 @@ describe('ToolConfigurationController (API)', () => { }; }; - it('should return a forbidden status', async () => { + it('should return a unauthorized status', async () => { const { loggedInClient } = await setup(); const response: Response = await loggedInClient.get(`/preferred-tools`); From 2751b18866d3bac204f20ef74e7a6644a730ec6b Mon Sep 17 00:00:00 2001 From: MBergCap Date: Fri, 11 Oct 2024 14:37:48 +0200 Subject: [PATCH 15/23] fix test --- .../external-tool/uc/external-tool-configuration.uc.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.spec.ts b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.spec.ts index c7ec709da1a..eff5fceeb7e 100644 --- a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.spec.ts +++ b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.spec.ts @@ -738,7 +738,7 @@ describe('ExternalToolConfigurationUc', () => { const user: User = userFactory.build(); authorizationService.getUserWithPermissions.mockResolvedValue(user); - authorizationService.hasAllPermissions.mockImplementation(() => { + authorizationService.checkAllPermissions.mockImplementation(() => { throw new ForbiddenException(); }); @@ -751,7 +751,7 @@ describe('ExternalToolConfigurationUc', () => { const func = async () => uc.getPreferedToolsForContext('userId', 'schoolId', ToolContextType.BOARD_ELEMENT); await expect(func).rejects.toThrow(ForbiddenException); - expect(authorizationService.hasAllPermissions).toHaveBeenCalledWith(user, [Permission.CONTEXT_TOOL_ADMIN]); + expect(authorizationService.checkAllPermissions).toHaveBeenCalledWith(user, [Permission.CONTEXT_TOOL_ADMIN]); }); }); @@ -801,7 +801,7 @@ describe('ExternalToolConfigurationUc', () => { await uc.getPreferedToolsForContext('userId', 'schoolId', ToolContextType.BOARD_ELEMENT); - expect(authorizationService.hasAllPermissions).toHaveBeenCalledWith(user, [Permission.CONTEXT_TOOL_ADMIN]); + expect(authorizationService.checkAllPermissions).toHaveBeenCalledWith(user, [Permission.CONTEXT_TOOL_ADMIN]); }); it('should call externalToolLogoService', async () => { From 67e4c4f4a794db328b60bd00e335723973b10950 Mon Sep 17 00:00:00 2001 From: MBergCap Date: Fri, 11 Oct 2024 16:27:31 +0200 Subject: [PATCH 16/23] change response --- .../controller/dto/response/preferred-tool.response.ts | 6 +++--- .../tool/external-tool/mapper/tool-configuration.mapper.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/server/src/modules/tool/external-tool/controller/dto/response/preferred-tool.response.ts b/apps/server/src/modules/tool/external-tool/controller/dto/response/preferred-tool.response.ts index 040958ac6e1..2e7acfef272 100644 --- a/apps/server/src/modules/tool/external-tool/controller/dto/response/preferred-tool.response.ts +++ b/apps/server/src/modules/tool/external-tool/controller/dto/response/preferred-tool.response.ts @@ -1,4 +1,4 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { ApiProperty } from '@nestjs/swagger'; export class PreferredToolResponse { @ApiProperty({ type: String, description: 'Id of the school external tool' }) @@ -7,11 +7,11 @@ export class PreferredToolResponse { @ApiProperty({ type: String, description: 'Name of the external tool' }) name: string; - @ApiPropertyOptional({ + @ApiProperty({ type: String, description: 'Name of the icon to be rendered when displaying it as a preferred tool', }) - iconName?: string; + iconName: string; constructor(configuration: PreferredToolResponse) { this.schoolExternalToolId = configuration.schoolExternalToolId; diff --git a/apps/server/src/modules/tool/external-tool/mapper/tool-configuration.mapper.ts b/apps/server/src/modules/tool/external-tool/mapper/tool-configuration.mapper.ts index fc222f0446a..5c43de01e38 100644 --- a/apps/server/src/modules/tool/external-tool/mapper/tool-configuration.mapper.ts +++ b/apps/server/src/modules/tool/external-tool/mapper/tool-configuration.mapper.ts @@ -94,7 +94,7 @@ export class ToolConfigurationMapper { const mapped = new PreferredToolResponse({ schoolExternalToolId: schoolExternalTool.id ?? '', name: externalTool.name, - iconName: externalTool.iconName, + iconName: externalTool.iconName ?? '', }); return mapped; From aceedb24d4addb0437432ab1b75c81f0e90a40d1 Mon Sep 17 00:00:00 2001 From: MBergCap Date: Fri, 11 Oct 2024 16:34:50 +0200 Subject: [PATCH 17/23] adjust seed data --- backup/setup/external-tools.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backup/setup/external-tools.json b/backup/setup/external-tools.json index 83ac0f44606..51e01a117c6 100644 --- a/backup/setup/external-tools.json +++ b/backup/setup/external-tools.json @@ -1019,7 +1019,7 @@ "isDeactivated": false, "restrictToContexts": [], "isPreferred": true, - "iconName": "magnify" + "iconName": "mdiMagnify" }, { "_id": { @@ -1053,6 +1053,6 @@ "isDeactivated": false, "restrictToContexts": [], "isPreferred": true, - "iconName": "magnify-plus-outline" + "iconName": "mdiMagnifyPlusOutline" } ] From 1996d0e9c937581d7e7a46547516ccc15bb4e437 Mon Sep 17 00:00:00 2001 From: MBergCap Date: Mon, 14 Oct 2024 09:32:33 +0200 Subject: [PATCH 18/23] fix test --- .../controller/api-test/tool-configuration.api.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/server/src/modules/tool/external-tool/controller/api-test/tool-configuration.api.spec.ts b/apps/server/src/modules/tool/external-tool/controller/api-test/tool-configuration.api.spec.ts index 5358f8423b6..5fddbc6c977 100644 --- a/apps/server/src/modules/tool/external-tool/controller/api-test/tool-configuration.api.spec.ts +++ b/apps/server/src/modules/tool/external-tool/controller/api-test/tool-configuration.api.spec.ts @@ -849,12 +849,12 @@ describe('ToolConfigurationController (API)', () => { { schoolExternalToolId: schoolExternalTool.id, name: externalTool.name, - iconName: externalTool.iconName, + iconName: 'iconName', }, { schoolExternalToolId: schoolExternalTool2.id, name: externalToolWithContextRestriction.name, - iconName: externalToolWithContextRestriction.iconName, + iconName: 'iconName', }, ], }); @@ -870,7 +870,7 @@ describe('ToolConfigurationController (API)', () => { { schoolExternalToolId: schoolExternalTool.id, name: externalTool.name, - iconName: externalTool.iconName, + iconName: 'iconName', }, ], }); From ce2457f527e2b06bbf4c7daa05c1cf6dcc92da87 Mon Sep 17 00:00:00 2001 From: MBergCap Date: Mon, 14 Oct 2024 16:15:39 +0200 Subject: [PATCH 19/23] add seed data --- backup/setup/external-tools.json | 46 ++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/backup/setup/external-tools.json b/backup/setup/external-tools.json index 51e01a117c6..d15a447cc88 100644 --- a/backup/setup/external-tools.json +++ b/backup/setup/external-tools.json @@ -1054,5 +1054,51 @@ "restrictToContexts": [], "isPreferred": true, "iconName": "mdiMagnifyPlusOutline" + }, + { + "_id": { + "$oid": "670d07654c3f9eec4c9b6432" + }, + "createdAt": { + "$date": "2023-04-27T09:51:15.592Z" + }, + "updatedAt": { + "$date": "2023-04-27T09:51:15.592Z" + }, + "name": "CY Test Tool Preferred Course Restriction", + "url": "https://google.de/", + "config_type": "basic", + "config_baseUrl": "https://google.de/", + "parameters": [], + "isHidden": false, + "openNewTab": true, + "version": 1, + "isDeactivated": false, + "restrictToContexts": ["course"], + "isPreferred": true, + "iconName": "mdiMagnify" + }, + { + "_id": { + "$oid": "670d07704c3f9eec4c9b6434" + }, + "createdAt": { + "$date": "2023-04-27T09:51:15.592Z" + }, + "updatedAt": { + "$date": "2023-04-27T09:51:15.592Z" + }, + "name": "CY Test Tool Preferred Board Restriction", + "url": "https://google.de/", + "config_type": "basic", + "config_baseUrl": "https://google.de/", + "parameters": [], + "isHidden": false, + "openNewTab": true, + "version": 1, + "isDeactivated": false, + "restrictToContexts": ["board-element"], + "isPreferred": true, + "iconName": "mdiEyeOutline" } ] From 85cbef713ed89aec124eabcd77664e505f7fa59d Mon Sep 17 00:00:00 2001 From: MBergCap Date: Mon, 14 Oct 2024 16:29:11 +0200 Subject: [PATCH 20/23] adjust seed data --- backup/setup/external-tools.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backup/setup/external-tools.json b/backup/setup/external-tools.json index d15a447cc88..25587ccdca2 100644 --- a/backup/setup/external-tools.json +++ b/backup/setup/external-tools.json @@ -1053,7 +1053,7 @@ "isDeactivated": false, "restrictToContexts": [], "isPreferred": true, - "iconName": "mdiMagnifyPlusOutline" + "iconName": "mdiChatOutline" }, { "_id": { @@ -1076,7 +1076,7 @@ "isDeactivated": false, "restrictToContexts": ["course"], "isPreferred": true, - "iconName": "mdiMagnify" + "iconName": "mdiFileQuestionOutline" }, { "_id": { From c4f9fac36090f6ebc6abedeb14cbce388fe55935 Mon Sep 17 00:00:00 2001 From: MBergCap Date: Tue, 15 Oct 2024 09:08:55 +0200 Subject: [PATCH 21/23] fix imports --- .../tool/external-tool/controller/dto/request/index.ts | 1 + .../tool/external-tool/controller/dto/response/index.ts | 2 ++ .../controller/dto/response/preferred-tool-list.response.ts | 2 +- .../external-tool/controller/tool-configuration.controller.ts | 4 ++-- .../tool/external-tool/mapper/tool-configuration.mapper.ts | 4 ++-- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/server/src/modules/tool/external-tool/controller/dto/request/index.ts b/apps/server/src/modules/tool/external-tool/controller/dto/request/index.ts index fc62b2b4cc4..dddbe0954d0 100644 --- a/apps/server/src/modules/tool/external-tool/controller/dto/request/index.ts +++ b/apps/server/src/modules/tool/external-tool/controller/dto/request/index.ts @@ -11,3 +11,4 @@ export * from './school-external-tool-id.params'; export * from './context-external-tool-id.params'; export { ExternalToolMediumParams } from './external-tool-medium.params'; export { ExternalToolBulkCreateParams } from './external-tool-bulk-create.params'; +export * from './tool-context-type.params'; diff --git a/apps/server/src/modules/tool/external-tool/controller/dto/response/index.ts b/apps/server/src/modules/tool/external-tool/controller/dto/response/index.ts index a1f0b8fff5d..8a78d73d44c 100644 --- a/apps/server/src/modules/tool/external-tool/controller/dto/response/index.ts +++ b/apps/server/src/modules/tool/external-tool/controller/dto/response/index.ts @@ -13,3 +13,5 @@ export { ExternalToolImportResultListResponse, ExternalToolImportResultResponse, } from './external-tool-import-result-response'; +export * from './preferred-tool.response'; +export * from './preferred-tool-list.response'; diff --git a/apps/server/src/modules/tool/external-tool/controller/dto/response/preferred-tool-list.response.ts b/apps/server/src/modules/tool/external-tool/controller/dto/response/preferred-tool-list.response.ts index fed8815fb3a..fd116aa231d 100644 --- a/apps/server/src/modules/tool/external-tool/controller/dto/response/preferred-tool-list.response.ts +++ b/apps/server/src/modules/tool/external-tool/controller/dto/response/preferred-tool-list.response.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { PreferredToolResponse } from '@modules/tool/external-tool/controller/dto/response/preferred-tool.response'; +import { PreferredToolResponse } from './preferred-tool.response'; export class PreferredToolListResponse { @ApiProperty({ type: [PreferredToolResponse] }) diff --git a/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts b/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts index bac6f7f2e45..9817690782b 100644 --- a/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts +++ b/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts @@ -8,8 +8,6 @@ import { ApiTags, ApiUnauthorizedResponse, } from '@nestjs/swagger'; -import { PreferredToolListResponse } from '@modules/tool/external-tool/controller/dto/response/preferred-tool-list.response'; -import { ToolContextTypeParams } from '@modules/tool/external-tool/controller/dto/request/tool-context-type.params'; import { ToolContextType } from '../../common/enum'; import { ExternalTool } from '../domain'; import { ToolConfigurationMapper } from '../mapper/tool-configuration.mapper'; @@ -24,6 +22,8 @@ import { SchoolExternalToolIdParams, SchoolIdParams, ToolContextTypesListResponse, + PreferredToolListResponse, + ToolContextTypeParams, } from './dto'; @ApiTags('Tool') diff --git a/apps/server/src/modules/tool/external-tool/mapper/tool-configuration.mapper.ts b/apps/server/src/modules/tool/external-tool/mapper/tool-configuration.mapper.ts index 5c43de01e38..914e97b56c4 100644 --- a/apps/server/src/modules/tool/external-tool/mapper/tool-configuration.mapper.ts +++ b/apps/server/src/modules/tool/external-tool/mapper/tool-configuration.mapper.ts @@ -1,5 +1,3 @@ -import { PreferredToolListResponse } from '@modules/tool/external-tool/controller/dto/response/preferred-tool-list.response'; -import { PreferredToolResponse } from '@modules/tool/external-tool/controller/dto/response/preferred-tool.response'; import { ToolContextType } from '../../common/enum'; import { ContextExternalToolConfigurationTemplateListResponse, @@ -7,6 +5,8 @@ import { SchoolExternalToolConfigurationTemplateListResponse, SchoolExternalToolConfigurationTemplateResponse, ToolContextTypesListResponse, + PreferredToolListResponse, + PreferredToolResponse, } from '../controller/dto'; import { ExternalTool } from '../domain'; import { ContextExternalToolTemplateInfo } from '../uc'; From 6c0e121b99ac09a59a47028a45ac2b160bdea91e Mon Sep 17 00:00:00 2001 From: MBergCap Date: Tue, 15 Oct 2024 09:55:10 +0200 Subject: [PATCH 22/23] fix PR Comment --- .../external-tool/uc/external-tool-configuration.uc.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts index 07ee8a6a090..85d4db71781 100644 --- a/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts +++ b/apps/server/src/modules/tool/external-tool/uc/external-tool-configuration.uc.ts @@ -164,11 +164,11 @@ export class ExternalToolConfigurationUc { schoolId: EntityId, contextType?: ToolContextType ): Promise { - const externalTools: Page = await this.externalToolService.findExternalTools({ isPreferred: true }); - const user: User = await this.authorizationService.getUserWithPermissions(userId); this.authorizationService.checkAllPermissions(user, [Permission.CONTEXT_TOOL_ADMIN]); + const externalTools: Page = await this.externalToolService.findExternalTools({ isPreferred: true }); + const schoolExternalTools: SchoolExternalTool[] = ( await Promise.all( externalTools.data.map((externalTool) => @@ -209,9 +209,6 @@ export class ExternalToolConfigurationUc { toolTemplateInfo.externalTool, CustomParameterScope.CONTEXT ); - }); - - availableToolsForContext.forEach((toolTemplateInfo) => { toolTemplateInfo.externalTool.logoUrl = this.externalToolLogoService.buildLogoUrl(toolTemplateInfo.externalTool); }); From 3a96f9715a12ce56f4a764dd062869f1c1eb0d63 Mon Sep 17 00:00:00 2001 From: MBergCap Date: Tue, 15 Oct 2024 11:31:44 +0200 Subject: [PATCH 23/23] fix import --- .../controller/api-test/tool-configuration.api.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/modules/tool/external-tool/controller/api-test/tool-configuration.api.spec.ts b/apps/server/src/modules/tool/external-tool/controller/api-test/tool-configuration.api.spec.ts index 5fddbc6c977..f4f1e043e1c 100644 --- a/apps/server/src/modules/tool/external-tool/controller/api-test/tool-configuration.api.spec.ts +++ b/apps/server/src/modules/tool/external-tool/controller/api-test/tool-configuration.api.spec.ts @@ -16,7 +16,6 @@ import { import { AccountEntity } from '@src/modules/account/domain/entity/account.entity'; import { accountFactory } from '@src/modules/account/testing'; import { Response } from 'supertest'; -import { PreferredToolListResponse } from '@modules/tool/external-tool/controller/dto/response/preferred-tool-list.response'; import { CustomParameterLocationParams, CustomParameterScopeTypeParams, @@ -35,6 +34,7 @@ import { SchoolExternalToolConfigurationTemplateListResponse, SchoolExternalToolConfigurationTemplateResponse, ToolContextTypesListResponse, + PreferredToolListResponse, } from '../dto'; describe('ToolConfigurationController (API)', () => {