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 new file mode 100644 index 00000000000..ca64669c166 --- /dev/null +++ b/apps/server/src/modules/tool/common/controller/dto/context-external-tool-configuration-status.response.ts @@ -0,0 +1,22 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class ContextExternalToolConfigurationStatusResponse { + @ApiProperty({ + type: Boolean, + description: + 'Is the tool outdated on school scope, because of non matching versions or required parameter changes on ExternalTool?', + }) + isOutdatedOnScopeSchool: boolean; + + @ApiProperty({ + type: Boolean, + description: + 'Is the tool outdated on context scope, because of non matching versions or required parameter changes on SchoolExternalTool?', + }) + isOutdatedOnScopeContext: boolean; + + constructor(props: ContextExternalToolConfigurationStatusResponse) { + this.isOutdatedOnScopeSchool = props.isOutdatedOnScopeSchool; + this.isOutdatedOnScopeContext = props.isOutdatedOnScopeContext; + } +} diff --git a/apps/server/src/modules/tool/common/controller/dto/index.ts b/apps/server/src/modules/tool/common/controller/dto/index.ts index 87fa450d468..e4f13ca8151 100644 --- a/apps/server/src/modules/tool/common/controller/dto/index.ts +++ b/apps/server/src/modules/tool/common/controller/dto/index.ts @@ -1 +1,2 @@ export { ContextExternalToolCountPerContextResponse } from './context-external-tool-count-per-context.response'; +export { ContextExternalToolConfigurationStatusResponse } from './context-external-tool-configuration-status.response'; 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 new file mode 100644 index 00000000000..be533e50212 --- /dev/null +++ b/apps/server/src/modules/tool/common/domain/context-external-tool-configuration-status.ts @@ -0,0 +1,10 @@ +export class ContextExternalToolConfigurationStatus { + isOutdatedOnScopeSchool: boolean; + + isOutdatedOnScopeContext: boolean; + + constructor(props: ContextExternalToolConfigurationStatus) { + this.isOutdatedOnScopeSchool = props.isOutdatedOnScopeSchool; + this.isOutdatedOnScopeContext = props.isOutdatedOnScopeContext; + } +} diff --git a/apps/server/src/modules/tool/common/domain/index.ts b/apps/server/src/modules/tool/common/domain/index.ts index ae59ec3f2a3..27dd13a3fb5 100644 --- a/apps/server/src/modules/tool/common/domain/index.ts +++ b/apps/server/src/modules/tool/common/domain/index.ts @@ -1,3 +1,3 @@ export * from './custom-parameter.do'; export * from './custom-parameter-entry.do'; -export * from '../enum/tool-configuration-status'; +export * from './context-external-tool-configuration-status'; diff --git a/apps/server/src/modules/tool/common/enum/index.ts b/apps/server/src/modules/tool/common/enum/index.ts index 24628ac19f5..0be1ea98eb8 100644 --- a/apps/server/src/modules/tool/common/enum/index.ts +++ b/apps/server/src/modules/tool/common/enum/index.ts @@ -10,4 +10,3 @@ export * from './tool-context-type.enum'; export * from './custom-parameter-location.enum'; export * from './custom-parameter-scope.enum'; export * from './custom-parameter-type.enum'; -export * from './tool-configuration-status'; diff --git a/apps/server/src/modules/tool/common/enum/tool-configuration-status.ts b/apps/server/src/modules/tool/common/enum/tool-configuration-status.ts deleted file mode 100644 index de0db175c49..00000000000 --- a/apps/server/src/modules/tool/common/enum/tool-configuration-status.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum ToolConfigurationStatus { - LATEST = 'Latest', - OUTDATED = 'Outdated', - UNKNOWN = 'Unknown', -} 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 0c16ca50e9a..7e75fdd81ae 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 @@ -1,14 +1,14 @@ -import { ToolConfigurationStatusResponse } from '../../context-external-tool/controller/dto'; -import { ToolConfigurationStatus } from '../enum'; - -export const statusMapping: Record = { - [ToolConfigurationStatus.LATEST]: ToolConfigurationStatusResponse.LATEST, - [ToolConfigurationStatus.OUTDATED]: ToolConfigurationStatusResponse.OUTDATED, - [ToolConfigurationStatus.UNKNOWN]: ToolConfigurationStatusResponse.UNKNOWN, -}; +import { ContextExternalToolConfigurationStatusResponse } from '../controller/dto'; +import { ContextExternalToolConfigurationStatus } from '../domain'; export class ToolStatusResponseMapper { - static mapToResponse(status: ToolConfigurationStatus): ToolConfigurationStatusResponse { - return statusMapping[status]; + static mapToResponse(status: ContextExternalToolConfigurationStatus): ContextExternalToolConfigurationStatusResponse { + const configurationStatus: ContextExternalToolConfigurationStatusResponse = + new ContextExternalToolConfigurationStatusResponse({ + isOutdatedOnScopeSchool: status.isOutdatedOnScopeSchool, + isOutdatedOnScopeContext: status.isOutdatedOnScopeContext, + }); + + 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 7327fa65abf..cabd791a766 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 @@ -1,9 +1,15 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { contextExternalToolFactory, externalToolFactory, schoolExternalToolFactory } from '@shared/testing'; +import { + contextExternalToolFactory, + externalToolFactory, + schoolExternalToolFactory, + toolConfigurationStatusFactory, +} from '@shared/testing'; import { CommonToolService } from './common-tool.service'; import { ExternalTool } from '../../external-tool/domain'; import { SchoolExternalTool } from '../../school-external-tool/domain'; -import { ToolConfigurationStatus, ToolContextType } from '../enum'; +import { ToolContextType } from '../enum'; +import { ContextExternalToolConfigurationStatus } from '../domain'; import { ContextExternalTool } from '../../context-external-tool/domain'; describe('CommonToolService', () => { @@ -36,16 +42,21 @@ describe('CommonToolService', () => { }; }; - it('should return ToolConfigurationStatus.LATEST', () => { + it('should return a configuration status with latest true', () => { const { externalTool, schoolExternalTool, contextExternalTool } = setup(); - const result: ToolConfigurationStatus = service.determineToolConfigurationStatus( + const result: ContextExternalToolConfigurationStatus = service.determineToolConfigurationStatus( externalTool, schoolExternalTool, contextExternalTool ); - expect(result).toBe(ToolConfigurationStatus.LATEST); + expect(result).toEqual( + toolConfigurationStatusFactory.build({ + isOutdatedOnScopeContext: false, + isOutdatedOnScopeSchool: false, + }) + ); }); }); @@ -62,16 +73,21 @@ describe('CommonToolService', () => { }; }; - it('should return ToolConfigurationStatus.OUTDATED', () => { + it('should return outdated status for school level', () => { const { externalTool, schoolExternalTool, contextExternalTool } = setup(); - const result: ToolConfigurationStatus = service.determineToolConfigurationStatus( + const result: ContextExternalToolConfigurationStatus = service.determineToolConfigurationStatus( externalTool, schoolExternalTool, contextExternalTool ); - expect(result).toBe(ToolConfigurationStatus.OUTDATED); + expect(result).toEqual( + toolConfigurationStatusFactory.build({ + isOutdatedOnScopeContext: true, + isOutdatedOnScopeSchool: true, + }) + ); }); }); @@ -88,16 +104,21 @@ describe('CommonToolService', () => { }; }; - it('should return ToolConfigurationStatus.OUTDATED', () => { + it('should return outdated status for context level', () => { const { externalTool, schoolExternalTool, contextExternalTool } = setup(); - const result: ToolConfigurationStatus = service.determineToolConfigurationStatus( + const result: ContextExternalToolConfigurationStatus = service.determineToolConfigurationStatus( externalTool, schoolExternalTool, contextExternalTool ); - expect(result).toBe(ToolConfigurationStatus.OUTDATED); + expect(result).toEqual( + toolConfigurationStatusFactory.build({ + isOutdatedOnScopeContext: true, + isOutdatedOnScopeSchool: true, + }) + ); }); }); @@ -114,16 +135,21 @@ describe('CommonToolService', () => { }; }; - it('should return ToolConfigurationStatus.OUTDATED', () => { + it('should return outdated status for context and school level', () => { const { externalTool, schoolExternalTool, contextExternalTool } = setup(); - const result: ToolConfigurationStatus = service.determineToolConfigurationStatus( + const result: ContextExternalToolConfigurationStatus = service.determineToolConfigurationStatus( externalTool, schoolExternalTool, contextExternalTool ); - expect(result).toBe(ToolConfigurationStatus.OUTDATED); + expect(result).toEqual( + toolConfigurationStatusFactory.build({ + isOutdatedOnScopeContext: true, + isOutdatedOnScopeSchool: true, + }) + ); }); }); @@ -140,16 +166,21 @@ describe('CommonToolService', () => { }; }; - it('should return ToolConfigurationStatus.LATEST', () => { + it('should return a configuration status with latest true', () => { const { externalTool, schoolExternalTool, contextExternalTool } = setup(); - const result: ToolConfigurationStatus = service.determineToolConfigurationStatus( + const result: ContextExternalToolConfigurationStatus = service.determineToolConfigurationStatus( externalTool, schoolExternalTool, contextExternalTool ); - expect(result).toBe(ToolConfigurationStatus.LATEST); + expect(result).toEqual( + toolConfigurationStatusFactory.build({ + isOutdatedOnScopeContext: false, + isOutdatedOnScopeSchool: false, + }) + ); }); }); @@ -166,16 +197,21 @@ describe('CommonToolService', () => { }; }; - it('should return ToolConfigurationStatus.LATEST', () => { + it('should return a configuration status with latest true', () => { const { externalTool, schoolExternalTool, contextExternalTool } = setup(); - const result: ToolConfigurationStatus = service.determineToolConfigurationStatus( + const result: ContextExternalToolConfigurationStatus = service.determineToolConfigurationStatus( externalTool, schoolExternalTool, contextExternalTool ); - expect(result).toBe(ToolConfigurationStatus.LATEST); + expect(result).toEqual( + toolConfigurationStatusFactory.build({ + isOutdatedOnScopeContext: false, + isOutdatedOnScopeSchool: false, + }) + ); }); }); @@ -192,16 +228,21 @@ describe('CommonToolService', () => { }; }; - it('should return ToolConfigurationStatus.LATEST', () => { + it('should return a configuration status with latest true', () => { const { externalTool, schoolExternalTool, contextExternalTool } = setup(); - const result: ToolConfigurationStatus = service.determineToolConfigurationStatus( + const result: ContextExternalToolConfigurationStatus = service.determineToolConfigurationStatus( externalTool, schoolExternalTool, contextExternalTool ); - expect(result).toBe(ToolConfigurationStatus.LATEST); + expect(result).toEqual( + toolConfigurationStatusFactory.build({ + isOutdatedOnScopeContext: false, + isOutdatedOnScopeSchool: false, + }) + ); }); }); }); 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 eea1706aaf1..b1c8d0e8da2 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 @@ -2,7 +2,8 @@ import { Injectable } from '@nestjs/common'; import { ExternalTool } from '../../external-tool/domain'; import { SchoolExternalTool } from '../../school-external-tool/domain'; import { ContextExternalTool } from '../../context-external-tool/domain'; -import { ToolConfigurationStatus, ToolContextType } from '../enum'; +import { ToolContextType } from '../enum'; +import { ContextExternalToolConfigurationStatus } from '../domain'; import { ToolVersion } from '../interface'; // TODO N21-1337 remove class when tool versioning is removed @@ -15,16 +16,25 @@ export class CommonToolService { externalTool: ExternalTool, schoolExternalTool: SchoolExternalTool, contextExternalTool: ContextExternalTool - ): ToolConfigurationStatus { + ): ContextExternalToolConfigurationStatus { + const configurationStatus: ContextExternalToolConfigurationStatus = new ContextExternalToolConfigurationStatus({ + isOutdatedOnScopeContext: true, + isOutdatedOnScopeSchool: true, + }); + if ( this.isLatest(schoolExternalTool, externalTool) && this.isLatest(contextExternalTool, schoolExternalTool) && this.isLatest(contextExternalTool, externalTool) ) { - return ToolConfigurationStatus.LATEST; + configurationStatus.isOutdatedOnScopeContext = false; + configurationStatus.isOutdatedOnScopeSchool = false; + } else { + configurationStatus.isOutdatedOnScopeContext = true; + configurationStatus.isOutdatedOnScopeSchool = true; } - return ToolConfigurationStatus.OUTDATED; + return configurationStatus; } private isLatest(tool1: ToolVersion, tool2: ToolVersion): boolean { diff --git a/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-reference.api.spec.ts b/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-reference.api.spec.ts index bc907e421eb..11952df60bc 100644 --- a/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-reference.api.spec.ts +++ b/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-reference.api.spec.ts @@ -14,14 +14,15 @@ import { externalToolEntityFactory, schoolExternalToolEntityFactory, schoolFactory, + contextExternalToolConfigurationStatusResponseFactory, } from '@shared/testing'; + import { Response } from 'supertest'; import { CustomParameterLocation, CustomParameterScope, ToolContextType } from '../../../common/enum'; import { ExternalToolEntity } from '../../../external-tool/entity'; import { SchoolExternalToolEntity } from '../../../school-external-tool/entity'; import { ContextExternalToolEntity, ContextExternalToolType } from '../../entity'; import { ContextExternalToolContextParams, ToolReferenceListResponse, ToolReferenceResponse } from '../dto'; -import { ToolConfigurationStatusResponse } from '../dto/tool-configuration-status.response'; describe('ToolReferenceController (API)', () => { let app: INestApplication; @@ -174,7 +175,10 @@ describe('ToolReferenceController (API)', () => { { contextToolId: contextExternalToolEntity.id, displayName: contextExternalToolEntity.displayName as string, - status: ToolConfigurationStatusResponse.LATEST, + status: contextExternalToolConfigurationStatusResponseFactory.build({ + isOutdatedOnScopeSchool: false, + isOutdatedOnScopeContext: false, + }), logoUrl: `http://localhost:3030/api/v3/tools/external-tools/${externalToolEntity.id}/logo`, openInNewTab: externalToolEntity.openNewTab, }, @@ -303,7 +307,10 @@ describe('ToolReferenceController (API)', () => { expect(response.body).toEqual({ contextToolId: contextExternalToolEntity.id, displayName: contextExternalToolEntity.displayName as string, - status: ToolConfigurationStatusResponse.LATEST, + status: contextExternalToolConfigurationStatusResponseFactory.build({ + isOutdatedOnScopeSchool: false, + isOutdatedOnScopeContext: false, + }), logoUrl: `http://localhost:3030/api/v3/tools/external-tools/${externalToolEntity.id}/logo`, openInNewTab: externalToolEntity.openNewTab, }); diff --git a/apps/server/src/modules/tool/context-external-tool/controller/dto/index.ts b/apps/server/src/modules/tool/context-external-tool/controller/dto/index.ts index e6da4bb909f..89922cda255 100644 --- a/apps/server/src/modules/tool/context-external-tool/controller/dto/index.ts +++ b/apps/server/src/modules/tool/context-external-tool/controller/dto/index.ts @@ -5,4 +5,3 @@ export * from './context-external-tool-context.params'; export * from './context-external-tool.response'; export * from './tool-reference-list.response'; export * from './tool-reference.response'; -export * from './tool-configuration-status.response'; diff --git a/apps/server/src/modules/tool/context-external-tool/controller/dto/tool-configuration-status.response.ts b/apps/server/src/modules/tool/context-external-tool/controller/dto/tool-configuration-status.response.ts deleted file mode 100644 index 200ac12e101..00000000000 --- a/apps/server/src/modules/tool/context-external-tool/controller/dto/tool-configuration-status.response.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum ToolConfigurationStatusResponse { - LATEST = 'Latest', - OUTDATED = 'Outdated', - UNKNOWN = 'Unknown', -} diff --git a/apps/server/src/modules/tool/context-external-tool/controller/dto/tool-reference.response.ts b/apps/server/src/modules/tool/context-external-tool/controller/dto/tool-reference.response.ts index 0ccefffa6ae..a22eba809d1 100644 --- a/apps/server/src/modules/tool/context-external-tool/controller/dto/tool-reference.response.ts +++ b/apps/server/src/modules/tool/context-external-tool/controller/dto/tool-reference.response.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { ToolConfigurationStatusResponse } from './tool-configuration-status.response'; +import { ContextExternalToolConfigurationStatusResponse } from '../../../common/controller/dto'; export class ToolReferenceResponse { @ApiProperty({ nullable: false, required: true, description: 'The id of the tool in the context' }) @@ -19,13 +19,12 @@ export class ToolReferenceResponse { openInNewTab: boolean; @ApiProperty({ - enum: ToolConfigurationStatusResponse, - enumName: 'ToolConfigurationStatusResponse', + type: ContextExternalToolConfigurationStatusResponse, nullable: false, required: true, description: 'The status of the tool', }) - status: ToolConfigurationStatusResponse; + status: ContextExternalToolConfigurationStatusResponse; constructor(toolReferenceResponse: ToolReferenceResponse) { this.contextToolId = toolReferenceResponse.contextToolId; diff --git a/apps/server/src/modules/tool/context-external-tool/controller/tool-reference.controller.ts b/apps/server/src/modules/tool/context-external-tool/controller/tool-reference.controller.ts index 4f7b7ed2703..1370e9a0a93 100644 --- a/apps/server/src/modules/tool/context-external-tool/controller/tool-reference.controller.ts +++ b/apps/server/src/modules/tool/context-external-tool/controller/tool-reference.controller.ts @@ -60,7 +60,8 @@ export class ToolReferenceController { const toolReferenceResponses: ToolReferenceResponse[] = ContextExternalToolResponseMapper.mapToToolReferenceResponses(toolReferences); - const toolReferenceListResponse = new ToolReferenceListResponse(toolReferenceResponses); + + const toolReferenceListResponse: ToolReferenceListResponse = new ToolReferenceListResponse(toolReferenceResponses); return toolReferenceListResponse; } diff --git a/apps/server/src/modules/tool/context-external-tool/domain/tool-reference.ts b/apps/server/src/modules/tool/context-external-tool/domain/tool-reference.ts index b3ab846d2b0..9089e3596b3 100644 --- a/apps/server/src/modules/tool/context-external-tool/domain/tool-reference.ts +++ b/apps/server/src/modules/tool/context-external-tool/domain/tool-reference.ts @@ -1,4 +1,4 @@ -import { ToolConfigurationStatus } from '../../common/enum'; +import { ContextExternalToolConfigurationStatus } from '../../common/domain'; export class ToolReference { contextToolId: string; @@ -9,7 +9,7 @@ export class ToolReference { openInNewTab: boolean; - status: ToolConfigurationStatus; + status: ContextExternalToolConfigurationStatus; constructor(toolReference: ToolReference) { this.contextToolId = toolReference.contextToolId; diff --git a/apps/server/src/modules/tool/context-external-tool/mapper/tool-reference.mapper.ts b/apps/server/src/modules/tool/context-external-tool/mapper/tool-reference.mapper.ts index be6e6b8ab12..391a4a056bb 100644 --- a/apps/server/src/modules/tool/context-external-tool/mapper/tool-reference.mapper.ts +++ b/apps/server/src/modules/tool/context-external-tool/mapper/tool-reference.mapper.ts @@ -1,4 +1,4 @@ -import { ToolConfigurationStatus } from '../../common/enum'; +import { ContextExternalToolConfigurationStatus } from '../../common/domain'; import { ExternalTool } from '../../external-tool/domain'; import { ContextExternalTool, ToolReference } from '../domain'; @@ -6,7 +6,7 @@ export class ToolReferenceMapper { static mapToToolReference( externalTool: ExternalTool, contextExternalTool: ContextExternalTool, - status: ToolConfigurationStatus + status: ContextExternalToolConfigurationStatus ): ToolReference { const toolReference = new ToolReference({ contextToolId: contextExternalTool.id ?? '', diff --git a/apps/server/src/modules/tool/context-external-tool/service/tool-reference.service.spec.ts b/apps/server/src/modules/tool/context-external-tool/service/tool-reference.service.spec.ts index be90bdd3599..d9e3e1a9c4a 100644 --- a/apps/server/src/modules/tool/context-external-tool/service/tool-reference.service.spec.ts +++ b/apps/server/src/modules/tool/context-external-tool/service/tool-reference.service.spec.ts @@ -1,8 +1,12 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { ObjectId } from '@mikro-orm/mongodb'; import { Test, TestingModule } from '@nestjs/testing'; -import { contextExternalToolFactory, externalToolFactory, schoolExternalToolFactory } from '@shared/testing'; -import { ToolConfigurationStatus } from '../../common/enum'; +import { + contextExternalToolFactory, + externalToolFactory, + schoolExternalToolFactory, + toolConfigurationStatusFactory, +} from '@shared/testing'; import { ExternalToolLogoService, ExternalToolService } from '../../external-tool/service'; import { SchoolExternalToolService } from '../../school-external-tool/service'; import { ToolReference } from '../domain'; @@ -79,7 +83,12 @@ describe('ToolReferenceService', () => { contextExternalToolService.findByIdOrFail.mockResolvedValueOnce(contextExternalTool); schoolExternalToolService.findById.mockResolvedValueOnce(schoolExternalTool); externalToolService.findById.mockResolvedValueOnce(externalTool); - toolVersionService.determineToolConfigurationStatus.mockResolvedValue(ToolConfigurationStatus.OUTDATED); + toolVersionService.determineToolConfigurationStatus.mockResolvedValue( + toolConfigurationStatusFactory.build({ + isOutdatedOnScopeSchool: true, + isOutdatedOnScopeContext: false, + }) + ); externalToolLogoService.buildLogoUrl.mockReturnValue(logoUrl); return { @@ -123,7 +132,10 @@ describe('ToolReferenceService', () => { logoUrl, displayName: contextExternalTool.displayName as string, openInNewTab: externalTool.openNewTab, - status: ToolConfigurationStatus.OUTDATED, + status: toolConfigurationStatusFactory.build({ + isOutdatedOnScopeSchool: true, + isOutdatedOnScopeContext: false, + }), contextToolId: contextExternalToolId, }); }); diff --git a/apps/server/src/modules/tool/context-external-tool/service/tool-reference.service.ts b/apps/server/src/modules/tool/context-external-tool/service/tool-reference.service.ts index 4720c07df74..39894db0aa1 100644 --- a/apps/server/src/modules/tool/context-external-tool/service/tool-reference.service.ts +++ b/apps/server/src/modules/tool/context-external-tool/service/tool-reference.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common'; import { EntityId } from '@shared/domain/types'; import { ExternalTool } from '../../external-tool/domain'; import { ExternalToolLogoService, ExternalToolService } from '../../external-tool/service'; +import { ContextExternalToolConfigurationStatus } from '../../common/domain'; import { SchoolExternalTool } from '../../school-external-tool/domain'; import { SchoolExternalToolService } from '../../school-external-tool/service'; import { ContextExternalTool, ToolReference } from '../domain'; @@ -28,11 +29,12 @@ export class ToolReferenceService { ); const externalTool: ExternalTool = await this.externalToolService.findById(schoolExternalTool.toolId); - const status = await this.toolVersionService.determineToolConfigurationStatus( - externalTool, - schoolExternalTool, - contextExternalTool - ); + const status: ContextExternalToolConfigurationStatus = + await this.toolVersionService.determineToolConfigurationStatus( + externalTool, + schoolExternalTool, + contextExternalTool + ); const toolReference: ToolReference = ToolReferenceMapper.mapToToolReference( externalTool, 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 87f96a096d2..7edeca8459e 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 @@ -1,8 +1,13 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; -import { contextExternalToolFactory, externalToolFactory, schoolExternalToolFactory } from '@shared/testing'; -import { ToolConfigurationStatus } from '../../common/enum'; +import { + contextExternalToolFactory, + externalToolFactory, + schoolExternalToolFactory, + toolConfigurationStatusFactory, +} from '@shared/testing'; +import { ContextExternalToolConfigurationStatus } from '../../common/domain'; import { CommonToolService } from '../../common/service'; import { SchoolExternalToolValidationService } from '../../school-external-tool/service'; import { IToolFeatures, ToolFeatures } from '../../tool-config'; @@ -116,13 +121,18 @@ describe('ToolVersionService', () => { it('should return latest tool status', async () => { const { externalTool, schoolExternalTool, contextExternalTool } = setup(); - const status: ToolConfigurationStatus = await service.determineToolConfigurationStatus( + const status: ContextExternalToolConfigurationStatus = await service.determineToolConfigurationStatus( externalTool, schoolExternalTool, contextExternalTool ); - expect(status).toEqual(ToolConfigurationStatus.LATEST); + expect(status).toEqual( + toolConfigurationStatusFactory.build({ + isOutdatedOnScopeContext: false, + isOutdatedOnScopeSchool: false, + }) + ); }); it('should call schoolExternalToolValidationService', async () => { @@ -142,7 +152,63 @@ describe('ToolVersionService', () => { }); }); - describe('when FEATURE_COMPUTE_TOOL_STATUS_WITHOUT_VERSIONS_ENABLED is true and validation throws an error', () => { + describe('when FEATURE_COMPUTE_TOOL_STATUS_WITHOUT_VERSIONS_ENABLED is true and validation of SchoolExternalTool throws an error', () => { + 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(ApiValidationError); + contextExternalToolValidationService.validate.mockResolvedValue(); + + return { + externalTool, + schoolExternalTool, + contextExternalTool, + }; + }; + + it('should return outdated tool status', async () => { + const { externalTool, schoolExternalTool, contextExternalTool } = setup(); + + const status: ContextExternalToolConfigurationStatus = await service.determineToolConfigurationStatus( + externalTool, + schoolExternalTool, + contextExternalTool + ); + + expect(status).toEqual( + toolConfigurationStatusFactory.build({ + isOutdatedOnScopeContext: false, + isOutdatedOnScopeSchool: true, + }) + ); + }); + + it('should call schoolExternalToolValidationService', async () => { + const { externalTool, schoolExternalTool, contextExternalTool } = setup(); + + await service.determineToolConfigurationStatus(externalTool, schoolExternalTool, contextExternalTool); + + expect(schoolExternalToolValidationService.validate).toHaveBeenCalledWith(schoolExternalTool); + }); + + it('should call contextExternalToolValidationService', async () => { + const { externalTool, schoolExternalTool, contextExternalTool } = setup(); + + await service.determineToolConfigurationStatus(externalTool, schoolExternalTool, contextExternalTool); + + expect(contextExternalToolValidationService.validate).toHaveBeenCalledWith(contextExternalTool); + }); + }); + + describe('when FEATURE_COMPUTE_TOOL_STATUS_WITHOUT_VERSIONS_ENABLED is true and validation of ContextExternalTool throws an error', () => { const setup = () => { const externalTool = externalToolFactory.buildWithId(); const schoolExternalTool = schoolExternalToolFactory.buildWithId({ @@ -167,13 +233,74 @@ describe('ToolVersionService', () => { it('should return outdated tool status', async () => { const { externalTool, schoolExternalTool, contextExternalTool } = setup(); - const status: ToolConfigurationStatus = await service.determineToolConfigurationStatus( + const status: ContextExternalToolConfigurationStatus = await service.determineToolConfigurationStatus( externalTool, schoolExternalTool, contextExternalTool ); - expect(status).toEqual(ToolConfigurationStatus.OUTDATED); + expect(status).toEqual( + toolConfigurationStatusFactory.build({ + isOutdatedOnScopeContext: true, + isOutdatedOnScopeSchool: false, + }) + ); + }); + + it('should call schoolExternalToolValidationService', async () => { + const { externalTool, schoolExternalTool, contextExternalTool } = setup(); + + await service.determineToolConfigurationStatus(externalTool, schoolExternalTool, contextExternalTool); + + expect(schoolExternalToolValidationService.validate).toHaveBeenCalledWith(schoolExternalTool); + }); + + it('should call contextExternalToolValidationService', async () => { + const { externalTool, schoolExternalTool, contextExternalTool } = setup(); + + await service.determineToolConfigurationStatus(externalTool, schoolExternalTool, contextExternalTool); + + expect(contextExternalToolValidationService.validate).toHaveBeenCalledWith(contextExternalTool); + }); + }); + + describe('when FEATURE_COMPUTE_TOOL_STATUS_WITHOUT_VERSIONS_ENABLED is true and validation of SchoolExternalTool and ContextExternalTool throws an error', () => { + 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(ApiValidationError); + contextExternalToolValidationService.validate.mockRejectedValueOnce(ApiValidationError); + + return { + externalTool, + schoolExternalTool, + contextExternalTool, + }; + }; + + it('should return outdated 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, + }) + ); }); it('should call schoolExternalToolValidationService', async () => { 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 91c77a1aa98..191e1d0cc77 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 @@ -1,6 +1,6 @@ import { Inject } from '@nestjs/common'; import { Injectable } from '@nestjs/common/decorators/core/injectable.decorator'; -import { ToolConfigurationStatus } from '../../common/enum'; +import { ContextExternalToolConfigurationStatus } from '../../common/domain'; import { CommonToolService } from '../../common/service'; import { ExternalTool } from '../../external-tool/domain'; import { SchoolExternalTool } from '../../school-external-tool/domain'; @@ -22,18 +22,29 @@ export class ToolVersionService { externalTool: ExternalTool, schoolExternalTool: SchoolExternalTool, contextExternalTool: ContextExternalTool - ): Promise { + ): Promise { // TODO N21-1337 remove if statement, when feature flag is removed if (this.toolFeatures.toolStatusWithoutVersions) { + const configurationStatus: ContextExternalToolConfigurationStatus = new ContextExternalToolConfigurationStatus({ + isOutdatedOnScopeContext: false, + isOutdatedOnScopeSchool: false, + }); + try { await this.schoolExternalToolValidationService.validate(schoolExternalTool); + } catch (err) { + configurationStatus.isOutdatedOnScopeSchool = true; + } + + try { await this.contextExternalToolValidationService.validate(contextExternalTool); - return ToolConfigurationStatus.LATEST; } catch (err) { - return ToolConfigurationStatus.OUTDATED; + configurationStatus.isOutdatedOnScopeContext = true; } + + return configurationStatus; } - const status: ToolConfigurationStatus = this.commonToolService.determineToolConfigurationStatus( + const status: ContextExternalToolConfigurationStatus = this.commonToolService.determineToolConfigurationStatus( externalTool, schoolExternalTool, contextExternalTool diff --git a/apps/server/src/modules/tool/context-external-tool/uc/tool-reference.uc.spec.ts b/apps/server/src/modules/tool/context-external-tool/uc/tool-reference.uc.spec.ts index 12c3ee32a1d..23072469b80 100644 --- a/apps/server/src/modules/tool/context-external-tool/uc/tool-reference.uc.spec.ts +++ b/apps/server/src/modules/tool/context-external-tool/uc/tool-reference.uc.spec.ts @@ -3,8 +3,8 @@ import { AuthorizationContextBuilder } from '@modules/authorization'; import { ForbiddenException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { Permission } from '@shared/domain/interface'; -import { contextExternalToolFactory, externalToolFactory } from '@shared/testing'; -import { ToolConfigurationStatus, ToolContextType } from '../../common/enum'; +import { contextExternalToolFactory, externalToolFactory, toolConfigurationStatusFactory } from '@shared/testing'; +import { ToolContextType } from '../../common/enum'; import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; import { ExternalTool } from '../../external-tool/domain'; import { ContextExternalTool, ToolReference } from '../domain'; @@ -60,7 +60,10 @@ describe('ToolReferenceUc', () => { logoUrl: externalTool.logoUrl, contextToolId: contextExternalTool.id as string, displayName: contextExternalTool.displayName as string, - status: ToolConfigurationStatus.LATEST, + status: toolConfigurationStatusFactory.build({ + isOutdatedOnScopeSchool: false, + isOutdatedOnScopeContext: false, + }), openInNewTab: externalTool.openNewTab, }); @@ -146,7 +149,10 @@ describe('ToolReferenceUc', () => { logoUrl: externalTool.logoUrl, contextToolId: contextExternalTool.id as string, displayName: contextExternalTool.displayName as string, - status: ToolConfigurationStatus.LATEST, + status: toolConfigurationStatusFactory.build({ + isOutdatedOnScopeSchool: false, + isOutdatedOnScopeContext: false, + }), openInNewTab: externalTool.openNewTab, }); 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 3c68ec5c8c2..8f7f13c1d13 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 @@ -16,7 +16,7 @@ import { schoolFactory, userFactory, } from '@shared/testing'; -import { ToolConfigurationStatusResponse } from '../../../context-external-tool/controller/dto'; +import { schoolToolConfigurationStatusFactory } from '@shared/testing/factory'; import { ContextExternalToolEntity, ContextExternalToolType } from '../../../context-external-tool/entity'; import { CustomParameterScope, CustomParameterType, ExternalToolEntity } from '../../../external-tool/entity'; import { SchoolExternalToolEntity } from '../../entity'; @@ -141,7 +141,9 @@ describe('ToolSchoolController (API)', () => { name: externalToolEntity.name, schoolId: postParams.schoolId, toolId: postParams.toolId, - status: ToolConfigurationStatusResponse.LATEST, + status: schoolToolConfigurationStatusFactory.build({ + isOutdatedOnScopeSchool: false, + }), toolVersion: postParams.version, parameters: [ { name: 'param1', value: 'value' }, @@ -298,7 +300,9 @@ describe('ToolSchoolController (API)', () => { name: externalToolEntity.name, schoolId: school.id, toolId: externalToolEntity.id, - status: ToolConfigurationStatusResponse.OUTDATED, + status: schoolToolConfigurationStatusFactory.build({ + isOutdatedOnScopeSchool: true, + }), toolVersion: schoolExternalToolEntity.toolVersion, parameters: [ { @@ -341,7 +345,9 @@ describe('ToolSchoolController (API)', () => { name: '', schoolId: school.id, toolId: externalToolEntity.id, - status: ToolConfigurationStatusResponse.UNKNOWN, + status: schoolToolConfigurationStatusFactory.build({ + isOutdatedOnScopeSchool: false, + }), toolVersion: schoolExternalToolEntity.toolVersion, parameters: [ { @@ -467,7 +473,9 @@ describe('ToolSchoolController (API)', () => { name: externalToolEntity.name, schoolId: postParamsUpdate.schoolId, toolId: postParamsUpdate.toolId, - status: ToolConfigurationStatusResponse.LATEST, + status: schoolToolConfigurationStatusFactory.build({ + isOutdatedOnScopeSchool: false, + }), toolVersion: postParamsUpdate.version, parameters: [ { 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 new file mode 100644 index 00000000000..8e86a8894e2 --- /dev/null +++ b/apps/server/src/modules/tool/school-external-tool/controller/domain/school-external-tool-configuration-status.ts @@ -0,0 +1,7 @@ +export class SchoolExternalToolConfigurationStatus { + isOutdatedOnScopeSchool: boolean; + + constructor(props: SchoolExternalToolConfigurationStatus) { + this.isOutdatedOnScopeSchool = props.isOutdatedOnScopeSchool; + } +} diff --git a/apps/server/src/modules/tool/school-external-tool/controller/dto/index.ts b/apps/server/src/modules/tool/school-external-tool/controller/dto/index.ts index 595b16d7f9c..3631b6989b4 100644 --- a/apps/server/src/modules/tool/school-external-tool/controller/dto/index.ts +++ b/apps/server/src/modules/tool/school-external-tool/controller/dto/index.ts @@ -6,3 +6,4 @@ export * from './school-external-tool-post.params'; export * from './school-external-tool-search.params'; export * from './school-external-tool-search-list.response'; export * from './school-external-tool-metadata.response'; +export * from '../domain/school-external-tool-configuration-status'; 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 new file mode 100644 index 00000000000..b8bfe811f83 --- /dev/null +++ b/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool-configuration.response.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class SchoolExternalToolConfigurationStatusResponse { + @ApiProperty({ + type: Boolean, + description: + 'Is the tool outdated on school scope, because of non matching versions or required parameter changes on ExternalTool?', + }) + isOutdatedOnScopeSchool: boolean; + + constructor(props: SchoolExternalToolConfigurationStatusResponse) { + this.isOutdatedOnScopeSchool = props.isOutdatedOnScopeSchool; + } +} diff --git a/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool.response.ts b/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool.response.ts index 32dd35f10bd..5d86bdcd186 100644 --- a/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool.response.ts +++ b/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool.response.ts @@ -1,6 +1,6 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { ToolConfigurationStatusResponse } from '../../../context-external-tool/controller/dto/tool-configuration-status.response'; import { CustomParameterEntryResponse } from './custom-parameter-entry.response'; +import { SchoolExternalToolConfigurationStatusResponse } from './school-external-tool-configuration.response'; export class SchoolExternalToolResponse { @ApiProperty() @@ -21,8 +21,8 @@ export class SchoolExternalToolResponse { @ApiProperty() toolVersion: number; - @ApiProperty({ enum: ToolConfigurationStatusResponse }) - status: ToolConfigurationStatusResponse; + @ApiProperty({ type: SchoolExternalToolConfigurationStatusResponse }) + status: SchoolExternalToolConfigurationStatusResponse; @ApiPropertyOptional() logoUrl?: string; diff --git a/apps/server/src/modules/tool/school-external-tool/domain/school-external-tool.do.ts b/apps/server/src/modules/tool/school-external-tool/domain/school-external-tool.do.ts index 621ea1f6366..9fc769f6673 100644 --- a/apps/server/src/modules/tool/school-external-tool/domain/school-external-tool.do.ts +++ b/apps/server/src/modules/tool/school-external-tool/domain/school-external-tool.do.ts @@ -1,7 +1,7 @@ import { BaseDO } from '@shared/domain/domainobject/base.do'; import { CustomParameterEntry } from '../../common/domain'; -import { ToolConfigurationStatus } from '../../common/enum'; import { ToolVersion } from '../../common/interface'; +import { SchoolExternalToolConfigurationStatus } from '../controller/dto'; export interface SchoolExternalToolProps { id?: string; @@ -16,7 +16,7 @@ export interface SchoolExternalToolProps { toolVersion: number; - status?: ToolConfigurationStatus; + status?: SchoolExternalToolConfigurationStatus; } export class SchoolExternalTool extends BaseDO implements ToolVersion { @@ -30,7 +30,7 @@ export class SchoolExternalTool extends BaseDO implements ToolVersion { toolVersion: number; - status?: ToolConfigurationStatus; + status?: SchoolExternalToolConfigurationStatus; constructor(props: SchoolExternalToolProps) { super(props.id); diff --git a/apps/server/src/modules/tool/school-external-tool/mapper/index.ts b/apps/server/src/modules/tool/school-external-tool/mapper/index.ts index 48cd0b13a20..4b663b7b50b 100644 --- a/apps/server/src/modules/tool/school-external-tool/mapper/index.ts +++ b/apps/server/src/modules/tool/school-external-tool/mapper/index.ts @@ -1,3 +1,4 @@ export * from './school-external-tool-request.mapper'; export * from './school-external-tool-response.mapper'; export * from './school-external-tool-metadata.mapper'; +export * from './school-external-tool-status-response.mapper'; 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 ca2296e6df7..eb2e0da2fe5 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 @@ -1,5 +1,9 @@ -import { schoolExternalToolFactory } from '@shared/testing/factory'; -import { ToolConfigurationStatusResponse } from '../../context-external-tool/controller/dto'; +import { + schoolExternalToolFactory, + schoolToolConfigurationStatusFactory, + schoolToolConfigurationStatusResponseFactory, +} from '@shared/testing/factory'; + import { SchoolExternalToolResponse, SchoolExternalToolSearchListResponse } from '../controller/dto'; import { SchoolExternalTool } from '../domain'; import { SchoolExternalToolResponseMapper } from './school-external-tool-response.mapper'; @@ -52,7 +56,9 @@ describe('SchoolExternalToolResponseMapper', () => { value: do1.parameters[0].value, }, ], - status: ToolConfigurationStatusResponse.LATEST, + status: schoolToolConfigurationStatusResponseFactory.build({ + isOutdatedOnScopeSchool: false, + }), }, { id: do2.id as string, @@ -66,7 +72,9 @@ describe('SchoolExternalToolResponseMapper', () => { value: do2.parameters[0].value, }, ], - status: ToolConfigurationStatusResponse.UNKNOWN, + status: schoolToolConfigurationStatusFactory.build({ + isOutdatedOnScopeSchool: false, + }), }, ]) ); @@ -98,7 +106,9 @@ describe('SchoolExternalToolResponseMapper', () => { expect.objectContaining({ id: '', name: '', - status: ToolConfigurationStatusResponse.UNKNOWN, + status: schoolToolConfigurationStatusResponseFactory.build({ + isOutdatedOnScopeSchool: 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 7388b1a6a41..7f48f554003 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 @@ -1,13 +1,12 @@ import { Injectable } from '@nestjs/common'; import { CustomParameterEntry } from '../../common/domain'; -import { ToolStatusResponseMapper } from '../../common/mapper/tool-status-response.mapper'; -import { ToolConfigurationStatusResponse } from '../../context-external-tool/controller/dto'; import { CustomParameterEntryResponse, SchoolExternalToolResponse, SchoolExternalToolSearchListResponse, } from '../controller/dto'; import { SchoolExternalTool } from '../domain'; +import { SchoolToolConfigurationStatusResponseMapper } from './school-external-tool-status-response.mapper'; @Injectable() export class SchoolExternalToolResponseMapper { @@ -26,9 +25,9 @@ export class SchoolExternalToolResponseMapper { schoolId: schoolExternalTool.schoolId, parameters: this.mapToCustomParameterEntryResponse(schoolExternalTool.parameters), toolVersion: schoolExternalTool.toolVersion, - status: schoolExternalTool.status - ? ToolStatusResponseMapper.mapToResponse(schoolExternalTool.status) - : ToolConfigurationStatusResponse.UNKNOWN, + status: SchoolToolConfigurationStatusResponseMapper.mapToResponse( + schoolExternalTool.status ?? { isOutdatedOnScopeSchool: 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 new file mode 100644 index 00000000000..290ad5c084a --- /dev/null +++ b/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-status-response.mapper.ts @@ -0,0 +1,13 @@ +import { SchoolExternalToolConfigurationStatus } from '../controller/dto'; +import { SchoolExternalToolConfigurationStatusResponse } from '../controller/dto/school-external-tool-configuration.response'; + +export class SchoolToolConfigurationStatusResponseMapper { + static mapToResponse(status: SchoolExternalToolConfigurationStatus): SchoolExternalToolConfigurationStatusResponse { + const configurationStatus: SchoolExternalToolConfigurationStatusResponse = + new SchoolExternalToolConfigurationStatusResponse({ + isOutdatedOnScopeSchool: status.isOutdatedOnScopeSchool, + }); + + 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 1def9935806..43875b3d55c 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 @@ -2,12 +2,15 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; import { SchoolExternalToolRepo } from '@shared/repo'; -import { externalToolFactory } from '@shared/testing/factory/domainobject/tool/external-tool.factory'; -import { schoolExternalToolFactory } from '@shared/testing/factory/domainobject/tool/school-external-tool.factory'; -import { ToolConfigurationStatus } from '../../common/enum'; +import { + schoolExternalToolFactory, + schoolToolConfigurationStatusFactory, + externalToolFactory, +} from '@shared/testing/factory'; import { ExternalTool } from '../../external-tool/domain'; import { ExternalToolService } from '../../external-tool/service'; import { IToolFeatures, ToolFeatures } from '../../tool-config'; +import { SchoolExternalToolConfigurationStatus } from '../controller/domain/school-external-tool-configuration-status'; import { SchoolExternalTool } from '../domain'; import { SchoolExternalToolValidationService } from './school-external-tool-validation.service'; import { SchoolExternalToolService } from './school-external-tool.service'; @@ -112,7 +115,11 @@ describe('SchoolExternalToolService', () => { schoolExternalTool ); - expect(schoolExternalToolDOs[0].status).toEqual(ToolConfigurationStatus.OUTDATED); + expect(schoolExternalToolDOs[0].status).toEqual( + schoolToolConfigurationStatusFactory.build({ + isOutdatedOnScopeSchool: true, + }) + ); }); }); @@ -128,7 +135,11 @@ describe('SchoolExternalToolService', () => { schoolExternalTool ); - expect(schoolExternalToolDOs[0].status).toEqual(ToolConfigurationStatus.LATEST); + expect(schoolExternalToolDOs[0].status).toEqual( + schoolToolConfigurationStatusFactory.build({ + isOutdatedOnScopeSchool: false, + }) + ); }); }); @@ -144,7 +155,11 @@ describe('SchoolExternalToolService', () => { schoolExternalTool ); - expect(schoolExternalToolDOs[0].status).toEqual(ToolConfigurationStatus.LATEST); + expect(schoolExternalToolDOs[0].status).toEqual( + schoolToolConfigurationStatusFactory.build({ + isOutdatedOnScopeSchool: false, + }) + ); }); }); }); @@ -170,7 +185,11 @@ describe('SchoolExternalToolService', () => { const schoolExternalToolDOs: SchoolExternalTool[] = await service.findSchoolExternalTools(schoolExternalTool); expect(schoolExternalToolValidationService.validate).toHaveBeenCalledWith(schoolExternalTool); - expect(schoolExternalToolDOs[0].status).toEqual(ToolConfigurationStatus.LATEST); + expect(schoolExternalToolDOs[0].status).toEqual( + schoolToolConfigurationStatusFactory.build({ + isOutdatedOnScopeSchool: false, + }) + ); }); }); @@ -195,7 +214,11 @@ describe('SchoolExternalToolService', () => { const schoolExternalToolDOs: SchoolExternalTool[] = await service.findSchoolExternalTools(schoolExternalTool); expect(schoolExternalToolValidationService.validate).toHaveBeenCalledWith(schoolExternalTool); - expect(schoolExternalToolDOs[0].status).toEqual(ToolConfigurationStatus.OUTDATED); + expect(schoolExternalToolDOs[0].status).toEqual( + schoolToolConfigurationStatusFactory.build({ + 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 a36d7544d94..b396147e72c 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 @@ -1,10 +1,10 @@ import { Inject, Injectable } from '@nestjs/common'; import { EntityId } from '@shared/domain/types'; import { SchoolExternalToolRepo } from '@shared/repo'; -import { ToolConfigurationStatus } from '../../common/enum'; import { ExternalTool } from '../../external-tool/domain'; import { ExternalToolService } from '../../external-tool/service'; import { IToolFeatures, ToolFeatures } from '../../tool-config'; +import { SchoolExternalToolConfigurationStatus } from '../controller/dto'; import { SchoolExternalTool } from '../domain'; import { SchoolExternalToolQuery } from '../uc/dto/school-external-tool.types'; import { SchoolExternalToolValidationService } from './school-external-tool-validation.service'; @@ -43,7 +43,7 @@ export class SchoolExternalToolService { private async enrichDataFromExternalTool(tool: SchoolExternalTool): Promise { const externalTool: ExternalTool = await this.externalToolService.findById(tool.toolId); - const status: ToolConfigurationStatus = await this.determineStatus(tool, externalTool); + const status: SchoolExternalToolConfigurationStatus = await this.determineSchoolToolStatus(tool, externalTool); const schoolExternalTool: SchoolExternalTool = new SchoolExternalTool({ ...tool, status, @@ -53,24 +53,33 @@ export class SchoolExternalToolService { return schoolExternalTool; } - private async determineStatus( + private async determineSchoolToolStatus( tool: SchoolExternalTool, externalTool: ExternalTool - ): Promise { + ): Promise { + const status: SchoolExternalToolConfigurationStatus = new SchoolExternalToolConfigurationStatus({ + isOutdatedOnScopeSchool: true, + }); + if (this.toolFeatures.toolStatusWithoutVersions) { try { await this.schoolExternalToolValidationService.validate(tool); - return ToolConfigurationStatus.LATEST; + + status.isOutdatedOnScopeSchool = false; + + return status; } catch (err) { - return ToolConfigurationStatus.OUTDATED; + return status; } } if (externalTool.version <= tool.toolVersion) { - return ToolConfigurationStatus.LATEST; + status.isOutdatedOnScopeSchool = false; + + return status; } - return ToolConfigurationStatus.OUTDATED; + return status; } async deleteSchoolExternalToolById(schoolExternalToolId: EntityId): Promise { 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 c20249fd69f..168d32e1f12 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 @@ -1,3 +1,4 @@ +import { toolConfigurationStatusFactory } from '@shared/testing'; import { ToolStatusOutdatedLoggableException } from './tool-status-outdated.loggable-exception'; describe('ToolStatusOutdatedLoggableException', () => { @@ -5,8 +6,14 @@ describe('ToolStatusOutdatedLoggableException', () => { const setup = () => { const toolId = 'toolId'; const userId = 'userId'; + const toolConfigStatus = toolConfigurationStatusFactory.build(); - const exception = new ToolStatusOutdatedLoggableException(userId, toolId); + const exception = new ToolStatusOutdatedLoggableException( + userId, + toolId, + toolConfigStatus.isOutdatedOnScopeSchool, + toolConfigStatus.isOutdatedOnScopeContext + ); return { exception, @@ -25,6 +32,8 @@ describe('ToolStatusOutdatedLoggableException', () => { data: { userId: 'userId', toolId: 'toolId', + isOutdatedOnScopeSchool: false, + isOutdatedOnScopeContext: 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 b51ebbd8dee..59a4fea3766 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 @@ -3,7 +3,12 @@ import { EntityId } from '@shared/domain/types'; import { ErrorLogMessage, Loggable, LogMessage, ValidationErrorLogMessage } from '@src/core/logger'; export class ToolStatusOutdatedLoggableException extends BadRequestException implements Loggable { - constructor(private readonly userId: EntityId, private readonly toolId: EntityId) { + constructor( + private readonly userId: EntityId, + private readonly toolId: EntityId, + private readonly isOutdatedOnScopeSchool: boolean, + private readonly isOutdatedOnScopeContext: boolean + ) { super(); } @@ -15,6 +20,8 @@ export class ToolStatusOutdatedLoggableException extends BadRequestException imp data: { userId: this.userId, toolId: this.toolId, + isOutdatedOnScopeSchool: this.isOutdatedOnScopeSchool, + isOutdatedOnScopeContext: this.isOutdatedOnScopeContext, }, }; } 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 c16dc16f365..8bfae71cce1 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 @@ -6,8 +6,9 @@ import { contextExternalToolFactory, externalToolFactory, schoolExternalToolFactory, + toolConfigurationStatusFactory, } from '@shared/testing'; -import { ToolConfigType, ToolConfigurationStatus } from '../../common/enum'; +import { ToolConfigType } from '../../common/enum'; import { ContextExternalTool } from '../../context-external-tool/domain'; import { BasicToolConfig, ExternalTool } from '../../external-tool/domain'; import { ExternalToolService } from '../../external-tool/service'; @@ -107,7 +108,12 @@ describe('ToolLaunchService', () => { schoolExternalToolService.findById.mockResolvedValue(schoolExternalTool); externalToolService.findById.mockResolvedValue(externalTool); basicToolLaunchStrategy.createLaunchData.mockResolvedValue(launchDataDO); - toolVersionService.determineToolConfigurationStatus.mockResolvedValueOnce(ToolConfigurationStatus.LATEST); + toolVersionService.determineToolConfigurationStatus.mockResolvedValueOnce( + toolConfigurationStatusFactory.build({ + isOutdatedOnScopeContext: false, + isOutdatedOnScopeSchool: false, + }) + ); return { launchDataDO, @@ -173,7 +179,12 @@ describe('ToolLaunchService', () => { schoolExternalToolService.findById.mockResolvedValue(schoolExternalTool); externalToolService.findById.mockResolvedValue(externalTool); - toolVersionService.determineToolConfigurationStatus.mockResolvedValueOnce(ToolConfigurationStatus.LATEST); + toolVersionService.determineToolConfigurationStatus.mockResolvedValueOnce( + toolConfigurationStatusFactory.build({ + isOutdatedOnScopeContext: false, + isOutdatedOnScopeSchool: false, + }) + ); return { launchParams, @@ -218,7 +229,12 @@ describe('ToolLaunchService', () => { schoolExternalToolService.findById.mockResolvedValue(schoolExternalTool); externalToolService.findById.mockResolvedValue(externalTool); basicToolLaunchStrategy.createLaunchData.mockResolvedValue(launchDataDO); - toolVersionService.determineToolConfigurationStatus.mockResolvedValueOnce(ToolConfigurationStatus.OUTDATED); + toolVersionService.determineToolConfigurationStatus.mockResolvedValueOnce( + toolConfigurationStatusFactory.build({ + isOutdatedOnScopeContext: true, + isOutdatedOnScopeSchool: true, + }) + ); return { launchParams, @@ -232,7 +248,9 @@ describe('ToolLaunchService', () => { const func = () => service.getLaunchData(userId, launchParams.contextExternalTool); - await expect(func).rejects.toThrow(new ToolStatusOutdatedLoggableException(userId, contextExternalToolId)); + await expect(func).rejects.toThrow( + new ToolStatusOutdatedLoggableException(userId, contextExternalToolId, 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 2d84ec1ad7d..bb323017967 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 @@ -1,6 +1,7 @@ import { Injectable, InternalServerErrorException } from '@nestjs/common'; import { EntityId } from '@shared/domain/types'; -import { ToolConfigType, ToolConfigurationStatus } from '../../common/enum'; +import { ContextExternalToolConfigurationStatus } from '../../common/domain'; +import { ToolConfigType } from '../../common/enum'; import { ContextExternalTool } from '../../context-external-tool/domain'; import { ToolVersionService } from '../../context-external-tool/service/tool-version-service'; import { ExternalTool } from '../../external-tool/domain'; @@ -89,14 +90,20 @@ export class ToolLaunchService { schoolExternalTool: SchoolExternalTool, contextExternalTool: ContextExternalTool ): Promise { - const status = await this.toolVersionService.determineToolConfigurationStatus( - externalTool, - schoolExternalTool, - contextExternalTool - ); - - if (status !== ToolConfigurationStatus.LATEST) { - throw new ToolStatusOutdatedLoggableException(userId, contextExternalTool.id ?? ''); + const status: ContextExternalToolConfigurationStatus = + await this.toolVersionService.determineToolConfigurationStatus( + externalTool, + schoolExternalTool, + contextExternalTool + ); + + if (status.isOutdatedOnScopeSchool || status.isOutdatedOnScopeContext) { + throw new ToolStatusOutdatedLoggableException( + userId, + contextExternalTool.id ?? '', + status.isOutdatedOnScopeSchool, + status.isOutdatedOnScopeContext + ); } } } diff --git a/apps/server/src/shared/testing/factory/context-external-tool-configuration-status-response.factory.ts b/apps/server/src/shared/testing/factory/context-external-tool-configuration-status-response.factory.ts new file mode 100644 index 00000000000..a2e9ec6c2d2 --- /dev/null +++ b/apps/server/src/shared/testing/factory/context-external-tool-configuration-status-response.factory.ts @@ -0,0 +1,10 @@ +import { ContextExternalToolConfigurationStatusResponse } from '@modules/tool/common/controller/dto/context-external-tool-configuration-status.response'; +import { Factory } from 'fishery'; + +export const contextExternalToolConfigurationStatusResponseFactory = + Factory.define(() => { + return { + isOutdatedOnScopeContext: false, + isOutdatedOnScopeSchool: false, + }; + }); diff --git a/apps/server/src/shared/testing/factory/domainobject/tool/index.ts b/apps/server/src/shared/testing/factory/domainobject/tool/index.ts index 61ca058e8a7..2baa19dc715 100644 --- a/apps/server/src/shared/testing/factory/domainobject/tool/index.ts +++ b/apps/server/src/shared/testing/factory/domainobject/tool/index.ts @@ -1,3 +1,5 @@ export * from './context-external-tool.factory'; export * from './external-tool.factory'; export * from './school-external-tool.factory'; +export * from './tool-configuration-status.factory'; +export * from './school-external-tool-configuration-status.factory'; diff --git a/apps/server/src/shared/testing/factory/domainobject/tool/school-external-tool-configuration-status.factory.ts b/apps/server/src/shared/testing/factory/domainobject/tool/school-external-tool-configuration-status.factory.ts new file mode 100644 index 00000000000..1153fa87da2 --- /dev/null +++ b/apps/server/src/shared/testing/factory/domainobject/tool/school-external-tool-configuration-status.factory.ts @@ -0,0 +1,8 @@ +import { SchoolExternalToolConfigurationStatus } from '@modules/tool/school-external-tool/controller/dto'; +import { Factory } from 'fishery'; + +export const schoolToolConfigurationStatusFactory = Factory.define(() => { + return { + isOutdatedOnScopeSchool: false, + }; +}); diff --git a/apps/server/src/shared/testing/factory/domainobject/tool/school-external-tool.factory.ts b/apps/server/src/shared/testing/factory/domainobject/tool/school-external-tool.factory.ts index a10ae7797fe..94ea7e8b392 100644 --- a/apps/server/src/shared/testing/factory/domainobject/tool/school-external-tool.factory.ts +++ b/apps/server/src/shared/testing/factory/domainobject/tool/school-external-tool.factory.ts @@ -1,7 +1,8 @@ -import { CustomParameterEntry, ToolConfigurationStatus } from '@modules/tool/common/domain'; +import { CustomParameterEntry } from '@modules/tool/common/domain'; import { SchoolExternalTool, SchoolExternalToolProps } from '@modules/tool/school-external-tool/domain'; import { DeepPartial } from 'fishery'; import { DoBaseFactory } from '../do-base.factory'; +import { schoolToolConfigurationStatusFactory } from './school-external-tool-configuration-status.factory'; class SchoolExternalToolFactory extends DoBaseFactory { withSchoolId(schoolId: string): this { @@ -24,6 +25,6 @@ export const schoolExternalToolFactory = SchoolExternalToolFactory.define(School }), ], toolId: 'toolId', - status: ToolConfigurationStatus.LATEST, + status: schoolToolConfigurationStatusFactory.build(), }; }); 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 new file mode 100644 index 00000000000..e9d6e4f25d4 --- /dev/null +++ b/apps/server/src/shared/testing/factory/domainobject/tool/tool-configuration-status.factory.ts @@ -0,0 +1,9 @@ +import { ContextExternalToolConfigurationStatus } from '@modules/tool/common/domain'; +import { Factory } from 'fishery'; + +export const toolConfigurationStatusFactory = Factory.define(() => { + return { + isOutdatedOnScopeContext: false, + isOutdatedOnScopeSchool: false, + }; +}); diff --git a/apps/server/src/shared/testing/factory/index.ts b/apps/server/src/shared/testing/factory/index.ts index c177ae70942..645dcb56445 100644 --- a/apps/server/src/shared/testing/factory/index.ts +++ b/apps/server/src/shared/testing/factory/index.ts @@ -41,3 +41,5 @@ export * from './legacy-file-entity-mock.factory'; export * from './jwt.test.factory'; export * from './axios-error.factory'; export { externalSchoolDtoFactory } from './external-school-dto.factory'; +export * from './context-external-tool-configuration-status-response.factory'; +export * from './school-tool-configuration-status-response.factory'; 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 new file mode 100644 index 00000000000..6dabc2148c0 --- /dev/null +++ b/apps/server/src/shared/testing/factory/school-tool-configuration-status-response.factory.ts @@ -0,0 +1,9 @@ +import { SchoolExternalToolConfigurationStatusResponse } from '@modules/tool/school-external-tool/controller/dto/school-external-tool-configuration.response'; +import { Factory } from 'fishery'; + +export const schoolToolConfigurationStatusResponseFactory = + Factory.define(() => { + return { + isOutdatedOnScopeSchool: false, + }; + }); diff --git a/backup/setup/context-external-tools.json b/backup/setup/context-external-tools.json index 88bb98d33ec..239539c14e9 100644 --- a/backup/setup/context-external-tools.json +++ b/backup/setup/context-external-tools.json @@ -34,5 +34,69 @@ "$oid": "647de374cf6a427b9d39e5ba" }, "toolVersion": 2 + }, + { + "_id": { + "$oid": "647de3cfab79fd5bd57e68f5" + }, + "createdAt": { + "$date": "2023-11-30T15:25:00.241Z" + }, + "updatedAt": { + "$date": "2023-11-30T15:25:00.241Z" + }, + "schoolTool": { + "$oid": "647de374cf6a427b9d39e5bc" + }, + "contextId": "5fa3a2f3a9c31a26f4d1d309", + "contextType": "course", + "displayName": "Cypress Test Tool School Scope", + "parameters": [], + "toolVersion": 1 + }, + { + "_id": { + "$oid": "647de3cfab79fd5bd57e68f7" + }, + "createdAt": { + "$date": "2023-11-30T15:29:54.630Z" + }, + "updatedAt": { + "$date": "2023-11-30T15:29:54.630Z" + }, + "schoolTool": { + "$oid": "647de374cf6a427b9d39e5bd" + }, + "contextId": "5fa3a2f3a9c31a26f4d1d309", + "contextType": "course", + "displayName": "Cypress Test Tool Context Scope", + "parameters": [{ + "name": "search", + "value": "test" + }], + "toolVersion": 1 + }, + { + "_id": { + "$oid": "647de3cfab79fd5bd57e68f9" + }, + "createdAt": { + "$date": "2023-11-30T15:30:08.532Z" + }, + "updatedAt": { + "$date": "2023-11-30T15:30:08.532Z" + }, + "schoolTool": { + "$oid": "647de374cf6a427b9d39e5be" + }, + "contextId": "5fa3a2f3a9c31a26f4d1d309", + "contextType": "course", + "displayName": "Cypress Test Tool School and Context Scope", + "parameters": [{ + "name": "contextparam", + "value": "test" + }], + "toolVersion": 1 } + ] diff --git a/backup/setup/external-tools.json b/backup/setup/external-tools.json index ffec19620bd..1bcd7d1c045 100644 --- a/backup/setup/external-tools.json +++ b/backup/setup/external-tools.json @@ -121,5 +121,92 @@ "restrictToContexts": [ "board-element" ] + }, + { + "_id": { + "$oid": "647de247cf6a427b9d39e5b1" + }, + "createdAt": { + "$date": "2023-11-30T12:37:54.977Z" + }, + "updatedAt": { + "$date": "2023-11-30T15:31:47.749Z" + }, + "name": "Cy Test Tool School Scope", + "config_type": "basic", + "config_baseUrl": "http:google.com", + "parameters": [{ + "name": "searchparam", + "displayName": "searchparameter", + "description": "", + "scope": "school", + "location": "path", + "type": "string", + "isOptional": false + }], + "isHidden": false, + "openNewTab": false, + "version": 2 + }, + { + "_id": { + "$oid": "647de247cf6a427b9d39e5c2" + }, + "createdAt": { + "$date": "2023-11-30T12:40:29.049Z" + }, + "updatedAt": { + "$date": "2023-11-30T15:32:05.991Z" + }, + "name": "CY Test Tool Context Scope", + "config_type": "basic", + "config_baseUrl": "https:google.com", + "parameters": [{ + "name": "searchparam", + "displayName": "searchparameter", + "description": "", + "scope": "context", + "location": "path", + "type": "string", + "isOptional": false + }], + "isHidden": false, + "openNewTab": false, + "version": 2 + }, + { + "_id": { + "$oid": "647de247cf6a427b9d39e5c3" + }, + "createdAt": { + "$date": "2023-11-30T15:28:04.733Z" + }, + "updatedAt": { + "$date": "2023-11-30T15:32:42.888Z" + }, + "name": "CY Test Tool School and Context Scope", + "config_type": "basic", + "config_baseUrl": "https:google.com", + "parameters": [{ + "name": "schoolParam", + "displayName": "cypress test school", + "description": "", + "scope": "school", + "location": "path", + "type": "string", + "isOptional": false + }, { + "name": "contextparammm", + "displayName": "cypress test context", + "description": "", + "scope": "context", + "location": "path", + "type": "string", + "isOptional": false + }], + "isHidden": false, + "openNewTab": false, + "version": 2 } + ] diff --git a/backup/setup/school-external-tools.json b/backup/setup/school-external-tools.json index b3d4e4b0197..6c39fd86c0a 100644 --- a/backup/setup/school-external-tools.json +++ b/backup/setup/school-external-tools.json @@ -91,5 +91,68 @@ }, "schoolParameters": [], "toolVersion": 1 + }, + { + "_id": { + "$oid": "647de374cf6a427b9d39e5bc" + }, + "createdAt": { + "$date": "2023-11-30T15:23:31.370Z" + }, + "updatedAt": { + "$date": "2023-11-30T15:23:31.370Z" + }, + "tool": { + "$oid": "647de247cf6a427b9d39e5b1" + }, + "school": { + "$oid": "5fa2c5ccb229544f2c69666c" + }, + "schoolParameters": [{ + "name": "search", + "value": "xxx" + }], + "toolVersion": 1 + }, + { + "_id": { + "$oid": "647de374cf6a427b9d39e5bd" + }, + "createdAt": { + "$date": "2023-11-30T15:23:45.423Z" + }, + "updatedAt": { + "$date": "2023-11-30T15:23:45.423Z" + }, + "tool": { + "$oid": "647de247cf6a427b9d39e5c2" + }, + "school": { + "$oid": "5fa2c5ccb229544f2c69666c" + }, + "schoolParameters": [], + "toolVersion": 1 + }, + { + "_id": { + "$oid": "647de374cf6a427b9d39e5be" + }, + "createdAt": { + "$date": "2023-11-30T15:29:00.061Z" + }, + "updatedAt": { + "$date": "2023-11-30T15:29:00.061Z" + }, + "tool": { + "$oid": "647de247cf6a427b9d39e5c3" + }, + "school": { + "$oid": "5fa2c5ccb229544f2c69666c" + }, + "schoolParameters": [{ + "name": "schoolParan", + "value": "test" + }], + "toolVersion": 1 } ]