From fdfcf51e11a2a92e23aebfb82356f3e1c868f8aa Mon Sep 17 00:00:00 2001 From: mrikallab <93978883+mrikallab@users.noreply.github.com> Date: Mon, 8 Jan 2024 14:49:47 +0100 Subject: [PATCH] N21-1421 Deactivation of ctl tools (#4672) --- ...rnal-tool-configuration-status.response.ts | 7 + ...text-external-tool-configuration-status.ts | 3 + .../mapper/tool-status-response.mapper.ts | 1 + .../service/common-tool.service.spec.ts | 73 ++++++++++ .../common/service/common-tool.service.ts | 5 + .../service/tool-version-service.spec.ts | 128 ++++++++++++++++++ .../service/tool-version-service.ts | 9 ++ .../controller/api-test/tool.api.spec.ts | 4 + .../request/external-tool-create.params.ts | 8 ++ .../request/external-tool-update.params.ts | 7 + .../dto/response/external-tool.response.ts | 4 + .../domain/external-tool.do.spec.ts | 1 + .../external-tool/domain/external-tool.do.ts | 5 + .../entity/external-tool.entity.spec.ts | 1 + .../entity/external-tool.entity.ts | 4 + .../external-tool-request.mapper.spec.ts | 12 ++ .../mapper/external-tool-request.mapper.ts | 2 + .../external-tool-response.mapper.spec.ts | 6 + .../mapper/external-tool-response.mapper.ts | 1 + ...xternal-tool-configuration.service.spec.ts | 56 ++++++++ .../external-tool-configuration.service.ts | 13 +- .../uc/dto/external-tool.types.ts | 2 + .../api-test/tool-school.api.spec.ts | 3 + ...hool-external-tool-configuration-status.ts | 3 + ...ol-external-tool-configuration.response.ts | 7 + .../dto/school-external-tool-post.params.ts | 10 +- ...l-tool-configuration-status.entity.spec.ts | 38 ++++++ ...ternal-tool-configuration-status.entity.ts | 15 ++ .../school-external-tool.entity.spec.ts | 65 +++++++-- .../entity/school-external-tool.entity.ts | 6 + ...chool-external-tool-request.mapper.spec.ts | 5 + .../school-external-tool-request.mapper.ts | 10 +- ...hool-external-tool-response.mapper.spec.ts | 3 + .../school-external-tool-response.mapper.ts | 2 +- ...ol-external-tool-status-response.mapper.ts | 1 + .../school-external-tool.service.spec.ts | 73 ++++++++++ .../service/school-external-tool.service.ts | 9 ++ .../tool-launch.controller.api.spec.ts | 109 +++++++++++++++ ...status-outdated.loggable-exception.spec.ts | 4 +- ...tool-status-outdated.loggable-exception.ts | 4 +- .../service/tool-launch.service.spec.ts | 5 +- .../service/tool-launch.service.ts | 9 +- .../external-tool.repo.integration.spec.ts | 1 + .../externaltool/external-tool.repo.mapper.ts | 2 + .../school-external-tool.repo.ts | 2 + ...l-configuration-status-response.factory.ts | 1 + .../tool/external-tool.factory.ts | 1 + ...ernal-tool-configuration-status.factory.ts | 1 + .../tool/tool-configuration-status.factory.ts | 1 + .../factory/external-tool-entity.factory.ts | 1 + ...ool-configuration-status-entity.factory.ts | 10 ++ .../school-external-tool-entity.factory.ts | 2 + ...l-configuration-status-response.factory.ts | 1 + backup/setup/context-external-tools.json | 44 ++++++ backup/setup/external-tools.json | 111 +++++++++++++++ backup/setup/school-external-tools.json | 52 +++++++ 56 files changed, 935 insertions(+), 28 deletions(-) create mode 100644 apps/server/src/modules/tool/school-external-tool/entity/school-external-tool-configuration-status.entity.spec.ts create mode 100644 apps/server/src/modules/tool/school-external-tool/entity/school-external-tool-configuration-status.entity.ts create mode 100644 apps/server/src/shared/testing/factory/school-external-tool-configuration-status-entity.factory.ts diff --git a/apps/server/src/modules/tool/common/controller/dto/context-external-tool-configuration-status.response.ts b/apps/server/src/modules/tool/common/controller/dto/context-external-tool-configuration-status.response.ts index ca64669c166..66b0b2ecf4f 100644 --- a/apps/server/src/modules/tool/common/controller/dto/context-external-tool-configuration-status.response.ts +++ b/apps/server/src/modules/tool/common/controller/dto/context-external-tool-configuration-status.response.ts @@ -15,8 +15,15 @@ export class ContextExternalToolConfigurationStatusResponse { }) isOutdatedOnScopeContext: boolean; + @ApiProperty({ + type: Boolean, + description: 'Is the tool deactivated, because of superhero or school administrator', + }) + isDeactivated: boolean; + constructor(props: ContextExternalToolConfigurationStatusResponse) { this.isOutdatedOnScopeSchool = props.isOutdatedOnScopeSchool; this.isOutdatedOnScopeContext = props.isOutdatedOnScopeContext; + this.isDeactivated = props.isDeactivated; } } diff --git a/apps/server/src/modules/tool/common/domain/context-external-tool-configuration-status.ts b/apps/server/src/modules/tool/common/domain/context-external-tool-configuration-status.ts index be533e50212..ac66651841a 100644 --- a/apps/server/src/modules/tool/common/domain/context-external-tool-configuration-status.ts +++ b/apps/server/src/modules/tool/common/domain/context-external-tool-configuration-status.ts @@ -3,8 +3,11 @@ export class ContextExternalToolConfigurationStatus { isOutdatedOnScopeContext: boolean; + isDeactivated: boolean; + constructor(props: ContextExternalToolConfigurationStatus) { this.isOutdatedOnScopeSchool = props.isOutdatedOnScopeSchool; this.isOutdatedOnScopeContext = props.isOutdatedOnScopeContext; + this.isDeactivated = props.isDeactivated; } } diff --git a/apps/server/src/modules/tool/common/mapper/tool-status-response.mapper.ts b/apps/server/src/modules/tool/common/mapper/tool-status-response.mapper.ts index 7e75fdd81ae..3ba0b6d9328 100644 --- a/apps/server/src/modules/tool/common/mapper/tool-status-response.mapper.ts +++ b/apps/server/src/modules/tool/common/mapper/tool-status-response.mapper.ts @@ -7,6 +7,7 @@ export class ToolStatusResponseMapper { new ContextExternalToolConfigurationStatusResponse({ isOutdatedOnScopeSchool: status.isOutdatedOnScopeSchool, isOutdatedOnScopeContext: status.isOutdatedOnScopeContext, + isDeactivated: status.isDeactivated, }); return configurationStatus; diff --git a/apps/server/src/modules/tool/common/service/common-tool.service.spec.ts b/apps/server/src/modules/tool/common/service/common-tool.service.spec.ts index cabd791a766..a47489f0892 100644 --- a/apps/server/src/modules/tool/common/service/common-tool.service.spec.ts +++ b/apps/server/src/modules/tool/common/service/common-tool.service.spec.ts @@ -86,6 +86,7 @@ describe('CommonToolService', () => { toolConfigurationStatusFactory.build({ isOutdatedOnScopeContext: true, isOutdatedOnScopeSchool: true, + isDeactivated: false, }) ); }); @@ -117,6 +118,7 @@ describe('CommonToolService', () => { toolConfigurationStatusFactory.build({ isOutdatedOnScopeContext: true, isOutdatedOnScopeSchool: true, + isDeactivated: false, }) ); }); @@ -210,6 +212,7 @@ describe('CommonToolService', () => { toolConfigurationStatusFactory.build({ isOutdatedOnScopeContext: false, isOutdatedOnScopeSchool: false, + isDeactivated: false, }) ); }); @@ -241,6 +244,76 @@ describe('CommonToolService', () => { toolConfigurationStatusFactory.build({ isOutdatedOnScopeContext: false, isOutdatedOnScopeSchool: false, + isDeactivated: false, + }) + ); + }); + }); + + describe('when schoolExternalTool is deactivated', () => { + const setup = () => { + const externalTool: ExternalTool = externalToolFactory.buildWithId({ version: 1 }); + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ + toolVersion: 2, + status: { isDeactivated: true }, + }); + const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId({ toolVersion: 2 }); + + return { + externalTool, + schoolExternalTool, + contextExternalTool, + }; + }; + + it('should return a configuration status with deactivated true', () => { + const { externalTool, schoolExternalTool, contextExternalTool } = setup(); + + const result: ContextExternalToolConfigurationStatus = service.determineToolConfigurationStatus( + externalTool, + schoolExternalTool, + contextExternalTool + ); + + expect(result).toEqual( + toolConfigurationStatusFactory.build({ + isOutdatedOnScopeContext: false, + isOutdatedOnScopeSchool: false, + isDeactivated: true, + }) + ); + }); + }); + + describe('when externalTool is deactivated', () => { + const setup = () => { + const externalTool: ExternalTool = externalToolFactory.buildWithId({ version: 1, isDeactivated: true }); + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ + toolVersion: 2, + }); + const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId({ toolVersion: 2 }); + + return { + externalTool, + schoolExternalTool, + contextExternalTool, + }; + }; + + it('should return a configuration status with deactivated true', () => { + const { externalTool, schoolExternalTool, contextExternalTool } = setup(); + + const result: ContextExternalToolConfigurationStatus = service.determineToolConfigurationStatus( + externalTool, + schoolExternalTool, + contextExternalTool + ); + + expect(result).toEqual( + toolConfigurationStatusFactory.build({ + isOutdatedOnScopeContext: false, + isOutdatedOnScopeSchool: false, + isDeactivated: true, }) ); }); diff --git a/apps/server/src/modules/tool/common/service/common-tool.service.ts b/apps/server/src/modules/tool/common/service/common-tool.service.ts index b1c8d0e8da2..9b5404f7ae7 100644 --- a/apps/server/src/modules/tool/common/service/common-tool.service.ts +++ b/apps/server/src/modules/tool/common/service/common-tool.service.ts @@ -20,6 +20,7 @@ export class CommonToolService { const configurationStatus: ContextExternalToolConfigurationStatus = new ContextExternalToolConfigurationStatus({ isOutdatedOnScopeContext: true, isOutdatedOnScopeSchool: true, + isDeactivated: false, }); if ( @@ -34,6 +35,10 @@ export class CommonToolService { configurationStatus.isOutdatedOnScopeSchool = true; } + if (externalTool.isDeactivated || schoolExternalTool.status?.isDeactivated) { + configurationStatus.isDeactivated = true; + } + return configurationStatus; } diff --git a/apps/server/src/modules/tool/context-external-tool/service/tool-version-service.spec.ts b/apps/server/src/modules/tool/context-external-tool/service/tool-version-service.spec.ts index 7edeca8459e..f55651f738b 100644 --- a/apps/server/src/modules/tool/context-external-tool/service/tool-version-service.spec.ts +++ b/apps/server/src/modules/tool/context-external-tool/service/tool-version-service.spec.ts @@ -5,6 +5,7 @@ import { contextExternalToolFactory, externalToolFactory, schoolExternalToolFactory, + schoolToolConfigurationStatusFactory, toolConfigurationStatusFactory, } from '@shared/testing'; import { ContextExternalToolConfigurationStatus } from '../../common/domain'; @@ -319,5 +320,132 @@ describe('ToolVersionService', () => { expect(contextExternalToolValidationService.validate).toHaveBeenCalledWith(contextExternalTool); }); }); + + describe('when FEATURE_COMPUTE_TOOL_STATUS_WITHOUT_VERSIONS_ENABLED is true and SchoolExternalTool is deactivated', () => { + const setup = () => { + const externalTool = externalToolFactory.buildWithId(); + const schoolExternalTool = schoolExternalToolFactory.buildWithId({ + toolId: externalTool.id as string, + }); + schoolExternalTool.status = schoolToolConfigurationStatusFactory.build({ isDeactivated: true }); + const contextExternalTool = contextExternalToolFactory + .withSchoolExternalToolRef(schoolExternalTool.id as string) + .buildWithId(); + + toolFeatures.toolStatusWithoutVersions = true; + + schoolExternalToolValidationService.validate.mockRejectedValueOnce(Promise.resolve()); + contextExternalToolValidationService.validate.mockRejectedValueOnce(Promise.resolve()); + + return { + externalTool, + schoolExternalTool, + contextExternalTool, + }; + }; + + it('should return status is deactivated', async () => { + const { externalTool, schoolExternalTool, contextExternalTool } = setup(); + + const status: ContextExternalToolConfigurationStatus = await service.determineToolConfigurationStatus( + externalTool, + schoolExternalTool, + contextExternalTool + ); + + expect(status).toEqual( + toolConfigurationStatusFactory.build({ + isOutdatedOnScopeContext: true, + isOutdatedOnScopeSchool: true, + isDeactivated: true, + }) + ); + }); + }); + + describe('when FEATURE_COMPUTE_TOOL_STATUS_WITHOUT_VERSIONS_ENABLED is true and externalTool is deactivated', () => { + const setup = () => { + const externalTool = externalToolFactory.buildWithId({ + isDeactivated: true, + }); + const schoolExternalTool = schoolExternalToolFactory.buildWithId({ + toolId: externalTool.id as string, + }); + const contextExternalTool = contextExternalToolFactory + .withSchoolExternalToolRef(schoolExternalTool.id as string) + .buildWithId(); + + toolFeatures.toolStatusWithoutVersions = true; + + schoolExternalToolValidationService.validate.mockRejectedValueOnce(Promise.resolve()); + contextExternalToolValidationService.validate.mockRejectedValueOnce(Promise.resolve()); + + return { + externalTool, + schoolExternalTool, + contextExternalTool, + }; + }; + + it('should return deactivated tool status', async () => { + const { externalTool, schoolExternalTool, contextExternalTool } = setup(); + + const status: ContextExternalToolConfigurationStatus = await service.determineToolConfigurationStatus( + externalTool, + schoolExternalTool, + contextExternalTool + ); + + expect(status).toEqual( + toolConfigurationStatusFactory.build({ + isOutdatedOnScopeContext: true, + isOutdatedOnScopeSchool: true, + isDeactivated: true, + }) + ); + }); + }); + + describe('when FEATURE_COMPUTE_TOOL_STATUS_WITHOUT_VERSIONS_ENABLED is true, externalTool and schoolExternalTool are not deactivated', () => { + const setup = () => { + const externalTool = externalToolFactory.buildWithId({}); + + const schoolExternalTool = schoolExternalToolFactory.buildWithId({ + toolId: externalTool.id as string, + }); + const contextExternalTool = contextExternalToolFactory + .withSchoolExternalToolRef(schoolExternalTool.id as string) + .buildWithId(); + + toolFeatures.toolStatusWithoutVersions = true; + + schoolExternalToolValidationService.validate.mockRejectedValueOnce(Promise.resolve()); + contextExternalToolValidationService.validate.mockRejectedValueOnce(Promise.resolve()); + + return { + externalTool, + schoolExternalTool, + contextExternalTool, + }; + }; + + it('should return deactivated tool status', async () => { + const { externalTool, schoolExternalTool, contextExternalTool } = setup(); + + const status: ContextExternalToolConfigurationStatus = await service.determineToolConfigurationStatus( + externalTool, + schoolExternalTool, + contextExternalTool + ); + + expect(status).toEqual( + toolConfigurationStatusFactory.build({ + isOutdatedOnScopeContext: true, + isOutdatedOnScopeSchool: true, + isDeactivated: false, + }) + ); + }); + }); }); }); diff --git a/apps/server/src/modules/tool/context-external-tool/service/tool-version-service.ts b/apps/server/src/modules/tool/context-external-tool/service/tool-version-service.ts index 191e1d0cc77..afe8110a88a 100644 --- a/apps/server/src/modules/tool/context-external-tool/service/tool-version-service.ts +++ b/apps/server/src/modules/tool/context-external-tool/service/tool-version-service.ts @@ -28,6 +28,7 @@ export class ToolVersionService { const configurationStatus: ContextExternalToolConfigurationStatus = new ContextExternalToolConfigurationStatus({ isOutdatedOnScopeContext: false, isOutdatedOnScopeSchool: false, + isDeactivated: this.isToolDeactivated(externalTool, schoolExternalTool), }); try { @@ -52,4 +53,12 @@ export class ToolVersionService { return status; } + + private isToolDeactivated(externalTool: ExternalTool, schoolExternalTool: SchoolExternalTool) { + if (externalTool.isDeactivated || (schoolExternalTool.status && schoolExternalTool.status.isDeactivated)) { + return true; + } + + return false; + } } diff --git a/apps/server/src/modules/tool/external-tool/controller/api-test/tool.api.spec.ts b/apps/server/src/modules/tool/external-tool/controller/api-test/tool.api.spec.ts index 93da1d956b2..e41be01e880 100644 --- a/apps/server/src/modules/tool/external-tool/controller/api-test/tool.api.spec.ts +++ b/apps/server/src/modules/tool/external-tool/controller/api-test/tool.api.spec.ts @@ -87,6 +87,7 @@ describe('ToolController (API)', () => { baseUrl: 'https://link.to-my-tool.com/:key', }, isHidden: false, + isDeactivated: false, logoUrl: 'https://link.to-my-logo.com', url: 'https://link.to-my-tool.com', openNewTab: true, @@ -151,6 +152,7 @@ describe('ToolController (API)', () => { baseUrl: 'https://link.to-my-tool.com/:key', }, isHidden: false, + isDeactivated: false, logoUrl: 'https://link.to-my-logo.com', url: 'https://link.to-my-tool.com', openNewTab: true, @@ -382,6 +384,7 @@ describe('ToolController (API)', () => { baseUrl: 'https://link.to-my-tool.com/:key', }, isHidden: false, + isDeactivated: false, logoUrl: 'https://link.to-my-logo.com', url: 'https://link.to-my-tool.com', openNewTab: true, @@ -449,6 +452,7 @@ describe('ToolController (API)', () => { baseUrl: 'https://link.to-my-tool.com/:key', }, isHidden: false, + isDeactivated: false, logoUrl: 'https://link.to-my-logo.com', url: 'https://link.to-my-tool.com', openNewTab: true, diff --git a/apps/server/src/modules/tool/external-tool/controller/dto/request/external-tool-create.params.ts b/apps/server/src/modules/tool/external-tool/controller/dto/request/external-tool-create.params.ts index 780535f3626..b3e54a14d9b 100644 --- a/apps/server/src/modules/tool/external-tool/controller/dto/request/external-tool-create.params.ts +++ b/apps/server/src/modules/tool/external-tool/controller/dto/request/external-tool-create.params.ts @@ -58,6 +58,14 @@ export class ExternalToolCreateParams { @ApiProperty() isHidden!: boolean; + @IsBoolean() + @ApiProperty({ + type: Boolean, + default: false, + description: 'Tool can be deactivated, related tools can not be added to e.g. school, course or board anymore', + }) + isDeactivated!: boolean; + @IsBoolean() @ApiProperty() openNewTab!: boolean; diff --git a/apps/server/src/modules/tool/external-tool/controller/dto/request/external-tool-update.params.ts b/apps/server/src/modules/tool/external-tool/controller/dto/request/external-tool-update.params.ts index be19e9c0bde..a3b642f496e 100644 --- a/apps/server/src/modules/tool/external-tool/controller/dto/request/external-tool-update.params.ts +++ b/apps/server/src/modules/tool/external-tool/controller/dto/request/external-tool-update.params.ts @@ -62,6 +62,13 @@ export class ExternalToolUpdateParams { @ApiProperty() isHidden!: boolean; + @IsBoolean() + @ApiProperty({ + type: Boolean, + default: false, + }) + isDeactivated!: boolean; + @IsBoolean() @ApiProperty() openNewTab!: boolean; diff --git a/apps/server/src/modules/tool/external-tool/controller/dto/response/external-tool.response.ts b/apps/server/src/modules/tool/external-tool/controller/dto/response/external-tool.response.ts index dc20b85b520..b77d170a876 100644 --- a/apps/server/src/modules/tool/external-tool/controller/dto/response/external-tool.response.ts +++ b/apps/server/src/modules/tool/external-tool/controller/dto/response/external-tool.response.ts @@ -25,6 +25,9 @@ export class ExternalToolResponse { @ApiProperty() isHidden: boolean; + @ApiProperty() + isDeactivated: boolean; + @ApiProperty() openNewTab: boolean; @@ -42,6 +45,7 @@ export class ExternalToolResponse { this.config = response.config; this.parameters = response.parameters; this.isHidden = response.isHidden; + this.isDeactivated = response.isDeactivated; this.openNewTab = response.openNewTab; this.version = response.version; this.restrictToContexts = response.restrictToContexts; diff --git a/apps/server/src/modules/tool/external-tool/domain/external-tool.do.spec.ts b/apps/server/src/modules/tool/external-tool/domain/external-tool.do.spec.ts index 22c77510b3d..b47fef01f6a 100644 --- a/apps/server/src/modules/tool/external-tool/domain/external-tool.do.spec.ts +++ b/apps/server/src/modules/tool/external-tool/domain/external-tool.do.spec.ts @@ -60,6 +60,7 @@ describe('ExternalTool', () => { isHidden: false, openNewTab: false, config: basicToolConfigFactory.build(), + isDeactivated: false, }); }).toThrowError(); }); diff --git a/apps/server/src/modules/tool/external-tool/domain/external-tool.do.ts b/apps/server/src/modules/tool/external-tool/domain/external-tool.do.ts index 9460fad9bc8..4bb48e21ab1 100644 --- a/apps/server/src/modules/tool/external-tool/domain/external-tool.do.ts +++ b/apps/server/src/modules/tool/external-tool/domain/external-tool.do.ts @@ -22,6 +22,8 @@ export interface ExternalToolProps { isHidden: boolean; + isDeactivated: boolean; + openNewTab: boolean; version: number; @@ -44,6 +46,8 @@ export class ExternalTool extends BaseDO implements ToolVersion { isHidden: boolean; + isDeactivated: boolean; + openNewTab: boolean; version: number; @@ -68,6 +72,7 @@ export class ExternalTool extends BaseDO implements ToolVersion { } this.parameters = props.parameters; this.isHidden = props.isHidden; + this.isDeactivated = props.isDeactivated; this.openNewTab = props.openNewTab; this.version = props.version; this.restrictToContexts = props.restrictToContexts; diff --git a/apps/server/src/modules/tool/external-tool/entity/external-tool.entity.spec.ts b/apps/server/src/modules/tool/external-tool/entity/external-tool.entity.spec.ts index 9d037046942..9b144b2c725 100644 --- a/apps/server/src/modules/tool/external-tool/entity/external-tool.entity.spec.ts +++ b/apps/server/src/modules/tool/external-tool/entity/external-tool.entity.spec.ts @@ -57,6 +57,7 @@ describe('ExternalToolEntity', () => { config: basicToolConfig, parameters: [customParameter], isHidden: true, + isDeactivated: false, openNewTab: true, version: 1, }); diff --git a/apps/server/src/modules/tool/external-tool/entity/external-tool.entity.ts b/apps/server/src/modules/tool/external-tool/entity/external-tool.entity.ts index 8a80404fe14..bc79a891392 100644 --- a/apps/server/src/modules/tool/external-tool/entity/external-tool.entity.ts +++ b/apps/server/src/modules/tool/external-tool/entity/external-tool.entity.ts @@ -31,6 +31,9 @@ export class ExternalToolEntity extends BaseEntityWithTimestamps { @Property() isHidden: boolean; + @Property() + isDeactivated: boolean; + @Property() openNewTab: boolean; @@ -49,6 +52,7 @@ export class ExternalToolEntity extends BaseEntityWithTimestamps { this.config = props.config; this.parameters = props.parameters; this.isHidden = props.isHidden; + this.isDeactivated = props.isDeactivated; this.openNewTab = props.openNewTab; this.version = props.version; this.restrictToContexts = props.restrictToContexts; diff --git a/apps/server/src/modules/tool/external-tool/mapper/external-tool-request.mapper.spec.ts b/apps/server/src/modules/tool/external-tool/mapper/external-tool-request.mapper.spec.ts index cf29d8e874c..8089b5b4f87 100644 --- a/apps/server/src/modules/tool/external-tool/mapper/external-tool-request.mapper.spec.ts +++ b/apps/server/src/modules/tool/external-tool/mapper/external-tool-request.mapper.spec.ts @@ -81,6 +81,7 @@ describe('ExternalToolRequestMapper', () => { externalToolCreateParams.isHidden = true; externalToolCreateParams.openNewTab = true; externalToolCreateParams.config = basicConfigParams; + externalToolCreateParams.isDeactivated = true; const customParameterDO: CustomParameter = customParameterFactory.build({ name: 'mockName', @@ -109,6 +110,7 @@ describe('ExternalToolRequestMapper', () => { openNewTab: true, version: 1, config: basicToolConfigDO, + isDeactivated: true, }); return { @@ -170,6 +172,7 @@ describe('ExternalToolRequestMapper', () => { externalToolCreateParams.isHidden = true; externalToolCreateParams.openNewTab = true; externalToolCreateParams.config = lti11ConfigParams; + externalToolCreateParams.isDeactivated = false; const customParameterDO: CustomParameter = customParameterFactory.build({ name: 'mockName', @@ -193,6 +196,7 @@ describe('ExternalToolRequestMapper', () => { openNewTab: true, version: 1, config: lti11ToolConfigDO, + isDeactivated: false, }); return { @@ -256,6 +260,7 @@ describe('ExternalToolRequestMapper', () => { externalToolCreateParams.isHidden = true; externalToolCreateParams.openNewTab = true; externalToolCreateParams.config = oauth2ConfigParams; + externalToolCreateParams.isDeactivated = false; const customParameterDO: CustomParameter = customParameterFactory.build({ name: 'mockName', @@ -279,6 +284,7 @@ describe('ExternalToolRequestMapper', () => { openNewTab: true, version: 1, config: oauth2ToolConfigDO, + isDeactivated: false, }); return { @@ -326,6 +332,7 @@ describe('ExternalToolRequestMapper', () => { externalToolUpdateParams.isHidden = true; externalToolUpdateParams.openNewTab = true; externalToolUpdateParams.config = basicConfigParams; + externalToolUpdateParams.isDeactivated = false; const customParameterDO: CustomParameter = customParameterFactory.build({ name: 'mockName', @@ -355,6 +362,7 @@ describe('ExternalToolRequestMapper', () => { openNewTab: true, version: 1, config: basicToolConfigDO, + isDeactivated: false, }, externalToolUpdateParams.id ); @@ -419,6 +427,7 @@ describe('ExternalToolRequestMapper', () => { externalToolUpdateParams.isHidden = true; externalToolUpdateParams.openNewTab = true; externalToolUpdateParams.config = lti11ConfigParams; + externalToolUpdateParams.isDeactivated = false; const customParameterDO: CustomParameter = customParameterFactory.build({ name: 'mockName', @@ -443,6 +452,7 @@ describe('ExternalToolRequestMapper', () => { openNewTab: true, version: 1, config: lti11ToolConfigDO, + isDeactivated: false, }, externalToolUpdateParams.id ); @@ -509,6 +519,7 @@ describe('ExternalToolRequestMapper', () => { externalToolUpdateParams.isHidden = true; externalToolUpdateParams.openNewTab = true; externalToolUpdateParams.config = oauth2ConfigParams; + externalToolUpdateParams.isDeactivated = false; const customParameterDO: CustomParameter = customParameterFactory.build({ name: 'mockName', @@ -533,6 +544,7 @@ describe('ExternalToolRequestMapper', () => { openNewTab: true, version: 1, config: oauth2ToolConfigDO, + isDeactivated: false, }, externalToolUpdateParams.id ); diff --git a/apps/server/src/modules/tool/external-tool/mapper/external-tool-request.mapper.ts b/apps/server/src/modules/tool/external-tool/mapper/external-tool-request.mapper.ts index c57e0ee457a..3ba9d04db37 100644 --- a/apps/server/src/modules/tool/external-tool/mapper/external-tool-request.mapper.ts +++ b/apps/server/src/modules/tool/external-tool/mapper/external-tool-request.mapper.ts @@ -79,6 +79,7 @@ export class ExternalToolRequestMapper { config: mappedConfig, parameters: mappedCustomParameter, isHidden: externalToolUpdateParams.isHidden, + isDeactivated: externalToolUpdateParams.isDeactivated, openNewTab: externalToolUpdateParams.openNewTab, version, restrictToContexts: externalToolUpdateParams.restrictToContexts, @@ -106,6 +107,7 @@ export class ExternalToolRequestMapper { config: mappedConfig, parameters: mappedCustomParameter, isHidden: externalToolCreateParams.isHidden, + isDeactivated: externalToolCreateParams.isDeactivated, openNewTab: externalToolCreateParams.openNewTab, version, restrictToContexts: externalToolCreateParams.restrictToContexts, diff --git a/apps/server/src/modules/tool/external-tool/mapper/external-tool-response.mapper.spec.ts b/apps/server/src/modules/tool/external-tool/mapper/external-tool-response.mapper.spec.ts index 0a491222081..e87502a5d21 100644 --- a/apps/server/src/modules/tool/external-tool/mapper/external-tool-response.mapper.spec.ts +++ b/apps/server/src/modules/tool/external-tool/mapper/external-tool-response.mapper.spec.ts @@ -61,6 +61,7 @@ describe('ExternalToolResponseMapper', () => { openNewTab: true, version: 1, config: basicToolConfigResponse, + isDeactivated: true, }); const basicToolConfig: BasicToolConfig = basicToolConfigFactory.build({ @@ -91,6 +92,7 @@ describe('ExternalToolResponseMapper', () => { openNewTab: true, version: 1, config: basicToolConfig, + isDeactivated: true, }); return { @@ -157,6 +159,7 @@ describe('ExternalToolResponseMapper', () => { openNewTab: true, version: 1, config: oauth2ToolConfigResponse, + isDeactivated: false, }); const customParameter: CustomParameter = customParameterFactory.build({ @@ -182,6 +185,7 @@ describe('ExternalToolResponseMapper', () => { openNewTab: true, version: 1, config: oauth2ToolConfigDO, + isDeactivated: false, }); return { @@ -245,6 +249,7 @@ describe('ExternalToolResponseMapper', () => { openNewTab: true, version: 1, config: lti11ToolConfigResponse, + isDeactivated: false, }); const customParameter: CustomParameter = customParameterFactory.build({ @@ -269,6 +274,7 @@ describe('ExternalToolResponseMapper', () => { openNewTab: true, version: 1, config: lti11ToolConfigDO, + isDeactivated: false, }); return { diff --git a/apps/server/src/modules/tool/external-tool/mapper/external-tool-response.mapper.ts b/apps/server/src/modules/tool/external-tool/mapper/external-tool-response.mapper.ts index 7be202132ef..283f49906cf 100644 --- a/apps/server/src/modules/tool/external-tool/mapper/external-tool-response.mapper.ts +++ b/apps/server/src/modules/tool/external-tool/mapper/external-tool-response.mapper.ts @@ -63,6 +63,7 @@ export class ExternalToolResponseMapper { config: mappedConfig, parameters: mappedCustomParameter, isHidden: externalTool.isHidden, + isDeactivated: externalTool.isDeactivated, openNewTab: externalTool.openNewTab, version: externalTool.version, restrictToContexts: externalTool.restrictToContexts, diff --git a/apps/server/src/modules/tool/external-tool/service/external-tool-configuration.service.spec.ts b/apps/server/src/modules/tool/external-tool/service/external-tool-configuration.service.spec.ts index 6e6f7159e80..ae0722d937c 100644 --- a/apps/server/src/modules/tool/external-tool/service/external-tool-configuration.service.spec.ts +++ b/apps/server/src/modules/tool/external-tool/service/external-tool-configuration.service.spec.ts @@ -6,6 +6,7 @@ import { customParameterFactory, externalToolFactory, schoolExternalToolFactory, + schoolToolConfigurationStatusFactory, setupEntities, } from '@shared/testing'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; @@ -65,9 +66,11 @@ describe('ExternalToolConfigurationService', () => { externalToolFactory.buildWithId(undefined, 'usedToolId'), externalToolFactory.buildWithId(undefined, 'unusedToolId'), ]; + const externalTools: ExternalTool[] = [ ...notHiddenTools, externalToolFactory.buildWithId({ isHidden: true }, 'hiddenToolId'), + externalToolFactory.buildWithId({ isDeactivated: true }, 'deactivatedToolId'), ]; const externalToolsPage: Page = new Page(externalTools, externalTools.length); const toolIdsInUse: EntityId[] = ['usedToolId', 'hiddenToolId']; @@ -98,6 +101,14 @@ describe('ExternalToolConfigurationService', () => { expect(result.length).toBe(notHiddenTools.length); }); + + it('should filter out deactivated tools', () => { + const { externalToolsPage, toolIdsInUse } = setup(); + + const result: ExternalTool[] = service.filterForAvailableTools(externalToolsPage, toolIdsInUse); + + expect(result.some((tool) => tool.id !== 'deactivatedToolId')).toBe(true); + }); }); }); @@ -176,9 +187,26 @@ describe('ExternalToolConfigurationService', () => { const availableSchoolExternalTools: SchoolExternalTool[] = [ schoolExternalToolFactory.buildWithId({ toolId: usedExternalToolId }, 'usedSchoolExternalToolId'), schoolExternalToolFactory.buildWithId(undefined, 'unusedSchoolExternalToolId'), + schoolExternalToolFactory.buildWithId(undefined, 'deactivatedToolId'), + schoolExternalToolFactory.buildWithId(undefined, 'deactivatedToolId'), + schoolExternalToolFactory.buildWithId(undefined, 'deactivatedToolId'), + schoolExternalToolFactory.buildWithId(undefined, 'unusedSchoolExternalToolId'), schoolExternalToolFactory.buildWithId({ toolId: usedExternalToolHiddenId }, 'usedSchoolExternalToolHiddenId'), ]; + availableSchoolExternalTools.forEach((tool): void => { + if (tool.id === 'deactivatedToolId') { + tool.status = schoolToolConfigurationStatusFactory.build({ + isDeactivated: true, + isOutdatedOnScopeSchool: false, + }); + } + tool.status = schoolToolConfigurationStatusFactory.build({ + isDeactivated: false, + isOutdatedOnScopeSchool: false, + }); + }); + return { externalTools, availableSchoolExternalTools }; }; @@ -192,6 +220,34 @@ describe('ExternalToolConfigurationService', () => { expect(result.every((toolInfo: ContextExternalToolTemplateInfo) => !toolInfo.externalTool.isHidden)).toBe(true); }); + + it('should filter out deactivated external tools', () => { + const { externalTools, availableSchoolExternalTools } = setup(); + + const result: ContextExternalToolTemplateInfo[] = service.filterForAvailableExternalTools( + externalTools, + availableSchoolExternalTools + ); + + expect(result.every((toolInfo: ContextExternalToolTemplateInfo) => !toolInfo.externalTool.isDeactivated)).toBe( + true + ); + }); + + it('should filter out deactivated school external tools', () => { + const { externalTools, availableSchoolExternalTools } = setup(); + + const result: ContextExternalToolTemplateInfo[] = service.filterForAvailableExternalTools( + externalTools, + availableSchoolExternalTools + ); + + expect( + result.every( + (toolInfo: ContextExternalToolTemplateInfo) => !toolInfo.schoolExternalTool.status?.isDeactivated + ) + ).toBe(true); + }); }); }); diff --git a/apps/server/src/modules/tool/external-tool/service/external-tool-configuration.service.ts b/apps/server/src/modules/tool/external-tool/service/external-tool-configuration.service.ts index da852d195c0..8fd21257815 100644 --- a/apps/server/src/modules/tool/external-tool/service/external-tool-configuration.service.ts +++ b/apps/server/src/modules/tool/external-tool/service/external-tool-configuration.service.ts @@ -20,9 +20,9 @@ export class ExternalToolConfigurationService { public filterForAvailableTools(externalTools: Page, toolIdsInUse: EntityId[]): ExternalTool[] { const visibleTools: ExternalTool[] = externalTools.data.filter((tool: ExternalTool): boolean => !tool.isHidden); - const availableTools: ExternalTool[] = visibleTools.filter( - (tool: ExternalTool): boolean => !!tool.id && !toolIdsInUse.includes(tool.id) - ); + const availableTools: ExternalTool[] = visibleTools + .filter((tool: ExternalTool): boolean => !!tool.id && !toolIdsInUse.includes(tool.id)) + .filter((tool) => !tool.isDeactivated); return availableTools; } @@ -72,9 +72,10 @@ export class ExternalToolConfigurationService { const unusedTools: ContextExternalToolTemplateInfo[] = toolsWithSchoolTool.filter( (toolRef): toolRef is ContextExternalToolTemplateInfo => !!toolRef ); - const availableTools: ContextExternalToolTemplateInfo[] = unusedTools.filter( - (toolRef): toolRef is ContextExternalToolTemplateInfo => !toolRef.externalTool.isHidden - ); + const availableTools: ContextExternalToolTemplateInfo[] = unusedTools + .filter((toolRef): toolRef is ContextExternalToolTemplateInfo => !toolRef.externalTool.isHidden) + .filter((toolRef) => !toolRef.externalTool.isDeactivated) + .filter((toolRef) => !toolRef.schoolExternalTool.status?.isDeactivated); return availableTools; } diff --git a/apps/server/src/modules/tool/external-tool/uc/dto/external-tool.types.ts b/apps/server/src/modules/tool/external-tool/uc/dto/external-tool.types.ts index 707546ba55e..a2daafe5f69 100644 --- a/apps/server/src/modules/tool/external-tool/uc/dto/external-tool.types.ts +++ b/apps/server/src/modules/tool/external-tool/uc/dto/external-tool.types.ts @@ -31,6 +31,8 @@ export type ExternalToolDto = { isHidden: boolean; + isDeactivated: boolean; + openNewTab: boolean; version: number; diff --git a/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts b/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts index 8f7f13c1d13..04c9d0d7b5d 100644 --- a/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts +++ b/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts @@ -96,6 +96,7 @@ describe('ToolSchoolController (API)', () => { { name: 'param1', value: 'value' }, { name: 'param2', value: '' }, ], + isDeactivated: false, }; em.persist([ @@ -458,6 +459,7 @@ describe('ToolSchoolController (API)', () => { schoolId: school.id, version: 1, parameters: [paramEntry], + isDeactivated: false, }; const updatedParamEntry: CustomParameterEntryParam = { name: 'param1', value: 'updatedValue' }; @@ -466,6 +468,7 @@ describe('ToolSchoolController (API)', () => { schoolId: school.id, version: 1, parameters: [updatedParamEntry], + isDeactivated: false, }; const schoolExternalToolResponse: SchoolExternalToolResponse = new SchoolExternalToolResponse({ diff --git a/apps/server/src/modules/tool/school-external-tool/controller/domain/school-external-tool-configuration-status.ts b/apps/server/src/modules/tool/school-external-tool/controller/domain/school-external-tool-configuration-status.ts index 8e86a8894e2..b8dcfcd13d3 100644 --- a/apps/server/src/modules/tool/school-external-tool/controller/domain/school-external-tool-configuration-status.ts +++ b/apps/server/src/modules/tool/school-external-tool/controller/domain/school-external-tool-configuration-status.ts @@ -1,7 +1,10 @@ export class SchoolExternalToolConfigurationStatus { isOutdatedOnScopeSchool: boolean; + isDeactivated: boolean; + constructor(props: SchoolExternalToolConfigurationStatus) { this.isOutdatedOnScopeSchool = props.isOutdatedOnScopeSchool; + this.isDeactivated = props.isDeactivated; } } diff --git a/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool-configuration.response.ts b/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool-configuration.response.ts index b8bfe811f83..36d500ba88e 100644 --- a/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool-configuration.response.ts +++ b/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool-configuration.response.ts @@ -8,7 +8,14 @@ export class SchoolExternalToolConfigurationStatusResponse { }) isOutdatedOnScopeSchool: boolean; + @ApiProperty({ + type: Boolean, + description: 'Is the tool deactivated, because of school administrator?', + }) + isDeactivated: boolean; + constructor(props: SchoolExternalToolConfigurationStatusResponse) { this.isOutdatedOnScopeSchool = props.isOutdatedOnScopeSchool; + this.isDeactivated = props.isDeactivated; } } diff --git a/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool-post.params.ts b/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool-post.params.ts index e65b1df0dd5..e21be39714b 100644 --- a/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool-post.params.ts +++ b/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool-post.params.ts @@ -1,6 +1,6 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Type } from 'class-transformer'; -import { IsArray, IsMongoId, IsNumber, IsOptional, IsString, ValidateNested } from 'class-validator'; +import { IsArray, IsBoolean, IsMongoId, IsNumber, IsOptional, IsString, ValidateNested } from 'class-validator'; import { CustomParameterEntryParam } from './custom-parameter-entry.params'; export class SchoolExternalToolPostParams { @@ -21,6 +21,14 @@ export class SchoolExternalToolPostParams { @Type(() => CustomParameterEntryParam) parameters?: CustomParameterEntryParam[]; + @ApiProperty({ + type: Boolean, + default: false, + description: 'Tool can be deactivated, related tools can not be added to e.g. course or board anymore', + }) + @IsBoolean() + isDeactivated!: boolean; + @ApiProperty() @IsNumber() version!: number; diff --git a/apps/server/src/modules/tool/school-external-tool/entity/school-external-tool-configuration-status.entity.spec.ts b/apps/server/src/modules/tool/school-external-tool/entity/school-external-tool-configuration-status.entity.spec.ts new file mode 100644 index 00000000000..ded662d34a6 --- /dev/null +++ b/apps/server/src/modules/tool/school-external-tool/entity/school-external-tool-configuration-status.entity.spec.ts @@ -0,0 +1,38 @@ +import { setupEntities } from '@shared/testing'; +import { schoolExternalToolConfigurationStatusEntityFactory } from '@shared/testing/factory/school-external-tool-configuration-status-entity.factory'; +import { SchoolExternalToolConfigurationStatusEntity } from './school-external-tool-configuration-status.entity'; + +describe('SchoolExternalToolConfigurationStatusEntity', () => { + beforeAll(async () => { + await setupEntities(); + }); + + describe('constructor', () => { + it('should throw an error by empty constructor', () => { + // @ts-expect-error: Test case + const test = () => new SchoolExternalToolConfigurationStatusEntity(); + expect(test).toThrow(); + }); + + it('should create a school external tool configuration status by passing required properties', () => { + const schoolExternalToolConfigurationStatusEntity: SchoolExternalToolConfigurationStatusEntity = + schoolExternalToolConfigurationStatusEntityFactory.build(); + expect( + schoolExternalToolConfigurationStatusEntity instanceof SchoolExternalToolConfigurationStatusEntity + ).toEqual(false); + }); + + it('should set school external tool status', () => { + const schoolExternalToolConfigurationStatusEntity: SchoolExternalToolConfigurationStatusEntity = + new SchoolExternalToolConfigurationStatusEntity({ + isDeactivated: true, + isOutdatedOnScopeSchool: false, + }); + + expect(schoolExternalToolConfigurationStatusEntity).toEqual({ + isDeactivated: true, + isOutdatedOnScopeSchool: false, + }); + }); + }); +}); diff --git a/apps/server/src/modules/tool/school-external-tool/entity/school-external-tool-configuration-status.entity.ts b/apps/server/src/modules/tool/school-external-tool/entity/school-external-tool-configuration-status.entity.ts new file mode 100644 index 00000000000..ea071f996e1 --- /dev/null +++ b/apps/server/src/modules/tool/school-external-tool/entity/school-external-tool-configuration-status.entity.ts @@ -0,0 +1,15 @@ +import { Embeddable, Property } from '@mikro-orm/core'; + +@Embeddable() +export class SchoolExternalToolConfigurationStatusEntity { + @Property() + isOutdatedOnScopeSchool: boolean; + + @Property() + isDeactivated: boolean; + + constructor(props: SchoolExternalToolConfigurationStatusEntity) { + this.isOutdatedOnScopeSchool = props.isOutdatedOnScopeSchool; + this.isDeactivated = props.isDeactivated; + } +} diff --git a/apps/server/src/modules/tool/school-external-tool/entity/school-external-tool.entity.spec.ts b/apps/server/src/modules/tool/school-external-tool/entity/school-external-tool.entity.spec.ts index c3b03b2d0cd..932ec713f54 100644 --- a/apps/server/src/modules/tool/school-external-tool/entity/school-external-tool.entity.spec.ts +++ b/apps/server/src/modules/tool/school-external-tool/entity/school-external-tool.entity.spec.ts @@ -1,11 +1,13 @@ -import { schoolFactory, setupEntities } from '@shared/testing'; -import { schoolExternalToolEntityFactory } from '@shared/testing/factory/school-external-tool-entity.factory'; import { - BasicToolConfigEntity, - CustomParameterEntity, - ExternalToolEntity, - ExternalToolConfigEntity, -} from '../../external-tool/entity'; + basicToolConfigFactory, + customParameterEntityFactory, + externalToolEntityFactory, + schoolFactory, + setupEntities, +} from '@shared/testing'; +import { schoolExternalToolConfigurationStatusEntityFactory } from '@shared/testing/factory/school-external-tool-configuration-status-entity.factory'; +import { schoolExternalToolEntityFactory } from '@shared/testing/factory/school-external-tool-entity.factory'; +import { CustomParameterEntity, ExternalToolEntity, ExternalToolConfigEntity } from '../../external-tool/entity'; import { CustomParameterLocation, CustomParameterScope, CustomParameterType, ToolConfigType } from '../../common/enum'; import { SchoolExternalToolEntity } from './school-external-tool.entity'; @@ -27,11 +29,11 @@ describe('SchoolExternalToolEntity', () => { }); it('should set schoolParameters to empty when is undefined', () => { - const externalToolConfigEntity: ExternalToolConfigEntity = new BasicToolConfigEntity({ + const externalToolConfigEntity: ExternalToolConfigEntity = basicToolConfigFactory.buildWithId({ type: ToolConfigType.OAUTH2, baseUrl: 'mockBaseUrl', }); - const customParameter: CustomParameterEntity = new CustomParameterEntity({ + const customParameter: CustomParameterEntity = customParameterEntityFactory.build({ name: 'parameterName', displayName: 'User Friendly Name', default: 'mock', @@ -43,7 +45,7 @@ describe('SchoolExternalToolEntity', () => { isOptional: false, isProtected: false, }); - const externalToolEntity: ExternalToolEntity = new ExternalToolEntity({ + const externalToolEntity: ExternalToolEntity = externalToolEntityFactory.buildWithId({ name: 'toolName', url: 'mockUrl', logoUrl: 'mockLogoUrl', @@ -52,15 +54,56 @@ describe('SchoolExternalToolEntity', () => { isHidden: true, openNewTab: true, version: 1, + isDeactivated: false, }); - const schoolExternalToolEntity: SchoolExternalToolEntity = new SchoolExternalToolEntity({ + const schoolExternalToolEntity: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId({ tool: externalToolEntity, school: schoolFactory.buildWithId(), schoolParameters: [], toolVersion: 1, + status: schoolExternalToolConfigurationStatusEntityFactory.build(), }); expect(schoolExternalToolEntity.schoolParameters).toEqual([]); }); + + it('should set school external tool configuration status', () => { + const externalToolConfigEntity: ExternalToolConfigEntity = basicToolConfigFactory.buildWithId({ + type: ToolConfigType.OAUTH2, + baseUrl: 'mockBaseUrl', + }); + const customParameter: CustomParameterEntity = customParameterEntityFactory.build({ + name: 'parameterName', + displayName: 'User Friendly Name', + default: 'mock', + location: CustomParameterLocation.PATH, + scope: CustomParameterScope.SCHOOL, + type: CustomParameterType.STRING, + regex: 'mockRegex', + regexComment: 'mockComment', + isOptional: false, + isProtected: false, + }); + const externalToolEntity: ExternalToolEntity = externalToolEntityFactory.buildWithId({ + name: 'toolName', + url: 'mockUrl', + logoUrl: 'mockLogoUrl', + config: externalToolConfigEntity, + parameters: [customParameter], + isHidden: true, + openNewTab: true, + version: 1, + isDeactivated: false, + }); + const schoolExternalToolEntity: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId({ + tool: externalToolEntity, + school: schoolFactory.buildWithId(), + schoolParameters: [], + toolVersion: 1, + status: schoolExternalToolConfigurationStatusEntityFactory.build(), + }); + + expect(schoolExternalToolEntity.status).toEqual({ isDeactivated: false, isOutdatedOnScopeSchool: false }); + }); }); }); diff --git a/apps/server/src/modules/tool/school-external-tool/entity/school-external-tool.entity.ts b/apps/server/src/modules/tool/school-external-tool/entity/school-external-tool.entity.ts index b5545239042..2662a5d3986 100644 --- a/apps/server/src/modules/tool/school-external-tool/entity/school-external-tool.entity.ts +++ b/apps/server/src/modules/tool/school-external-tool/entity/school-external-tool.entity.ts @@ -3,12 +3,14 @@ import { BaseEntityWithTimestamps } from '@shared/domain/entity/base.entity'; import { SchoolEntity } from '@shared/domain/entity/school.entity'; import { CustomParameterEntryEntity } from '../../common/entity'; import { ExternalToolEntity } from '../../external-tool/entity'; +import { SchoolExternalToolConfigurationStatusEntity } from './school-external-tool-configuration-status.entity'; export interface SchoolExternalToolProperties { tool: ExternalToolEntity; school: SchoolEntity; schoolParameters?: CustomParameterEntryEntity[]; toolVersion: number; + status?: SchoolExternalToolConfigurationStatusEntity; } @Entity({ tableName: 'school-external-tools' }) @@ -25,11 +27,15 @@ export class SchoolExternalToolEntity extends BaseEntityWithTimestamps { @Property() toolVersion: number; + @Embedded(() => SchoolExternalToolConfigurationStatusEntity, { object: true, nullable: true }) + status?: SchoolExternalToolConfigurationStatusEntity; + constructor(props: SchoolExternalToolProperties) { super(); this.tool = props.tool; this.school = props.school; this.schoolParameters = props.schoolParameters ?? []; this.toolVersion = props.toolVersion; + this.status = props.status; } } diff --git a/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-request.mapper.spec.ts b/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-request.mapper.spec.ts index 002f2c571cc..dbbc9717c04 100644 --- a/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-request.mapper.spec.ts +++ b/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-request.mapper.spec.ts @@ -1,3 +1,4 @@ +import { schoolToolConfigurationStatusFactory } from '@shared/testing'; import { SchoolExternalToolRequestMapper } from './school-external-tool-request.mapper'; import { SchoolExternalToolDto } from '../uc/dto/school-external-tool.types'; import { CustomParameterEntryParam, SchoolExternalToolPostParams } from '../controller/dto'; @@ -17,6 +18,7 @@ describe('SchoolExternalToolRequestMapper', () => { version: 1, schoolId: 'schoolId', parameters: [param], + isDeactivated: true, }; return { @@ -35,6 +37,7 @@ describe('SchoolExternalToolRequestMapper', () => { parameters: [{ name: param.name, value: param.value }], schoolId: params.schoolId, toolVersion: params.version, + status: schoolToolConfigurationStatusFactory.build({ isDeactivated: true }), }); }); }); @@ -46,6 +49,7 @@ describe('SchoolExternalToolRequestMapper', () => { version: 1, schoolId: 'schoolId', parameters: undefined, + isDeactivated: false, }; return { @@ -63,6 +67,7 @@ describe('SchoolExternalToolRequestMapper', () => { parameters: [], schoolId: params.schoolId, toolVersion: params.version, + status: schoolToolConfigurationStatusFactory.build(), }); }); }); diff --git a/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-request.mapper.ts b/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-request.mapper.ts index eff05c092cb..3fb0da2ee5b 100644 --- a/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-request.mapper.ts +++ b/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-request.mapper.ts @@ -1,6 +1,10 @@ import { Injectable } from '@nestjs/common'; import { CustomParameterEntry } from '../../common/domain'; -import { CustomParameterEntryParam, SchoolExternalToolPostParams } from '../controller/dto'; +import { + CustomParameterEntryParam, + SchoolExternalToolConfigurationStatus, + SchoolExternalToolPostParams, +} from '../controller/dto'; import { SchoolExternalToolDto } from '../uc/dto/school-external-tool.types'; @Injectable() @@ -11,6 +15,10 @@ export class SchoolExternalToolRequestMapper { schoolId: request.schoolId, toolVersion: request.version, parameters: this.mapRequestToCustomParameterEntryDO(request.parameters ?? []), + status: new SchoolExternalToolConfigurationStatus({ + isOutdatedOnScopeSchool: false, + isDeactivated: request.isDeactivated, + }), }; } diff --git a/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-response.mapper.spec.ts b/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-response.mapper.spec.ts index eb2e0da2fe5..73580d89633 100644 --- a/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-response.mapper.spec.ts +++ b/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-response.mapper.spec.ts @@ -58,6 +58,7 @@ describe('SchoolExternalToolResponseMapper', () => { ], status: schoolToolConfigurationStatusResponseFactory.build({ isOutdatedOnScopeSchool: false, + isDeactivated: false, }), }, { @@ -74,6 +75,7 @@ describe('SchoolExternalToolResponseMapper', () => { ], status: schoolToolConfigurationStatusFactory.build({ isOutdatedOnScopeSchool: false, + isDeactivated: false, }), }, ]) @@ -108,6 +110,7 @@ describe('SchoolExternalToolResponseMapper', () => { name: '', status: schoolToolConfigurationStatusResponseFactory.build({ isOutdatedOnScopeSchool: false, + isDeactivated: false, }), }) ); diff --git a/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-response.mapper.ts b/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-response.mapper.ts index 7f48f554003..d0b984a77b8 100644 --- a/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-response.mapper.ts +++ b/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-response.mapper.ts @@ -26,7 +26,7 @@ export class SchoolExternalToolResponseMapper { parameters: this.mapToCustomParameterEntryResponse(schoolExternalTool.parameters), toolVersion: schoolExternalTool.toolVersion, status: SchoolToolConfigurationStatusResponseMapper.mapToResponse( - schoolExternalTool.status ?? { isOutdatedOnScopeSchool: false } + schoolExternalTool.status ?? { isOutdatedOnScopeSchool: false, isDeactivated: false } ), }; } diff --git a/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-status-response.mapper.ts b/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-status-response.mapper.ts index 290ad5c084a..001efe071c0 100644 --- a/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-status-response.mapper.ts +++ b/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-status-response.mapper.ts @@ -6,6 +6,7 @@ export class SchoolToolConfigurationStatusResponseMapper { const configurationStatus: SchoolExternalToolConfigurationStatusResponse = new SchoolExternalToolConfigurationStatusResponse({ isOutdatedOnScopeSchool: status.isOutdatedOnScopeSchool, + isDeactivated: status.isDeactivated, }); return configurationStatus; diff --git a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool.service.spec.ts b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool.service.spec.ts index 43875b3d55c..ad0183f4536 100644 --- a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool.service.spec.ts +++ b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool.service.spec.ts @@ -191,6 +191,19 @@ describe('SchoolExternalToolService', () => { }) ); }); + + it('should return non deactivated tool status', async () => { + const { schoolExternalTool } = setup(); + + const schoolExternalToolDOs: SchoolExternalTool[] = await service.findSchoolExternalTools(schoolExternalTool); + + expect(schoolExternalToolValidationService.validate).toHaveBeenCalledWith(schoolExternalTool); + expect(schoolExternalToolDOs[0].status).toEqual( + schoolToolConfigurationStatusFactory.build({ + isDeactivated: false, + }) + ); + }); }); describe('when FEATURE_COMPUTE_TOOL_STATUS_WITHOUT_VERSIONS_ENABLED is true and validation throws error', () => { @@ -221,6 +234,66 @@ describe('SchoolExternalToolService', () => { ); }); }); + + describe('when FEATURE_COMPUTE_TOOL_STATUS_WITHOUT_VERSIONS_ENABLED is true and schoolExternalTool is deactivated', () => { + const setup = () => { + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.build({ + status: schoolToolConfigurationStatusFactory.build({ isDeactivated: true }), + }); + const externalTool: ExternalTool = externalToolFactory.buildWithId(); + + schoolExternalToolRepo.find.mockResolvedValue([schoolExternalTool]); + externalToolService.findById.mockResolvedValue(externalTool); + schoolExternalToolValidationService.validate.mockRejectedValue(Promise.resolve()); + toolFearures.toolStatusWithoutVersions = true; + + return { + schoolExternalTool, + }; + }; + + it('should return deactivated tool status true', async () => { + const { schoolExternalTool } = setup(); + + const schoolExternalToolDOs: SchoolExternalTool[] = await service.findSchoolExternalTools(schoolExternalTool); + + expect(schoolExternalToolDOs[0].status).toEqual( + schoolToolConfigurationStatusFactory.build({ + isDeactivated: true, + isOutdatedOnScopeSchool: true, + }) + ); + }); + }); + + describe('when FEATURE_COMPUTE_TOOL_STATUS_WITHOUT_VERSIONS_ENABLED is true and externalTool is deactivated', () => { + const setup = () => { + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.build(); + const externalTool: ExternalTool = externalToolFactory.buildWithId({ isDeactivated: true }); + + schoolExternalToolRepo.find.mockResolvedValue([schoolExternalTool]); + externalToolService.findById.mockResolvedValue(externalTool); + schoolExternalToolValidationService.validate.mockRejectedValue(Promise.resolve()); + toolFearures.toolStatusWithoutVersions = true; + + return { + schoolExternalTool, + }; + }; + + it('should return deactivated tool status true', async () => { + const { schoolExternalTool } = setup(); + + const schoolExternalToolDOs: SchoolExternalTool[] = await service.findSchoolExternalTools(schoolExternalTool); + + expect(schoolExternalToolDOs[0].status).toEqual( + schoolToolConfigurationStatusFactory.build({ + isDeactivated: true, + isOutdatedOnScopeSchool: true, + }) + ); + }); + }); }); }); diff --git a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool.service.ts b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool.service.ts index b396147e72c..0d58bcf00d0 100644 --- a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool.service.ts +++ b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool.service.ts @@ -59,6 +59,7 @@ export class SchoolExternalToolService { ): Promise { const status: SchoolExternalToolConfigurationStatus = new SchoolExternalToolConfigurationStatus({ isOutdatedOnScopeSchool: true, + isDeactivated: this.isToolDeactivated(externalTool, tool), }); if (this.toolFeatures.toolStatusWithoutVersions) { @@ -91,4 +92,12 @@ export class SchoolExternalToolService { createdSchoolExternalTool = await this.enrichDataFromExternalTool(createdSchoolExternalTool); return createdSchoolExternalTool; } + + private isToolDeactivated(externalTool: ExternalTool, schoolExternalTool: SchoolExternalTool) { + if (externalTool.isDeactivated || schoolExternalTool.status?.isDeactivated) { + return true; + } + + return false; + } } diff --git a/apps/server/src/modules/tool/tool-launch/controller/api-test/tool-launch.controller.api.spec.ts b/apps/server/src/modules/tool/tool-launch/controller/api-test/tool-launch.controller.api.spec.ts index aa35059b564..d313cae5d50 100644 --- a/apps/server/src/modules/tool/tool-launch/controller/api-test/tool-launch.controller.api.spec.ts +++ b/apps/server/src/modules/tool/tool-launch/controller/api-test/tool-launch.controller.api.spec.ts @@ -16,6 +16,7 @@ import { schoolFactory, customParameterFactory, } from '@shared/testing'; +import { schoolExternalToolConfigurationStatusEntityFactory } from '@shared/testing/factory/school-external-tool-configuration-status-entity.factory'; import { Response } from 'supertest'; import { CustomParameterLocation, CustomParameterScope, ToolConfigType } from '../../../common/enum'; import { ContextExternalToolEntity, ContextExternalToolType } from '../../../context-external-tool/entity'; @@ -174,6 +175,114 @@ describe('ToolLaunchController (API)', () => { }); }); + describe('when user wants to launch a deactivated tool', () => { + describe('when external tool is deactivated', () => { + const setup = async () => { + const school: SchoolEntity = schoolFactory.buildWithId(); + const { teacherUser, teacherAccount } = UserAndAccountTestFactory.buildTeacher({ school }, [ + Permission.CONTEXT_TOOL_USER, + ]); + const course: Course = courseFactory.buildWithId({ school, teachers: [teacherUser] }); + + const externalToolEntity: ExternalToolEntity = externalToolEntityFactory.buildWithId({ + config: basicToolConfigFactory.build({ baseUrl: 'https://mockurl.de', type: ToolConfigType.BASIC }), + version: 1, + isDeactivated: true, + }); + const schoolExternalToolEntity: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId({ + tool: externalToolEntity, + school, + toolVersion: 0, + }); + const contextExternalToolEntity: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ + schoolTool: schoolExternalToolEntity, + contextId: course.id, + contextType: ContextExternalToolType.COURSE, + toolVersion: 0, + }); + + const params: ToolLaunchParams = { contextExternalToolId: contextExternalToolEntity.id }; + + await em.persistAndFlush([ + school, + teacherUser, + teacherAccount, + course, + externalToolEntity, + schoolExternalToolEntity, + contextExternalToolEntity, + ]); + em.clear(); + + const loggedInClient: TestApiClient = await testApiClient.login(teacherAccount); + + return { params, loggedInClient }; + }; + + it('should return a bad request', async () => { + const { params, loggedInClient } = await setup(); + + const response: Response = await loggedInClient.get(`${params.contextExternalToolId}/launch`); + + expect(response.status).toBe(HttpStatus.BAD_REQUEST); + }); + }); + + describe('when school external tool is deactivated', () => { + const setup = async () => { + const school: SchoolEntity = schoolFactory.buildWithId(); + const { teacherUser, teacherAccount } = UserAndAccountTestFactory.buildTeacher({ school }, [ + Permission.CONTEXT_TOOL_USER, + ]); + const course: Course = courseFactory.buildWithId({ school, teachers: [teacherUser] }); + + const externalToolEntity: ExternalToolEntity = externalToolEntityFactory.buildWithId({ + config: basicToolConfigFactory.build({ baseUrl: 'https://mockurl.de', type: ToolConfigType.BASIC }), + version: 1, + }); + const schoolExternalToolEntity: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId({ + tool: externalToolEntity, + school, + toolVersion: 0, + status: schoolExternalToolConfigurationStatusEntityFactory.build({ + isDeactivated: true, + }), + }); + const contextExternalToolEntity: ContextExternalToolEntity = contextExternalToolEntityFactory.buildWithId({ + schoolTool: schoolExternalToolEntity, + contextId: course.id, + contextType: ContextExternalToolType.COURSE, + toolVersion: 0, + }); + + const params: ToolLaunchParams = { contextExternalToolId: contextExternalToolEntity.id }; + + await em.persistAndFlush([ + school, + teacherUser, + teacherAccount, + course, + externalToolEntity, + schoolExternalToolEntity, + contextExternalToolEntity, + ]); + em.clear(); + + const loggedInClient: TestApiClient = await testApiClient.login(teacherAccount); + + return { params, loggedInClient }; + }; + + it('should return a bad request', async () => { + const { params, loggedInClient } = await setup(); + + const response: Response = await loggedInClient.get(`${params.contextExternalToolId}/launch`); + + expect(response.status).toBe(HttpStatus.BAD_REQUEST); + }); + }); + }); + describe('when user wants to launch tool from another school', () => { const setup = async () => { const toolSchool: SchoolEntity = schoolFactory.buildWithId(); diff --git a/apps/server/src/modules/tool/tool-launch/error/tool-status-outdated.loggable-exception.spec.ts b/apps/server/src/modules/tool/tool-launch/error/tool-status-outdated.loggable-exception.spec.ts index 168d32e1f12..814dfad394c 100644 --- a/apps/server/src/modules/tool/tool-launch/error/tool-status-outdated.loggable-exception.spec.ts +++ b/apps/server/src/modules/tool/tool-launch/error/tool-status-outdated.loggable-exception.spec.ts @@ -12,7 +12,8 @@ describe('ToolStatusOutdatedLoggableException', () => { userId, toolId, toolConfigStatus.isOutdatedOnScopeSchool, - toolConfigStatus.isOutdatedOnScopeContext + toolConfigStatus.isOutdatedOnScopeContext, + toolConfigStatus.isDeactivated ); return { @@ -34,6 +35,7 @@ describe('ToolStatusOutdatedLoggableException', () => { toolId: 'toolId', isOutdatedOnScopeSchool: false, isOutdatedOnScopeContext: false, + isDeactivated: false, }, }); }); diff --git a/apps/server/src/modules/tool/tool-launch/error/tool-status-outdated.loggable-exception.ts b/apps/server/src/modules/tool/tool-launch/error/tool-status-outdated.loggable-exception.ts index 59a4fea3766..84b358e2ec6 100644 --- a/apps/server/src/modules/tool/tool-launch/error/tool-status-outdated.loggable-exception.ts +++ b/apps/server/src/modules/tool/tool-launch/error/tool-status-outdated.loggable-exception.ts @@ -7,7 +7,8 @@ export class ToolStatusOutdatedLoggableException extends BadRequestException imp private readonly userId: EntityId, private readonly toolId: EntityId, private readonly isOutdatedOnScopeSchool: boolean, - private readonly isOutdatedOnScopeContext: boolean + private readonly isOutdatedOnScopeContext: boolean, + private readonly isDeactivated: boolean ) { super(); } @@ -22,6 +23,7 @@ export class ToolStatusOutdatedLoggableException extends BadRequestException imp toolId: this.toolId, isOutdatedOnScopeSchool: this.isOutdatedOnScopeSchool, isOutdatedOnScopeContext: this.isOutdatedOnScopeContext, + isDeactivated: this.isDeactivated, }, }; } diff --git a/apps/server/src/modules/tool/tool-launch/service/tool-launch.service.spec.ts b/apps/server/src/modules/tool/tool-launch/service/tool-launch.service.spec.ts index 8bfae71cce1..30ce3c20cbf 100644 --- a/apps/server/src/modules/tool/tool-launch/service/tool-launch.service.spec.ts +++ b/apps/server/src/modules/tool/tool-launch/service/tool-launch.service.spec.ts @@ -200,7 +200,7 @@ describe('ToolLaunchService', () => { }); }); - describe('when tool configuration status is not LATEST', () => { + describe('when tool configuration status is not launchable', () => { const setup = () => { const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId(); const contextExternalTool: ContextExternalTool = contextExternalToolFactory @@ -233,6 +233,7 @@ describe('ToolLaunchService', () => { toolConfigurationStatusFactory.build({ isOutdatedOnScopeContext: true, isOutdatedOnScopeSchool: true, + isDeactivated: true, }) ); @@ -249,7 +250,7 @@ describe('ToolLaunchService', () => { const func = () => service.getLaunchData(userId, launchParams.contextExternalTool); await expect(func).rejects.toThrow( - new ToolStatusOutdatedLoggableException(userId, contextExternalToolId, true, true) + new ToolStatusOutdatedLoggableException(userId, contextExternalToolId, true, true, true) ); }); }); diff --git a/apps/server/src/modules/tool/tool-launch/service/tool-launch.service.ts b/apps/server/src/modules/tool/tool-launch/service/tool-launch.service.ts index bb323017967..8378926a107 100644 --- a/apps/server/src/modules/tool/tool-launch/service/tool-launch.service.ts +++ b/apps/server/src/modules/tool/tool-launch/service/tool-launch.service.ts @@ -54,7 +54,7 @@ export class ToolLaunchService { const { externalTool, schoolExternalTool } = await this.loadToolHierarchy(schoolExternalToolId); - await this.isToolStatusLatestOrThrow(userId, externalTool, schoolExternalTool, contextExternalTool); + await this.isToolStatusLaunchableOrThrow(userId, externalTool, schoolExternalTool, contextExternalTool); const strategy: ToolLaunchStrategy | undefined = this.strategies.get(externalTool.config.type); @@ -84,7 +84,7 @@ export class ToolLaunchService { }; } - private async isToolStatusLatestOrThrow( + private async isToolStatusLaunchableOrThrow( userId: EntityId, externalTool: ExternalTool, schoolExternalTool: SchoolExternalTool, @@ -97,12 +97,13 @@ export class ToolLaunchService { contextExternalTool ); - if (status.isOutdatedOnScopeSchool || status.isOutdatedOnScopeContext) { + if (status.isOutdatedOnScopeSchool || status.isOutdatedOnScopeContext || status.isDeactivated) { throw new ToolStatusOutdatedLoggableException( userId, contextExternalTool.id ?? '', status.isOutdatedOnScopeSchool, - status.isOutdatedOnScopeContext + status.isOutdatedOnScopeContext, + status.isDeactivated ); } } diff --git a/apps/server/src/shared/repo/externaltool/external-tool.repo.integration.spec.ts b/apps/server/src/shared/repo/externaltool/external-tool.repo.integration.spec.ts index e3e2949d5c1..a17320075d6 100644 --- a/apps/server/src/shared/repo/externaltool/external-tool.repo.integration.spec.ts +++ b/apps/server/src/shared/repo/externaltool/external-tool.repo.integration.spec.ts @@ -158,6 +158,7 @@ describe('ExternalToolRepo', () => { isHidden: true, openNewTab: true, version: 2, + isDeactivated: false, }); return { diff --git a/apps/server/src/shared/repo/externaltool/external-tool.repo.mapper.ts b/apps/server/src/shared/repo/externaltool/external-tool.repo.mapper.ts index 7740647ea9f..b9113606c7e 100644 --- a/apps/server/src/shared/repo/externaltool/external-tool.repo.mapper.ts +++ b/apps/server/src/shared/repo/externaltool/external-tool.repo.mapper.ts @@ -40,6 +40,7 @@ export class ExternalToolRepoMapper { config, parameters: this.mapCustomParametersToDOs(entity.parameters || []), isHidden: entity.isHidden, + isDeactivated: entity.isDeactivated, openNewTab: entity.openNewTab, version: entity.version, restrictToContexts: entity.restrictToContexts, @@ -100,6 +101,7 @@ export class ExternalToolRepoMapper { config, parameters: this.mapCustomParameterDOsToEntities(entityDO.parameters ?? []), isHidden: entityDO.isHidden, + isDeactivated: entityDO.isDeactivated, openNewTab: entityDO.openNewTab, version: entityDO.version, restrictToContexts: entityDO.restrictToContexts, diff --git a/apps/server/src/shared/repo/schoolexternaltool/school-external-tool.repo.ts b/apps/server/src/shared/repo/schoolexternaltool/school-external-tool.repo.ts index e910856caef..cfe2a61f940 100644 --- a/apps/server/src/shared/repo/schoolexternaltool/school-external-tool.repo.ts +++ b/apps/server/src/shared/repo/schoolexternaltool/school-external-tool.repo.ts @@ -71,6 +71,7 @@ export class SchoolExternalToolRepo extends BaseDORepo(() => { return { isOutdatedOnScopeSchool: false, + isDeactivated: false, }; }); diff --git a/apps/server/src/shared/testing/factory/domainobject/tool/tool-configuration-status.factory.ts b/apps/server/src/shared/testing/factory/domainobject/tool/tool-configuration-status.factory.ts index e9d6e4f25d4..75458a32094 100644 --- a/apps/server/src/shared/testing/factory/domainobject/tool/tool-configuration-status.factory.ts +++ b/apps/server/src/shared/testing/factory/domainobject/tool/tool-configuration-status.factory.ts @@ -5,5 +5,6 @@ export const toolConfigurationStatusFactory = Factory.define(() => { + return { + isOutdatedOnScopeSchool: false, + isDeactivated: false, + }; + }); diff --git a/apps/server/src/shared/testing/factory/school-external-tool-entity.factory.ts b/apps/server/src/shared/testing/factory/school-external-tool-entity.factory.ts index ff8e62fa4bc..e3f8b45cd59 100644 --- a/apps/server/src/shared/testing/factory/school-external-tool-entity.factory.ts +++ b/apps/server/src/shared/testing/factory/school-external-tool-entity.factory.ts @@ -1,6 +1,7 @@ import { SchoolExternalToolEntity, SchoolExternalToolProperties } from '@modules/tool/school-external-tool/entity'; import { BaseFactory } from '@shared/testing/factory/base.factory'; import { externalToolEntityFactory } from './external-tool-entity.factory'; +import { schoolExternalToolConfigurationStatusEntityFactory } from './school-external-tool-configuration-status-entity.factory'; import { schoolFactory } from './school.factory'; export const schoolExternalToolEntityFactory = BaseFactory.define< @@ -12,5 +13,6 @@ export const schoolExternalToolEntityFactory = BaseFactory.define< school: schoolFactory.buildWithId(), schoolParameters: [{ name: 'schoolMockParameter', value: 'mockValue' }], toolVersion: 0, + status: schoolExternalToolConfigurationStatusEntityFactory.build(), }; }); diff --git a/apps/server/src/shared/testing/factory/school-tool-configuration-status-response.factory.ts b/apps/server/src/shared/testing/factory/school-tool-configuration-status-response.factory.ts index 6dabc2148c0..480b68a181a 100644 --- a/apps/server/src/shared/testing/factory/school-tool-configuration-status-response.factory.ts +++ b/apps/server/src/shared/testing/factory/school-tool-configuration-status-response.factory.ts @@ -5,5 +5,6 @@ export const schoolToolConfigurationStatusResponseFactory = Factory.define(() => { return { isOutdatedOnScopeSchool: false, + isDeactivated: false, }; }); diff --git a/backup/setup/context-external-tools.json b/backup/setup/context-external-tools.json index 239539c14e9..46fd0272a4d 100644 --- a/backup/setup/context-external-tools.json +++ b/backup/setup/context-external-tools.json @@ -97,6 +97,50 @@ "value": "test" }], "toolVersion": 1 + }, + { + "_id": { + "$oid": "647de3cfab79fd5bd57e68fe" + }, + "createdAt": { + "$date": "2023-11-30T15:30:08.532Z" + }, + "updatedAt": { + "$date": "2023-11-30T15:30:08.532Z" + }, + "schoolTool": { + "$oid": "647de374cf6a427b9d39e6be" + }, + "contextId": "5fa3a2f3a9c31a26f4d1d309", + "contextType": "course", + "displayName": "Cypress Test Tool deactivated on External Tool", + "parameters": [{ + "name": "contextparam", + "value": "test" + }], + "toolVersion": 1 + }, + { + "_id": { + "$oid": "647de3cfab79fd5bd57e68ff" + }, + "createdAt": { + "$date": "2023-11-30T15:30:08.532Z" + }, + "updatedAt": { + "$date": "2023-11-30T15:30:08.532Z" + }, + "schoolTool": { + "$oid": "647de374cf6a427b9d39e7be" + }, + "contextId": "5fa3a2f3a9c31a26f4d1d309", + "contextType": "course", + "displayName": "Cypress Test Tool deactivated on School External Tool", + "parameters": [{ + "name": "contextparam", + "value": "test" + }], + "toolVersion": 1 } ] diff --git a/backup/setup/external-tools.json b/backup/setup/external-tools.json index 87764bd724d..f10aecc35a0 100644 --- a/backup/setup/external-tools.json +++ b/backup/setup/external-tools.json @@ -216,6 +216,117 @@ "isHidden": false, "openNewTab": false, "version": 2 + }, + { + "_id": { + "$oid": "647de247cf6a427b9d39e6c3" + }, + "createdAt": { + "$date": "2023-11-30T15:28:04.733Z" + }, + "updatedAt": { + "$date": "2023-11-30T15:32:42.888Z" + }, + "name": "CY Test Tool deactivated External Tool", + "config_type": "basic", + "config_baseUrl": "https:google.com", + "parameters": [{ + "name": "schoolParam", + "displayName": "cypress test school", + "description": "", + "scope": "school", + "location": "path", + "type": "string", + "isOptional": false, + "isProtected": false + }, { + "name": "contextparammm", + "displayName": "cypress test context", + "description": "", + "scope": "context", + "location": "path", + "type": "string", + "isOptional": false, + "isProtected": false + }], + "isHidden": false, + "openNewTab": false, + "version": 2, + "isDeactivated": true + }, + { + "_id": { + "$oid": "647de247cf6a427b9d39e7c3" + }, + "createdAt": { + "$date": "2023-11-30T15:28:04.733Z" + }, + "updatedAt": { + "$date": "2023-11-30T15:32:42.888Z" + }, + "name": "CY Test Tool deactivated School External Tool", + "config_type": "basic", + "config_baseUrl": "https:google.com", + "parameters": [{ + "name": "schoolParam", + "displayName": "cypress test school", + "description": "", + "scope": "school", + "location": "path", + "type": "string", + "isOptional": false, + "isProtected": false + }, { + "name": "contextparammm", + "displayName": "cypress test context", + "description": "", + "scope": "context", + "location": "path", + "type": "string", + "isOptional": false, + "isProtected": false + }], + "isHidden": false, + "openNewTab": false, + "version": 2, + "isDeactivated": false + }, + { + "_id": { + "$oid": "647de247cf6a427b9d39e8c3" + }, + "createdAt": { + "$date": "2023-11-30T15:28:04.733Z" + }, + "updatedAt": { + "$date": "2023-11-30T15:32:42.888Z" + }, + "name": "CY Test Tool active External Tool", + "config_type": "basic", + "config_baseUrl": "https:google.com", + "parameters": [{ + "name": "schoolParam", + "displayName": "cypress test school", + "description": "", + "scope": "school", + "location": "path", + "type": "string", + "isOptional": false, + "isProtected": false + }, { + "name": "contextparammm", + "displayName": "cypress test context", + "description": "", + "scope": "context", + "location": "path", + "type": "string", + "isOptional": false, + "isProtected": false + }], + "isHidden": false, + "openNewTab": false, + "version": 2, + "isDeactivated": false } ] diff --git a/backup/setup/school-external-tools.json b/backup/setup/school-external-tools.json index 6c39fd86c0a..380cf2d46b8 100644 --- a/backup/setup/school-external-tools.json +++ b/backup/setup/school-external-tools.json @@ -154,5 +154,57 @@ "value": "test" }], "toolVersion": 1 + }, + { + "_id": { + "$oid": "647de374cf6a427b9d39e6be" + }, + "createdAt": { + "$date": "2023-11-30T15:29:00.061Z" + }, + "updatedAt": { + "$date": "2023-11-30T15:29:00.061Z" + }, + "tool": { + "$oid": "647de247cf6a427b9d39e6c3" + }, + "school": { + "$oid": "5fa2c5ccb229544f2c69666c" + }, + "schoolParameters": [{ + "name": "schoolParan", + "value": "test" + }], + "toolVersion": 1, + "status": { + "isDeactivated": false, + "isOutdatedOnScopeSchool": false + } + }, + { + "_id": { + "$oid": "647de374cf6a427b9d39e7be" + }, + "createdAt": { + "$date": "2023-11-30T15:29:00.061Z" + }, + "updatedAt": { + "$date": "2023-11-30T15:29:00.061Z" + }, + "tool": { + "$oid": "647de247cf6a427b9d39e7c3" + }, + "school": { + "$oid": "5fa2c5ccb229544f2c69666c" + }, + "schoolParameters": [{ + "name": "schoolParan", + "value": "test" + }], + "toolVersion": 1, + "status": { + "isDeactivated": true, + "isOutdatedOnScopeSchool": false + } } ]