diff --git a/Methodology Library/Verra/Verra Redd/VM0041/VM0041.policy b/Methodology Library/Verra/Verra Redd/VM0041/VM0041.policy new file mode 100644 index 0000000000..7273135fa2 Binary files /dev/null and b/Methodology Library/Verra/Verra Redd/VM0041/VM0041.policy differ diff --git a/analytics-service/package.json b/analytics-service/package.json index 478fb76c9b..21eae694e7 100644 --- a/analytics-service/package.json +++ b/analytics-service/package.json @@ -12,8 +12,8 @@ }, "author": "Envision Blockchain Solutions ", "dependencies": { - "@guardian/common": "^2.19.1", - "@guardian/interfaces": "^2.19.1", + "@guardian/common": "^2.20.0-prerelease", + "@guardian/interfaces": "^2.20.0-prerelease", "@nestjs/common": "^9.4.1", "@nestjs/core": "^9.4.1", "@nestjs/jwt": "^10.0.3", @@ -79,5 +79,6 @@ "start": "node dist/index.js", "test": "mocha tests/**/*.test.js --reporter mocha-junit-reporter --reporter-options mochaFile=../test_results/ui-service.xml" }, - "version": "2.19.1" + "version": "2.20.0-prerelease", + "stableVersion": "2.19.1" } diff --git a/api-gateway/package.json b/api-gateway/package.json index 87bc2e1ed1..863392e80d 100644 --- a/api-gateway/package.json +++ b/api-gateway/package.json @@ -11,8 +11,8 @@ }, "author": "Envision Blockchain Solutions ", "dependencies": { - "@guardian/common": "^2.19.1", - "@guardian/interfaces": "^2.19.1", + "@guardian/common": "^2.20.0-prerelease", + "@guardian/interfaces": "^2.20.0-prerelease", "@nestjs/common": "^9.4.1", "@nestjs/core": "^9.4.1", "@nestjs/jwt": "^10.0.3", @@ -80,5 +80,6 @@ "start": "node dist/index.js", "test": "mocha tests/**/*.test.js --reporter mocha-junit-reporter --reporter-options mochaFile=../test_results/ui-service.xml" }, - "version": "2.19.1" + "version": "2.20.0-prerelease", + "stableVersion": "2.19.1" } diff --git a/api-gateway/src/api/service/analytics.ts b/api-gateway/src/api/service/analytics.ts index 56d6f8b88a..72fd1ca5ab 100644 --- a/api-gateway/src/api/service/analytics.ts +++ b/api-gateway/src/api/service/analytics.ts @@ -383,7 +383,8 @@ export class AnalyticsApi { eventsLvl, propLvl, childrenLvl, - idLvl + idLvl, + 0 ); } catch (error) { throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); @@ -764,7 +765,8 @@ export class AnalyticsApi { eventsLvl, propLvl, childrenLvl, - idLvl + idLvl, + 0 ); } catch (error) { throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); diff --git a/api-gateway/src/api/service/module.ts b/api-gateway/src/api/service/module.ts index 4341bc7d68..ab49cf7b2c 100644 --- a/api-gateway/src/api/service/module.ts +++ b/api-gateway/src/api/service/module.ts @@ -39,7 +39,7 @@ export class ModulesApi { return res.status(201).json(item); } catch (error) { new Logger().error(error, ['API_GATEWAY']); - throw error + throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); } } diff --git a/api-gateway/src/api/service/policy.ts b/api-gateway/src/api/service/policy.ts index 9f8148f6bc..3dda9f0a97 100644 --- a/api-gateway/src/api/service/policy.ts +++ b/api-gateway/src/api/service/policy.ts @@ -285,7 +285,7 @@ export class PolicyApi { const engineService = new PolicyEngine(); let model: any; try { - model = await engineService.getPolicy({filters: req.params.policyId}) as any; + model = await engineService.getPolicy({ filters: req.params.policyId }) as any; } catch (error) { new Logger().error(error, ['API_GATEWAY']); throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); @@ -1073,6 +1073,9 @@ export class PolicyApi { if (policy.owner !== req.user.did) { throw new HttpException('Invalid owner.', HttpStatus.FORBIDDEN) } + if (policy.status !== PolicyType.DRY_RUN) { + throw new HttpException('Invalid status.', HttpStatus.FORBIDDEN) + } try { return res.send(await engineService.getVirtualUsers(req.params.policyId)); } catch (error) { @@ -1099,6 +1102,9 @@ export class PolicyApi { if (policy.owner !== req.user.did) { throw new HttpException('Invalid owner.', HttpStatus.FORBIDDEN) } + if (policy.status !== PolicyType.DRY_RUN) { + throw new HttpException('Invalid status.', HttpStatus.FORBIDDEN) + } try { return res.status(201).send(await engineService.createVirtualUser(req.params.policyId, req.user.did)); } catch (error) { @@ -1125,6 +1131,9 @@ export class PolicyApi { if (policy.owner !== req.user.did) { throw new HttpException('Invalid owner.', HttpStatus.FORBIDDEN) } + if (policy.status !== PolicyType.DRY_RUN) { + throw new HttpException('Invalid status.', HttpStatus.FORBIDDEN) + } try { return res.send(await engineService.loginVirtualUser(req.params.policyId, req.body.did)); } catch (error) { @@ -1151,6 +1160,9 @@ export class PolicyApi { if (policy.owner !== req.user.did) { throw new HttpException('Invalid owner.', HttpStatus.FORBIDDEN) } + if (policy.status !== PolicyType.DRY_RUN) { + throw new HttpException('Invalid status.', HttpStatus.FORBIDDEN) + } try { return res.json(await engineService.restartDryRun(req.body, req.user, req.params.policyId)); } catch (error) { diff --git a/api-gateway/src/api/service/record.ts b/api-gateway/src/api/service/record.ts new file mode 100644 index 0000000000..cd910ba7f3 --- /dev/null +++ b/api-gateway/src/api/service/record.ts @@ -0,0 +1,642 @@ +import { PolicyType, UserRole } from '@guardian/interfaces'; +import { PolicyEngine } from '@helpers/policy-engine'; +import { IAuthUser, Logger } from '@guardian/common'; +import { Controller, Get, HttpCode, HttpException, HttpStatus, Post, Response, Param, Body } from '@nestjs/common'; +import { ApiBody, ApiForbiddenResponse, ApiInternalServerErrorResponse, ApiOkResponse, ApiOperation, ApiSecurity, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; +import { InternalServerErrorDTO } from '@middlewares/validation/schemas/errors'; +import { ApiImplicitParam } from '@nestjs/swagger/dist/decorators/api-implicit-param.decorator'; +import { Guardians } from '@helpers/guardians'; +import { Auth } from '@auth/auth.decorator'; +import { AuthUser } from '@auth/authorization-helper'; +import { RecordActionDTO, RecordStatusDTO, RunningDetailsDTO, RunningResultDTO } from '@middlewares/validation/schemas/record'; + +/** + * Check policy + * @param policyId + * @param owner + */ +export async function checkPolicy(policyId: string, owner: string): Promise { + let policy: any; + try { + const engineService = new PolicyEngine(); + policy = await engineService.getPolicy({ filters: policyId }); + } catch (error) { + new Logger().error(error, ['API_GATEWAY']); + throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); + } + if (!policy) { + throw new HttpException('Policy does not exist.', HttpStatus.NOT_FOUND) + } + if (policy.owner !== owner) { + throw new HttpException('Invalid owner.', HttpStatus.FORBIDDEN) + } + if (policy.status !== PolicyType.DRY_RUN) { + throw new HttpException('Invalid status.', HttpStatus.FORBIDDEN) + } + return policy; +} + +const ONLY_SR = ' Only users with the Standard Registry role are allowed to make the request.' + +@Controller('record') +@ApiTags('record') +export class RecordApi { + /** + * Get recording or running status + */ + @Get('/:policyId/status') + @Auth( + UserRole.STANDARD_REGISTRY + ) + @ApiSecurity('bearerAuth') + @ApiOperation({ + summary: 'Get recording or running status.', + description: 'Get recording or running status.' + ONLY_SR, + }) + @ApiImplicitParam({ + name: 'policyId', + type: String, + description: 'Policy Id', + required: true, + example: '000000000000000000000001' + }) + @ApiBody({ + description: 'Object that contains options', + required: true, + type: Object + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: RecordStatusDTO + }) + @ApiUnauthorizedResponse({ + description: 'Unauthorized.', + }) + @ApiForbiddenResponse({ + description: 'Forbidden.', + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) + @HttpCode(HttpStatus.OK) + async getRecordStatus( + @AuthUser() user: IAuthUser, + @Param('policyId') policyId: string, + ) { + await checkPolicy(policyId, user.did); + try { + const guardians = new Guardians(); + return await guardians.getRecordStatus(policyId, user.did); + } catch (error) { + new Logger().error(error, ['API_GATEWAY']); + throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + /** + * Start recording + */ + @Post('/:policyId/recording/start') + @Auth( + UserRole.STANDARD_REGISTRY + ) + @ApiSecurity('bearerAuth') + @ApiOperation({ + summary: 'Start recording.', + description: 'Start recording.' + ONLY_SR, + }) + @ApiImplicitParam({ + name: 'policyId', + type: String, + description: 'Policy Id', + required: true, + example: '000000000000000000000001' + }) + @ApiBody({ + description: 'Object that contains options', + required: true, + type: Object + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: Boolean + }) + @ApiUnauthorizedResponse({ + description: 'Unauthorized.', + }) + @ApiForbiddenResponse({ + description: 'Forbidden.', + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) + @HttpCode(HttpStatus.OK) + async startRecord( + @AuthUser() user: IAuthUser, + @Param('policyId') policyId: string, + @Body() options: any + ) { + await checkPolicy(policyId, user.did); + try { + const guardians = new Guardians(); + return await guardians.startRecording(policyId, user.did, options); + } catch (error) { + new Logger().error(error, ['API_GATEWAY']); + throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + /** + * Stop recording + */ + @Post('/:policyId/recording/stop') + @Auth( + UserRole.STANDARD_REGISTRY + ) + @ApiSecurity('bearerAuth') + @ApiOperation({ + summary: 'Stop recording.', + description: 'Stop recording.' + ONLY_SR, + }) + @ApiImplicitParam({ + name: 'policyId', + type: String, + description: 'Policy Id', + required: true, + example: '000000000000000000000001' + }) + @ApiBody({ + description: 'Object that contains options', + required: true, + type: Object + }) + @ApiOkResponse({ + description: 'Successful operation.', + schema: { + type: 'string', + format: 'binary' + }, + }) + @ApiUnauthorizedResponse({ + description: 'Unauthorized.', + }) + @ApiForbiddenResponse({ + description: 'Forbidden.', + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) + @HttpCode(HttpStatus.OK) + async stopRecord( + @AuthUser() user: IAuthUser, + @Param('policyId') policyId: string, + @Body() options: any, + @Response() res: any + ) { + await checkPolicy(policyId, user.did); + try { + const guardians = new Guardians(); + const result = await guardians.stopRecording(policyId, user.did, options); + res.setHeader('Content-disposition', `attachment; filename=${Date.now()}`); + res.setHeader('Content-type', 'application/zip'); + return res.send(result); + } catch (error) { + new Logger().error(error, ['API_GATEWAY']); + throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + /** + * Get recorded actions + */ + @Get('/:policyId/recording/actions') + @Auth( + UserRole.STANDARD_REGISTRY + ) + @ApiSecurity('bearerAuth') + @ApiOperation({ + summary: 'Get recorded actions.', + description: 'Get recorded actions.' + ONLY_SR, + }) + @ApiImplicitParam({ + name: 'policyId', + type: String, + description: 'Policy Id', + required: true, + example: '000000000000000000000001' + }) + @ApiBody({ + description: 'Object that contains options', + required: true, + type: Object + }) + @ApiOkResponse({ + description: 'Successful operation.', + isArray: true, + type: RecordActionDTO + }) + @ApiUnauthorizedResponse({ + description: 'Unauthorized.', + }) + @ApiForbiddenResponse({ + description: 'Forbidden.', + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) + @HttpCode(HttpStatus.OK) + async getRecordActions( + @AuthUser() user: IAuthUser, + @Param('policyId') policyId: string, + ) { + await checkPolicy(policyId, user.did); + try { + const guardians = new Guardians(); + return await guardians.getRecordedActions(policyId, user.did); + } catch (error) { + new Logger().error(error, ['API_GATEWAY']); + throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + /** + * Run record from a zip file + */ + @Post('/:policyId/running/start') + @Auth( + UserRole.STANDARD_REGISTRY + ) + @ApiSecurity('bearerAuth') + @ApiOperation({ + summary: 'Run record from a zip file.', + description: 'Run record from a zip file.' + ONLY_SR, + }) + @ApiImplicitParam({ + name: 'policyId', + type: String, + description: 'Policy Id', + required: true, + example: '000000000000000000000001' + }) + @ApiBody({ + description: 'A zip file containing record to be run.', + required: true, + type: String + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: Boolean + }) + @ApiUnauthorizedResponse({ + description: 'Unauthorized.', + }) + @ApiForbiddenResponse({ + description: 'Forbidden.', + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) + @HttpCode(HttpStatus.OK) + async runRecord( + @AuthUser() user: IAuthUser, + @Param('policyId') policyId: string, + @Body() file: any + ) { + await checkPolicy(policyId, user.did); + try { + const options = { file }; + const guardians = new Guardians(); + return await guardians.runRecord(policyId, user.did, options); + } catch (error) { + new Logger().error(error, ['API_GATEWAY']); + throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + /** + * Stop running + */ + @Post('/:policyId/running/stop') + @Auth( + UserRole.STANDARD_REGISTRY + ) + @ApiSecurity('bearerAuth') + @ApiOperation({ + summary: 'Stop running.', + description: 'Stop running.' + ONLY_SR, + }) + @ApiImplicitParam({ + name: 'policyId', + type: String, + description: 'Policy Id', + required: true, + example: '000000000000000000000001' + }) + @ApiBody({ + description: 'Object that contains options', + required: true, + type: Object + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: Boolean + }) + @ApiUnauthorizedResponse({ + description: 'Unauthorized.', + }) + @ApiForbiddenResponse({ + description: 'Forbidden.', + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) + @HttpCode(HttpStatus.OK) + async stopRunning( + @AuthUser() user: IAuthUser, + @Param('policyId') policyId: string, + @Body() options: any + ) { + await checkPolicy(policyId, user.did); + try { + const guardians = new Guardians(); + return await guardians.stopRunning(policyId, user.did, options); + } catch (error) { + new Logger().error(error, ['API_GATEWAY']); + throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + /** + * Get running results + */ + @Get('/:policyId/running/results') + @Auth( + UserRole.STANDARD_REGISTRY + ) + @ApiSecurity('bearerAuth') + @ApiOperation({ + summary: 'Get running results.', + description: 'Get running results.' + ONLY_SR, + }) + @ApiImplicitParam({ + name: 'policyId', + type: String, + description: 'Policy Id', + required: true, + example: '000000000000000000000001' + }) + @ApiBody({ + description: 'Object that contains options', + required: true, + type: Object + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: RunningResultDTO + }) + @ApiUnauthorizedResponse({ + description: 'Unauthorized.', + }) + @ApiForbiddenResponse({ + description: 'Forbidden.', + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) + @HttpCode(HttpStatus.OK) + async getRecordResults( + @AuthUser() user: IAuthUser, + @Param('policyId') policyId: string, + ) { + await checkPolicy(policyId, user.did); + try { + const guardians = new Guardians(); + return await guardians.getRecordResults(policyId, user.did); + } catch (error) { + new Logger().error(error, ['API_GATEWAY']); + throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + /** + * Get running details + */ + @Get('/:policyId/running/details') + @Auth( + UserRole.STANDARD_REGISTRY + ) + @ApiSecurity('bearerAuth') + @ApiOperation({ + summary: 'Get running details.', + description: 'Get running details.' + ONLY_SR, + }) + @ApiImplicitParam({ + name: 'policyId', + type: String, + description: 'Policy Id', + required: true, + example: '000000000000000000000001' + }) + @ApiBody({ + description: 'Object that contains options', + required: true, + type: Object + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: RunningDetailsDTO + }) + @ApiUnauthorizedResponse({ + description: 'Unauthorized.', + }) + @ApiForbiddenResponse({ + description: 'Forbidden.', + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) + @HttpCode(HttpStatus.OK) + async getRecordDetails( + @AuthUser() user: IAuthUser, + @Param('policyId') policyId: string + ) { + await checkPolicy(policyId, user.did); + try { + const guardians = new Guardians(); + return await guardians.getRecordDetails(policyId, user.did); + } catch (error) { + new Logger().error(error, ['API_GATEWAY']); + throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + /** + * Fast Forward + */ + @Post('/:policyId/running/fast-forward') + @Auth( + UserRole.STANDARD_REGISTRY + ) + @ApiSecurity('bearerAuth') + @ApiOperation({ + summary: 'Fast Forward.', + description: 'Fast Forward.' + ONLY_SR, + }) + @ApiImplicitParam({ + name: 'policyId', + type: String, + description: 'Policy Id', + required: true, + example: '000000000000000000000001' + }) + @ApiBody({ + description: 'Object that contains options', + required: true, + type: Object + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: Boolean + }) + @ApiUnauthorizedResponse({ + description: 'Unauthorized.', + }) + @ApiForbiddenResponse({ + description: 'Forbidden.', + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) + @HttpCode(HttpStatus.OK) + async fastForward( + @AuthUser() user: IAuthUser, + @Param('policyId') policyId: string, + @Body() options: any + ) { + await checkPolicy(policyId, user.did); + try { + const guardians = new Guardians(); + return await guardians.fastForward(policyId, user.did, options); + } catch (error) { + new Logger().error(error, ['API_GATEWAY']); + throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + /** + * Retry Step + */ + @Post('/:policyId/running/retry') + @Auth( + UserRole.STANDARD_REGISTRY + ) + @ApiSecurity('bearerAuth') + @ApiOperation({ + summary: 'Retry step.', + description: 'Retry step.' + ONLY_SR, + }) + @ApiImplicitParam({ + name: 'policyId', + type: String, + description: 'Policy Id', + required: true, + example: '000000000000000000000001' + }) + @ApiBody({ + description: 'Object that contains options', + required: true, + type: Object + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: Boolean + }) + @ApiUnauthorizedResponse({ + description: 'Unauthorized.', + }) + @ApiForbiddenResponse({ + description: 'Forbidden.', + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) + @HttpCode(HttpStatus.OK) + async retryStep( + @AuthUser() user: IAuthUser, + @Param('policyId') policyId: string, + @Body() options: any + ) { + await checkPolicy(policyId, user.did); + try { + const guardians = new Guardians(); + return await guardians.retryStep(policyId, user.did, options); + } catch (error) { + new Logger().error(error, ['API_GATEWAY']); + throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + /** + * Skip Step + */ + @Post('/:policyId/running/skip') + @Auth( + UserRole.STANDARD_REGISTRY + ) + @ApiSecurity('bearerAuth') + @ApiOperation({ + summary: 'Skip step.', + description: 'Skip step.' + ONLY_SR, + }) + @ApiImplicitParam({ + name: 'policyId', + type: String, + description: 'Policy Id', + required: true, + example: '000000000000000000000001' + }) + @ApiBody({ + description: 'Object that contains options', + required: true, + type: Object + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: Boolean + }) + @ApiUnauthorizedResponse({ + description: 'Unauthorized.', + }) + @ApiForbiddenResponse({ + description: 'Forbidden.', + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) + @HttpCode(HttpStatus.OK) + async skipStep( + @AuthUser() user: IAuthUser, + @Param('policyId') policyId: string, + @Body() options: any + ) { + await checkPolicy(policyId, user.did); + try { + const guardians = new Guardians(); + return await guardians.skipStep(policyId, user.did, options); + } catch (error) { + new Logger().error(error, ['API_GATEWAY']); + throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); + } + } +} \ No newline at end of file diff --git a/api-gateway/src/api/service/websockets.ts b/api-gateway/src/api/service/websockets.ts index 3b79ad9a8a..b6ce406ae9 100644 --- a/api-gateway/src/api/service/websockets.ts +++ b/api-gateway/src/api/service/websockets.ts @@ -222,9 +222,20 @@ export class WebSocketsService { } }, 500); + this.channel.subscribe('update-record', async (msg) => { + this.wss.clients.forEach((client: any) => { + if (this.checkUserByDid(client, msg)) { + this.send(client, { + type: 'update-record-event', + data: msg, + }); + } + }); + return new MessageResponse({}); + }); + this.channel.subscribe('update-block', async (msg) => { updateArray.push(msg); - return new MessageResponse({}); }); diff --git a/api-gateway/src/app.module.ts b/api-gateway/src/app.module.ts index be7a25fc25..a4abee5a81 100644 --- a/api-gateway/src/app.module.ts +++ b/api-gateway/src/app.module.ts @@ -36,6 +36,7 @@ import { ApplicationEnvironment } from './environment'; import { AuthGuard } from '@auth/auth-guard'; import { UsersService } from '@helpers/users'; import { RolesGuard } from '@auth/roles-guard'; +import { RecordApi } from '@api/service/record'; const JSON_REQUEST_LIMIT = process.env.JSON_REQUEST_LIMIT || '1mb'; const RAW_REQUEST_LIMIT = process.env.RAW_REQUEST_LIMIT || '1gb'; @@ -95,6 +96,7 @@ const RAW_REQUEST_LIMIT = process.env.RAW_REQUEST_LIMIT || '1gb'; BrandingApi, SuggestionsApi, NotificationsApi, + RecordApi ], providers: [ LoggerService, @@ -129,6 +131,7 @@ export class AppModule { consumer.apply(authorizationHelper).forRoutes(SuggestionsApi); consumer.apply(authorizationHelper).forRoutes(NotificationsApi); consumer.apply(authorizationHelper).forRoutes(TaskApi); + consumer.apply(authorizationHelper).forRoutes(RecordApi); consumer.apply(express.json({ limit: JSON_REQUEST_LIMIT diff --git a/api-gateway/src/helpers/guardians.ts b/api-gateway/src/helpers/guardians.ts index 01a3678f55..a6aea87766 100644 --- a/api-gateway/src/helpers/guardians.ts +++ b/api-gateway/src/helpers/guardians.ts @@ -671,7 +671,7 @@ export class Guardians extends NatsService { * @param task */ public async copySchemaAsync(iri: string, topicId: string, name: string, owner: string, task: NewTask): Promise { - return await this.sendMessage(MessageAPI.COPY_SCHEMA_ASYNC, {iri, topicId, name, task, owner}); + return await this.sendMessage(MessageAPI.COPY_SCHEMA_ASYNC, { iri, topicId, name, task, owner }); } /** @@ -933,6 +933,7 @@ export class Guardians extends NatsService { propLvl: any, childrenLvl: any, idLvl: any, + keyLvl: any, ) { return await this.sendMessage(MessageAPI.COMPARE_DOCUMENTS, { type, @@ -941,7 +942,8 @@ export class Guardians extends NatsService { eventsLvl, propLvl, childrenLvl, - idLvl + idLvl, + keyLvl }); } @@ -2278,4 +2280,122 @@ export class Guardians extends NatsService { ): Promise { return await this.sendMessage(MessageAPI.SEARCH_BLOCKS, { config, blockId, user }); } -} + + /** + * Start recording + * @param policyId + * @param owner + * @param options + * @returns {any} + */ + public async startRecording(policyId: string, owner: string, options: any): Promise { + return await this.sendMessage(MessageAPI.START_RECORDING, { policyId, owner, options }); + } + + /** + * Stop recording + * @param policyId + * @param owner + * @param options + * @returns {any} + */ + public async stopRecording(policyId: string, owner: string, options: any): Promise { + const file = await this.sendMessage(MessageAPI.STOP_RECORDING, { policyId, owner, options }); + return Buffer.from(file, 'base64'); + } + + /** + * Get recorded actions + * @param policyId + * @param owner + * @returns {any} + */ + public async getRecordedActions(policyId: string, owner: string): Promise { + return await this.sendMessage(MessageAPI.GET_RECORDED_ACTIONS, { policyId, owner }); + } + + /** + * Get recording or running status + * @param policyId + * @param owner + * @returns {any} + */ + public async getRecordStatus(policyId: string, owner: string): Promise { + return await this.sendMessage(MessageAPI.GET_RECORD_STATUS, { policyId, owner }); + } + + /** + * Run record + * @param policyId + * @param owner + * @param options + * @returns {any} + */ + public async runRecord(policyId: string, owner: string, options: any): Promise { + return await this.sendMessage(MessageAPI.RUN_RECORD, { policyId, owner, options }); + } + + /** + * Stop running + * @param policyId + * @param owner + * @param options + * @returns {any} + */ + public async stopRunning(policyId: string, owner: string, options: any): Promise { + return await this.sendMessage(MessageAPI.STOP_RUNNING, { policyId, owner, options }); + } + + /** + * Get running results + * @param policyId + * @param owner + * @returns {any} + */ + public async getRecordResults(policyId: string, owner: string): Promise { + return await this.sendMessage(MessageAPI.GET_RECORD_RESULTS, { policyId, owner }); + } + + /** + * Get record details + * @param policyId + * @param owner + * @returns {any} + */ + public async getRecordDetails(policyId: string, owner: string): Promise { + return await this.sendMessage(MessageAPI.GET_RECORD_DETAILS, { policyId, owner }); + } + + /** + * Fast Forward + * @param policyId + * @param owner + * @param options + * @returns {any} + */ + public async fastForward(policyId: string, owner: string, options: any): Promise { + return await this.sendMessage(MessageAPI.FAST_FORWARD, { policyId, owner, options }); + } + + /** + * Retry Step + * @param policyId + * @param owner + * @param options + * @returns {any} + */ + public async retryStep(policyId: string, owner: string, options: any): Promise { + return await this.sendMessage(MessageAPI.RECORD_RETRY_STEP, { policyId, owner, options }); + } + + /** + * Skip Step + * @param policyId + * @param owner + * @param options + * @returns {any} + */ + public async skipStep(policyId: string, owner: string, options: any): Promise { + return await this.sendMessage(MessageAPI.RECORD_SKIP_STEP, { policyId, owner, options }); + } +} \ No newline at end of file diff --git a/api-gateway/src/helpers/policy-engine.ts b/api-gateway/src/helpers/policy-engine.ts index 3a79d7a102..ef1270e565 100644 --- a/api-gateway/src/helpers/policy-engine.ts +++ b/api-gateway/src/helpers/policy-engine.ts @@ -142,7 +142,7 @@ export class PolicyEngine extends NatsService { } public async restartPolicyInstance(user: any, policyId: string) { - return await this.sendMessage(PolicyEngineEvents.RESTART_POLICY_INSTANCE, {user, policyId}); + return await this.sendMessage(PolicyEngineEvents.RESTART_POLICY_INSTANCE, { user, policyId }); } /** @@ -340,10 +340,10 @@ export class PolicyEngine extends NatsService { /** * Create new Virtual User * @param policyId - * @param did + * @param owner */ - public async createVirtualUser(policyId: string, did: string) { - return await this.sendMessage(PolicyEngineEvents.CREATE_VIRTUAL_USER, { policyId, did }); + public async createVirtualUser(policyId: string, owner: string) { + return await this.sendMessage(PolicyEngineEvents.CREATE_VIRTUAL_USER, { policyId, owner }); } /** diff --git a/api-gateway/src/middlewares/validation/schemas/record.ts b/api-gateway/src/middlewares/validation/schemas/record.ts new file mode 100644 index 0000000000..7cfdcdaf95 --- /dev/null +++ b/api-gateway/src/middlewares/validation/schemas/record.ts @@ -0,0 +1,131 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsObject, IsString, IsNumber, IsArray } from 'class-validator'; +import { Type } from 'class-transformer'; + +export class RecordStatusDTO { + @ApiProperty() + @IsString() + @IsNotEmpty() + type: string; + + @ApiProperty() + @IsString() + @IsNotEmpty() + policyId: string; + + @ApiProperty() + @IsString() + @IsNotEmpty() + uuid: string; + + @ApiProperty() + @IsString() + @IsNotEmpty() + status: string; +} + +export class RecordActionDTO { + @ApiProperty() + @IsString() + @IsNotEmpty() + uuid: string; + + @ApiProperty() + @IsString() + @IsNotEmpty() + policyId: string; + + @ApiProperty() + @IsString() + @IsNotEmpty() + method: string; + + @ApiProperty() + @IsString() + action: string; + + @ApiProperty() + @IsString() + time: string; + + @ApiProperty() + @IsString() + user: string; + + @ApiProperty() + @IsString() + target: string; +} + +export class ResultDocumentDTO { + @ApiProperty() + @IsString() + @IsNotEmpty() + type: string; + + @ApiProperty() + @IsString() + @IsNotEmpty() + schema: string; + + @ApiProperty() + @IsString() + @IsNotEmpty() + rate: string; + + @ApiProperty() + @IsObject() + @IsNotEmpty() + documents: any; +} + +export class ResultInfoDTO { + @ApiProperty() + @IsNumber() + @IsNotEmpty() + tokens: number; + + @ApiProperty() + @IsNumber() + @IsNotEmpty() + documents: number; +} + +export class RunningResultDTO { + @ApiProperty() + @IsObject() + @IsNotEmpty() + info: ResultInfoDTO; + + @ApiProperty() + @IsNumber() + @IsNotEmpty() + total: number; + + @ApiProperty({ type: () => ResultDocumentDTO }) + @IsArray() + @Type(() => ResultDocumentDTO) + documents: ResultDocumentDTO[]; +} + +export class RunningDetailsDTO { + @ApiProperty() + @IsObject() + @IsNotEmpty() + left: any; + + @ApiProperty() + @IsObject() + @IsNotEmpty() + right: any; + + @ApiProperty() + @IsNumber() + @IsNotEmpty() + total: number; + + @ApiProperty() + @IsObject() + @IsNotEmpty() + documents: any; +} \ No newline at end of file diff --git a/api-tests/package.json b/api-tests/package.json index 155c01f49b..8ddd18f1d2 100644 --- a/api-tests/package.json +++ b/api-tests/package.json @@ -1,6 +1,6 @@ { "name": "api-tests", - "version": "2.19.1", + "version": "2.20.0-prerelease", "description": "API Tests", "main": "index.js", "scripts": { @@ -25,5 +25,6 @@ "gulp-rename": "^2.0.0", "gulp-sourcemaps": "^3.0.0", "gulp-typescript": "^6.0.0-alpha.1" - } + }, + "stableVersion": "2.19.1" } diff --git a/auth-service/package.json b/auth-service/package.json index aca44cbec5..c7c68a9418 100644 --- a/auth-service/package.json +++ b/auth-service/package.json @@ -9,8 +9,8 @@ "@azure/core-rest-pipeline": "1.12.1" }, "dependencies": { - "@guardian/common": "^2.19.1", - "@guardian/interfaces": "^2.19.1", + "@guardian/common": "^2.20.0-prerelease", + "@guardian/interfaces": "^2.20.0-prerelease", "@meeco/cryppo": "^2.0.2", "@mikro-orm/core": "5.7.12", "@mikro-orm/mongodb": "5.7.12", @@ -72,5 +72,6 @@ "start": "node dist/index.js", "test": "mocha tests/**/*.test.js --reporter mocha-junit-reporter --reporter-options mochaFile=../test_results/ui-service.xml" }, - "version": "2.19.1" + "version": "2.20.0-prerelease", + "stableVersion": "2.19.1" } diff --git a/common/package.json b/common/package.json index ba3a6e4872..cd9e60769e 100644 --- a/common/package.json +++ b/common/package.json @@ -8,7 +8,7 @@ "@azure/identity": "^3.2.2", "@azure/keyvault-secrets": "^4.7.0", "@google-cloud/secret-manager": "^4.2.2", - "@guardian/interfaces": "^2.19.1", + "@guardian/interfaces": "^2.20.0-prerelease", "@hashgraph/sdk": "2.34.1", "@mattrglobal/jsonld-signatures-bbs": "^1.1.2", "@meeco/cryppo": "^2.0.2", @@ -80,5 +80,6 @@ "test:local": "mocha tests/**/*.test.js --exit", "test:stability": "mocha tests/stability.test.js" }, - "version": "2.19.1" + "version": "2.20.0-prerelease", + "stableVersion": "2.19.1" } diff --git a/common/src/database-modules/database-server.ts b/common/src/database-modules/database-server.ts index 0b9e829722..e68dc48746 100644 --- a/common/src/database-modules/database-server.ts +++ b/common/src/database-modules/database-server.ts @@ -25,7 +25,8 @@ import { TagCache, Contract as ContractCollection, ExternalDocument, - SuggestionsConfig + SuggestionsConfig, + Record } from '../entity'; import { Binary } from 'bson'; import { @@ -2396,8 +2397,18 @@ export class DatabaseServer { * @param module */ public static async createModules(module: any): Promise { - const item = new DataBaseHelper(PolicyModule).create(module); - return await new DataBaseHelper(PolicyModule).save(item); + module.name = module.name.replace(/\s+/g, ' ').trim(); + const dbHelper = new DataBaseHelper(PolicyModule); + const item = dbHelper.create(module); + if ( + (await dbHelper.count({ + name: item.name, + owner: item.owner, + })) > 0 + ) { + throw new Error(`Module with name ${item.name} is already exists`); + } + return await dbHelper.save(item); } /** @@ -2455,7 +2466,18 @@ export class DatabaseServer { * @param row */ public static async updateModule(row: PolicyModule): Promise { - return await new DataBaseHelper(PolicyModule).update(row); + row.name = row.name.replace(/\s+/g, ' ').trim(); + const dbHelper = new DataBaseHelper(PolicyModule); + if ( + (await dbHelper.count({ + id: { $ne: row.id }, + name: row.name, + owner: row.owner, + })) > 0 + ) { + throw new Error(`Module with name ${row.name} is already exists`); + } + return await dbHelper.update(row); } /** @@ -2694,4 +2716,23 @@ export class DatabaseServer { await new DataBaseHelper(VpDocumentCollection).update(items); } } + + /** + * Create Record + * @param record + */ + public static async createRecord(record: any): Promise { + const item = new DataBaseHelper(Record).create(record); + return await new DataBaseHelper(Record).save(item); + } + + /** + * Get Record + * @param filters Filters + * @param options Options + * @returns Record + */ + public static async getRecord(filters?: any, options?: any): Promise { + return await new DataBaseHelper(Record).find(filters, options); + } } diff --git a/common/src/entity/index.ts b/common/src/entity/index.ts index b4f64f070a..b0c58ca0d3 100644 --- a/common/src/entity/index.ts +++ b/common/src/entity/index.ts @@ -32,3 +32,4 @@ export * from './topic'; export * from './vc-document'; export * from './vp-document'; export * from './wiper-request'; +export * from './record'; \ No newline at end of file diff --git a/common/src/entity/record.ts b/common/src/entity/record.ts new file mode 100644 index 0000000000..546d344971 --- /dev/null +++ b/common/src/entity/record.ts @@ -0,0 +1,136 @@ +import { BaseEntity } from '../models'; +import { GenerateUUIDv4 } from '@guardian/interfaces'; +import { AfterDelete, BeforeCreate, BeforeUpdate, Entity, OnLoad, Property } from '@mikro-orm/core'; +import { ObjectId } from '@mikro-orm/mongodb'; +import { DataBaseHelper } from '../helpers'; + +/** + * Record collection + */ +@Entity() +export class Record extends BaseEntity { + /** + * UUID + */ + @Property({ + nullable: true, + index: true + }) + uuid?: string; + + /** + * Policy + */ + @Property({ + nullable: true, + index: true + }) + policyId?: string; + + /** + * Method + */ + @Property({ nullable: true }) + method?: string; + + /** + * Action + */ + @Property({ nullable: true }) + action?: string; + + /** + * Time + */ + @Property({ nullable: true, type: 'unknown' }) + time?: Date; + + /** + * User + */ + @Property({ nullable: true }) + user?: string; + + /** + * Target + */ + @Property({ nullable: true }) + target?: string; + + /** + * Document + */ + @Property({ persist: false, type: 'unknown' }) + document?: any; + + /** + * Document file id + */ + @Property({ nullable: true }) + documentFileId?: ObjectId; + + /** + * Create document + */ + @BeforeCreate() + async createDocument() { + await new Promise((resolve, reject) => { + try { + if (this.document) { + const uuid = GenerateUUIDv4(); + const fileStream = DataBaseHelper.gridFS.openUploadStream(uuid); + this.documentFileId = fileStream.id; + fileStream.write(JSON.stringify(this.document)); + fileStream.end(() => resolve()); + } else { + resolve(); + } + } catch (error) { + reject(error) + } + }); + } + + /** + * Update document + */ + @BeforeUpdate() + async updateDocument() { + if (this.document) { + if (this.documentFileId) { + DataBaseHelper.gridFS + .delete(this.documentFileId) + .catch(console.error); + } + await this.createDocument(); + } + } + + /** + * Load document + */ + @OnLoad() + async loadDocument() { + if (this.documentFileId && !this.document) { + const fileStream = DataBaseHelper.gridFS.openDownloadStream(this.documentFileId); + const bufferArray = []; + for await (const data of fileStream) { + bufferArray.push(data); + } + const buffer = Buffer.concat(bufferArray); + this.document = JSON.parse(buffer.toString()); + } + } + + /** + * Delete context + */ + @AfterDelete() + deleteDocument() { + if (this.documentFileId) { + DataBaseHelper.gridFS + .delete(this.documentFileId) + .catch(console.error); + } + } +} \ No newline at end of file diff --git a/common/src/hedera-modules/vcjs/vcjs.ts b/common/src/hedera-modules/vcjs/vcjs.ts index 2bdf8d27b8..5158f74901 100644 --- a/common/src/hedera-modules/vcjs/vcjs.ts +++ b/common/src/hedera-modules/vcjs/vcjs.ts @@ -34,6 +34,51 @@ export interface ISuite { suite: Ed25519Signature2018; } +/** + * Suite options + */ +export interface ISuiteOptions { + /** + * Issuer + */ + did: string; + /** + * Private key + */ + key: string | PrivateKey, + /** + * Signature type + */ + signatureType?: SignatureType; +} + +/** + * Document options + */ +export interface IDocumentOptions { + /** + * Group + */ + group?: { + /** + * Group ID + */ + groupId: string; + /** + * Group type + */ + type: string; + /** + * Group context + */ + context: any; + }; + /** + * UUID + */ + uuid?: string; +} + /** * Connecting VCJS library */ @@ -160,7 +205,7 @@ export class VCJS { /** * Generate new UUIDv4 */ - public generateUUID(): string { + protected generateUUID(): string { return `urn:uuid:${GenerateUUIDv4()}`; } @@ -337,7 +382,10 @@ export class VCJS { * @param {PrivateKey | string} key - Private Key * @param {any} subject - Credential Object * @param {any} [group] - Issuer - * @returns {HcsVcDocument} - VC Document + * + * @returns {VcDocument} - VC Document + * + * @deprecated */ public async createVC( did: string, @@ -346,15 +394,82 @@ export class VCJS { group?: any, signatureType: SignatureType = SignatureType.Ed25519Signature2018, ): Promise { - const document = - signatureType === SignatureType.Ed25519Signature2018 - ? DidRootKey.createByPrivateKey(did, key) - : await BBSDidRootKey.createByPrivateKey(did, key); - const id = this.generateUUID(); - const suite = await this.createSuite(document); - const vcSubject = VcSubject.create(subject); + return await this.createVcDocument(subject, { did, key, signatureType }, { group }); + } - const vc = new VcDocument(signatureType === SignatureType.BbsBlsSignature2020); + /** + * Create VC Document + * + * @param {string} did - DID + * @param {PrivateKey | string} key - Private Key + * @param {VcDocument} vc - json + * + * @returns {VcDocument} - VC Document + * + * @deprecated + */ + public async issueVC( + did: string, + key: string | PrivateKey, + vc: VcDocument + ): Promise { + return await this.issueVcDocument(vc, { did, key }); + } + + /** + * Create VP Document + * + * @param {string} did - DID + * @param {PrivateKey | string} key - Private Key + * @param {VcDocument[]} vcs - VC Documents + * @param {string} [uuid] - new uuid + * + * @returns {VpDocument} - VP Document + * + * @deprecated + */ + public async createVP( + did: string, + key: string | PrivateKey, + vcs: VcDocument[], + uuid?: string + ): Promise { + return await this.createVpDocument(vcs, { did, key }, { uuid }); + } + + /** + * Create VC Document + * + * @param {ICredentialSubject} subject - Credential Object + * @param {ISuiteOptions} suiteOptions - Suite Options (Issuer, Private Key, Signature Type) + * @param {IDocumentOptions} [documentOptions] - Document Options (UUID, Group) + * + * @returns {VcDocument} - VC Document + */ + public async createVcDocument( + subject: ICredentialSubject, + suiteOptions: ISuiteOptions, + documentOptions?: IDocumentOptions + ): Promise { + let id: string; + if (documentOptions && documentOptions.uuid) { + id = documentOptions.uuid; + } else { + id = this.generateUUID(); + } + + const hasBBSSignature: boolean = suiteOptions.signatureType === SignatureType.BbsBlsSignature2020; + let suite: Ed25519Signature2018 | BbsBlsSignature2020; + if (hasBBSSignature) { + const keyDocument = await BBSDidRootKey.createByPrivateKey(suiteOptions.did, suiteOptions.key); + suite = await this.createSuite(keyDocument); + } else { + const keyDocument = DidRootKey.createByPrivateKey(suiteOptions.did, suiteOptions.key); + suite = await this.createSuite(keyDocument); + } + + const vcSubject = VcSubject.create(subject); + const vc = new VcDocument(hasBBSSignature); vc.setId(id); vc.setIssuanceDate(TimestampUtils.now()); vc.addCredentialSubject(vcSubject); @@ -369,12 +484,12 @@ export class VCJS { for (const element of this.schemaContext) { vc.addContext(element); } - if (group) { - vc.setIssuer(new Issuer(did, group.groupId)); - vc.addType(group.type); - vc.addContext(group.context); + if (documentOptions && documentOptions.group) { + vc.setIssuer(new Issuer(suiteOptions.did, documentOptions.group.groupId)); + vc.addType(documentOptions.group.type); + vc.addContext(documentOptions.group.context); } else { - vc.setIssuer(new Issuer(did)); + vc.setIssuer(new Issuer(suiteOptions.did)); } return await this.issue(vc, suite, this.loader); @@ -383,49 +498,68 @@ export class VCJS { /** * Create VC Document * - * @param {string} did - DID - * @param {PrivateKey | string} key - Private Key - * @param {any} vc - json + * @param {VcDocument} vc - VC Document + * @param {ISuiteOptions} suiteOptions - Suite Options (Issuer, Private Key) + * @param {IDocumentOptions} [documentOptions] - Document Options (UUID, Group) + * + * @returns {VcDocument} - VC Document */ - public async issueVC( - did: string, - key: string | PrivateKey, - vc: VcDocument + public async issueVcDocument( + vc: VcDocument, + suiteOptions: ISuiteOptions, + documentOptions?: IDocumentOptions ): Promise { - const document = - vc.getContext().includes(VcDocument.BBS_SIGNATURE_CONTEXT) - ? await BBSDidRootKey.createByPrivateKey(did, key) - : DidRootKey.createByPrivateKey(did, key); - const id = this.generateUUID(); - const suite = await this.createSuite(document); + let id: string; + if (documentOptions && documentOptions.uuid) { + id = documentOptions.uuid; + } else { + id = this.generateUUID(); + } + + const hasBBSSignature: boolean = vc.getContext().includes(VcDocument.BBS_SIGNATURE_CONTEXT); + let suite: Ed25519Signature2018 | BbsBlsSignature2020; + if (hasBBSSignature) { + const keyDocument = await BBSDidRootKey.createByPrivateKey(suiteOptions.did, suiteOptions.key); + suite = await this.createSuite(keyDocument); + } else { + const keyDocument = DidRootKey.createByPrivateKey(suiteOptions.did, suiteOptions.key); + suite = await this.createSuite(keyDocument); + } + vc.setId(id); vc.setIssuanceDate(TimestampUtils.now()); vc.setProof(null); vc = await this.issue(vc, suite, this.loader); return vc; + } /** * Create VP Document * - * @param {string} did - DID - * @param {PrivateKey | string} key - Private Key - * @param {HcsVcDocument[]} vcs - VC Documents - * @param {string} [uuid] - new uuid + * @param {VcDocument[]} vcs - VC Documents + * @param {ISuiteOptions} suiteOptions - Suite Options (Issuer, Private Key) + * @param {IDocumentOptions} [documentOptions] - Document Options (UUID, Group) * - * @returns {HcsVpDocument} - VP Document + * @returns {VpDocument} - VP Document */ - public async createVP( - did: string, - key: string | PrivateKey, + public async createVpDocument( vcs: VcDocument[], - uuid?: string, + suiteOptions: ISuiteOptions, + documentOptions?: IDocumentOptions ): Promise { - uuid = uuid || this.generateUUID(); - const document = DidRootKey.createByPrivateKey(did, key); + let id: string; + if (documentOptions && documentOptions.uuid) { + id = documentOptions.uuid; + } else { + id = this.generateUUID(); + } + + const document = DidRootKey.createByPrivateKey(suiteOptions.did, suiteOptions.key); const suite = await this.createSuite(document); + let vp = new VpDocument(); - vp.setId(uuid); + vp.setId(id); vp.addVerifiableCredentials(vcs); vp = await this.issuePresentation(vp, suite as Ed25519Signature2018, this.loader); return vp; diff --git a/common/src/helpers/vc-helper.ts b/common/src/helpers/vc-helper.ts index cae0a6347e..37a9cb4586 100644 --- a/common/src/helpers/vc-helper.ts +++ b/common/src/helpers/vc-helper.ts @@ -26,6 +26,7 @@ import { import { Singleton } from '../decorators/singleton'; import { DataBaseHelper } from './db-helper'; import { Schema as SchemaCollection } from '../entity'; +import { IDocumentOptions, ISuiteOptions } from '../hedera-modules/vcjs/vcjs'; /** * Configured VCHelper @@ -187,29 +188,27 @@ export class VcHelper extends VCJS { * @param {PrivateKey | string} key - Private Key * @param {any} subject - Credential Object * @param {any} [group] - Issuer - * @returns {HcsVcDocument} - VC Document + * + * @returns {VcDocument} - VC Document + * + * @deprecated */ public override async createVC( did: string, key: string | PrivateKey, subject: ICredentialSubject, - group?: any + group?: any, ): Promise { - const vcSchema = await VcHelper.getSchemaByContext( - subject['@context'], - subject.type - ); - switch (vcSchema?.entity) { - case SchemaEntity.EVC: - return await super.createVC( - did, - key, - this.setNestedNodeIds(JSON.parse(JSON.stringify(subject))), - group, - SignatureType.BbsBlsSignature2020 - ); - default: - return await super.createVC(did, key, subject, group); + const vcSchema = await VcHelper.getSchemaByContext(subject['@context'], subject.type); + const entity: SchemaEntity = vcSchema?.entity; + if (entity === SchemaEntity.EVC) { + return await super.createVC(did, key, + this.setNestedNodeIds(JSON.parse(JSON.stringify(subject))), + group, + SignatureType.BbsBlsSignature2020 + ); + } else { + return await super.createVC(did, key, subject, group); } } @@ -218,10 +217,12 @@ export class VcHelper extends VCJS { * * @param {string} did - DID * @param {PrivateKey | string} key - Private Key - * @param {HcsVcDocument[]} vcs - VC Documents + * @param {VcDocument[]} vcs - VC Documents * @param {string} [uuid] - new uuid * - * @returns {HcsVpDocument} - VP Document + * @returns {VpDocument} - VP Document + * + * @deprecated */ public override async createVP( did: string, @@ -239,4 +240,55 @@ export class VcHelper extends VCJS { } return await super.createVP(did, key, vcs, uuid); } + + /** + * Create VC Document + * + * @param {ICredentialSubject} subject - Credential Object + * @param {ISuiteOptions} suiteOptions - Suite Options (Issuer, Private Key, Signature Type) + * @param {IDocumentOptions} [documentOptions] - Document Options (UUID, Group) + * + * @returns {VcDocument} - VC Document + */ + public override async createVcDocument( + subject: ICredentialSubject, + suiteOptions: ISuiteOptions, + documentOptions?: IDocumentOptions + ): Promise { + const vcSchema = await VcHelper.getSchemaByContext(subject['@context'], subject.type); + const entity: SchemaEntity = vcSchema?.entity; + if (entity === SchemaEntity.EVC) { + suiteOptions.signatureType = SignatureType.BbsBlsSignature2020; + subject = this.setNestedNodeIds(JSON.parse(JSON.stringify(subject))); + return await super.createVcDocument(subject, suiteOptions, documentOptions); + } else { + suiteOptions.signatureType = SignatureType.Ed25519Signature2018; + return await super.createVcDocument(subject, suiteOptions, documentOptions); + } + } + + /** + * Create VP Document + * + * @param {VcDocument[]} vcs - VC Documents + * @param {ISuiteOptions} suiteOptions - Suite Options (Issuer, Private Key) + * @param {IDocumentOptions} [documentOptions] - Document Options (UUID, Group) + * + * @returns {VpDocument} - VP Document + */ + public override async createVpDocument( + vcs: VcDocument[], + suiteOptions: ISuiteOptions, + documentOptions?: IDocumentOptions + ): Promise { + for (let i = 0; i < vcs.length; i++) { + const item = vcs[i]; + if (item.getProof().type !== SignatureType.BbsBlsSignature2020) { + continue; + } + const revealVc = await this.createRevealVC(item); + vcs[i] = await this.vcDeriveProof(item, revealVc); + } + return await super.createVpDocument(vcs, suiteOptions, documentOptions); + } } diff --git a/common/src/import-export/index.ts b/common/src/import-export/index.ts index fc14880e13..ad1ce54e6a 100644 --- a/common/src/import-export/index.ts +++ b/common/src/import-export/index.ts @@ -2,4 +2,5 @@ export * from './module'; export * from './policy'; export * from './schema'; export * from './tool'; -export * from './theme'; \ No newline at end of file +export * from './theme'; +export * from './record'; \ No newline at end of file diff --git a/common/src/import-export/record.ts b/common/src/import-export/record.ts new file mode 100644 index 0000000000..6954dc8d91 --- /dev/null +++ b/common/src/import-export/record.ts @@ -0,0 +1,345 @@ +import JSZip from 'jszip'; +import { Record } from '../entity'; +import { DatabaseServer } from '../database-modules'; + +/** + * Record result + */ +export interface IRecordResult { + /** + * Document ID + */ + id: string; + /** + * Document type + */ + type: 'vc' | 'vp' | 'schema'; + /** + * Document body (JSON) + */ + document: any; +} + +/** + * Record components + */ +export interface IRecordComponents { + /** + * Recorded items + */ + records: Record[]; + /** + * Result (Documents) + */ + results: IRecordResult[]; + /** + * Current time + */ + time: number; +} + +/** + * Record result + */ +export class RecordResult implements IRecordResult { + /** + * Document ID + */ + public id: string; + /** + * Document type + */ + public type: 'vc' | 'vp' | 'schema'; + /** + * Document body (JSON) + */ + public document: any; + + constructor( + type: 'vc' | 'vp' | 'schema', + id: string, + document: any + ) { + this.type = type; + this.id = id; + this.document = document; + } + + /** + * Get document name + */ + public get name(): string { + return btoa(`${this.type}|${this.id}`); + } + + /** + * Get file + */ + public get file(): string { + return JSON.stringify(this.document); + } + + /** + * Create record item by json + */ + public static from(name: string, json: string): RecordResult { + const [type, id] = atob(name).split('|') as any[]; + const document = JSON.parse(json); + return new RecordResult(type, id, document); + } + + /** + * Create record item by object + */ + public static fromObject(item: IRecordResult): RecordResult { + return new RecordResult(item.type, item.id, item.document); + } + + /** + * To object + */ + public toObject(): IRecordResult { + return { + id: this.id, + type: this.type, + document: this.document + }; + } +} + +/** + * Record import export + */ +export class RecordImportExport { + /** + * Record filename + */ + public static readonly recordFileName = 'actions.csv'; + + /** + * Get full time + * @param time + * @param base + * + * @returns time + * @private + */ + private static addTime(time: string | number | Date, base: string | number | Date): number { + return (Number(time) + Number(base)); + } + + /** + * Get diff time + * @param time + * @param base + * + * @returns time + * @private + */ + private static diffTime(time: string | number | Date, base: string | number | Date): string { + if (time && base) { + return String(Number(time) - Number(base)); + } else { + return '0'; + } + } + + /** + * Load record results + * @param uuid record + * + * @returns results + * @public + * @static + */ + public static async loadRecordResults( + policyId: string, + startTime: any, + endTime: any + ): Promise { + const results: IRecordResult[] = []; + const db = new DatabaseServer(policyId); + const vcs = await db.getVcDocuments({ + updateDate: { + $gte: new Date(startTime), + $lt: new Date(endTime) + } + }); + for (const vc of vcs) { + results.push({ + id: vc.document.id, + type: 'vc', + document: vc.document + }); + } + const vps = await db.getVpDocuments({ + updateDate: { + $gte: new Date(startTime), + $lt: new Date(endTime) + } + }); + for (const vp of vps) { + results.push({ + id: vp.document.id, + type: 'vp', + document: vp.document + }); + } + const policy = await DatabaseServer.getPolicyById(policyId); + if (policy) { + const schemas = await DatabaseServer.getSchemas({ topicId: policy.topicId }); + for (const schema of schemas) { + results.push({ + id: schema.contextURL || schema.iri, + type: 'schema', + document: schema.document + }); + } + } + return results; + } + + /** + * Load record components + * @param uuid record + * + * @returns components + * @public + * @static + */ + public static async loadRecordComponents(uuid: string): Promise { + const records = await DatabaseServer.getRecord({ uuid }, { orderBy: { time: 'ASC' } }); + const first = records[0]; + const last = records[records.length - 1]; + const time: any = first ? first.time : null; + if (first && last) { + const results = await RecordImportExport.loadRecordResults(first.policyId, first.time, last.time); + return { records, time, results }; + } else { + return { records, time, results: [] }; + } + } + + /** + * Generate Zip File + * @param record record to pack + * + * @returns Zip file + * @public + * @static + */ + public static async generate(uuid: string): Promise { + const components = await RecordImportExport.loadRecordComponents(uuid); + const file = await RecordImportExport.generateZipFile(components); + return file; + } + + /** + * Generate Zip File + * @param components record components + * + * @returns Zip file + * @public + * @static + */ + public static async generateZipFile(components: IRecordComponents): Promise { + const zip = new JSZip(); + zip.folder('documents'); + zip.folder('results'); + + let documentId = 0; + let json = ''; + for (const item of components.records) { + const row = [ + item.method, + RecordImportExport.diffTime(item.time, components.time) + ]; + if (item.method === 'START') { + row.push(''); + row.push(item.user); + } + if (item.method === 'ACTION' || item.method === 'GENERATE') { + row.push(item.action); + row.push(item.user || ''); + row.push(item.target || ''); + if (item.document) { + row.push(String(documentId)); + zip.file(`documents/${documentId}`, JSON.stringify(item.document)); + documentId++; + } else { + row.push(''); + } + } + json += row.join(',') + '\r\n'; + } + + for (const result of components.results) { + const item = RecordResult.fromObject(result) + zip.file(`results/${item.name}`, item.file); + } + + zip.file(RecordImportExport.recordFileName, json); + return zip; + } + + /** + * Parse zip record file + * @param zipFile Zip file + * @returns Parsed record + * @public + * @static + */ + public static async parseZipFile(zipFile: any): Promise { + const zip = new JSZip(); + const content = await zip.loadAsync(zipFile); + if (!content.files[RecordImportExport.recordFileName] || content.files[RecordImportExport.recordFileName].dir) { + throw new Error('Zip file is not a record'); + } + const recordString = await content.files[RecordImportExport.recordFileName].async('string'); + const documents = new Map(); + const documentFiles = Object.entries(content.files) + .filter(file => !file[1].dir) + .filter(file => /^documents\/.+/.test(file[0])); + for (const file of documentFiles) { + const documentId = file[0].split('/')[1]; + const json = await file[1].async('string'); + const document = JSON.parse(json); + documents.set(documentId, document); + } + + const results: IRecordResult[] = []; + const resultFiles = Object.entries(content.files) + .filter(file => !file[1].dir) + .filter(file => /^results\/.+/.test(file[0])); + for (const file of resultFiles) { + const name = file[0].split('/')[1]; + const json = await file[1].async('string'); + results.push(RecordResult.from(name, json).toObject()); + } + + const records: any[] = []; + const now = Date.now(); + const lines = recordString.split('\r\n'); + for (const line of lines) { + if (line && !line.startsWith('--')) { + const [method, time, action, user, target, documentId] = line.split(','); + if (method) { + records.push({ + method, + action, + user, + target, + time: RecordImportExport.addTime(now, time), + document: documents.get(documentId) + }); + } + } + } + + return { + records, + results, + time: now + }; + } +} \ No newline at end of file diff --git a/configs/.env..guardian.system b/configs/.env..guardian.system index b8e2c44bc9..a1a9177657 100644 --- a/configs/.env..guardian.system +++ b/configs/.env..guardian.system @@ -130,3 +130,4 @@ MEECO_PRESENTATION_DEFINITION_ID="832e996c-**********-9d170fa381a8" ACCESS_TOKEN_UPDATE_INTERVAL=60000 REFRESH_TOKEN_UPDATE_INTERVAL=31536000000 + diff --git a/docs/.gitbook/assets/0 (10).png b/docs/.gitbook/assets/0 (10).png new file mode 100644 index 0000000000..c74e88dd5d Binary files /dev/null and b/docs/.gitbook/assets/0 (10).png differ diff --git a/docs/.gitbook/assets/0 (11).png b/docs/.gitbook/assets/0 (11).png new file mode 100644 index 0000000000..c74e88dd5d Binary files /dev/null and b/docs/.gitbook/assets/0 (11).png differ diff --git a/docs/.gitbook/assets/1 (11).png b/docs/.gitbook/assets/1 (11).png index c0351557f0..b10edffb7f 100644 Binary files a/docs/.gitbook/assets/1 (11).png and b/docs/.gitbook/assets/1 (11).png differ diff --git a/docs/.gitbook/assets/1 (13).png b/docs/.gitbook/assets/1 (13).png new file mode 100644 index 0000000000..b10edffb7f Binary files /dev/null and b/docs/.gitbook/assets/1 (13).png differ diff --git a/docs/.gitbook/assets/10 (11).png b/docs/.gitbook/assets/10 (11).png new file mode 100644 index 0000000000..6d025951ea Binary files /dev/null and b/docs/.gitbook/assets/10 (11).png differ diff --git a/docs/.gitbook/assets/10 (12).png b/docs/.gitbook/assets/10 (12).png new file mode 100644 index 0000000000..6d025951ea Binary files /dev/null and b/docs/.gitbook/assets/10 (12).png differ diff --git a/docs/.gitbook/assets/11 (11).png b/docs/.gitbook/assets/11 (11).png new file mode 100644 index 0000000000..79065e4f32 Binary files /dev/null and b/docs/.gitbook/assets/11 (11).png differ diff --git a/docs/.gitbook/assets/11 (9).png b/docs/.gitbook/assets/11 (9).png index fd41123dd7..79065e4f32 100644 Binary files a/docs/.gitbook/assets/11 (9).png and b/docs/.gitbook/assets/11 (9).png differ diff --git a/docs/.gitbook/assets/12 (10).png b/docs/.gitbook/assets/12 (10).png new file mode 100644 index 0000000000..0a4e239b7c Binary files /dev/null and b/docs/.gitbook/assets/12 (10).png differ diff --git a/docs/.gitbook/assets/12 (11).png b/docs/.gitbook/assets/12 (11).png new file mode 100644 index 0000000000..0a4e239b7c Binary files /dev/null and b/docs/.gitbook/assets/12 (11).png differ diff --git a/docs/.gitbook/assets/13 (10).png b/docs/.gitbook/assets/13 (10).png new file mode 100644 index 0000000000..92b9aa21b8 Binary files /dev/null and b/docs/.gitbook/assets/13 (10).png differ diff --git a/docs/.gitbook/assets/13 (11).png b/docs/.gitbook/assets/13 (11).png new file mode 100644 index 0000000000..92b9aa21b8 Binary files /dev/null and b/docs/.gitbook/assets/13 (11).png differ diff --git a/docs/.gitbook/assets/14 (8).png b/docs/.gitbook/assets/14 (8).png index 4010e2ed54..bf2e86face 100644 Binary files a/docs/.gitbook/assets/14 (8).png and b/docs/.gitbook/assets/14 (8).png differ diff --git a/docs/.gitbook/assets/14 (9).png b/docs/.gitbook/assets/14 (9).png index a0af8ad45a..bf2e86face 100644 Binary files a/docs/.gitbook/assets/14 (9).png and b/docs/.gitbook/assets/14 (9).png differ diff --git a/docs/.gitbook/assets/15 (10).png b/docs/.gitbook/assets/15 (10).png new file mode 100644 index 0000000000..3d4b428e76 Binary files /dev/null and b/docs/.gitbook/assets/15 (10).png differ diff --git a/docs/.gitbook/assets/15 (11).png b/docs/.gitbook/assets/15 (11).png new file mode 100644 index 0000000000..3d4b428e76 Binary files /dev/null and b/docs/.gitbook/assets/15 (11).png differ diff --git a/docs/.gitbook/assets/16 (10).png b/docs/.gitbook/assets/16 (10).png new file mode 100644 index 0000000000..484e481b6d Binary files /dev/null and b/docs/.gitbook/assets/16 (10).png differ diff --git a/docs/.gitbook/assets/16 (8).png b/docs/.gitbook/assets/16 (8).png index acc5d45fbc..484e481b6d 100644 Binary files a/docs/.gitbook/assets/16 (8).png and b/docs/.gitbook/assets/16 (8).png differ diff --git a/docs/.gitbook/assets/17 (10).png b/docs/.gitbook/assets/17 (10).png new file mode 100644 index 0000000000..1298efee1d Binary files /dev/null and b/docs/.gitbook/assets/17 (10).png differ diff --git a/docs/.gitbook/assets/17 (11).png b/docs/.gitbook/assets/17 (11).png new file mode 100644 index 0000000000..1298efee1d Binary files /dev/null and b/docs/.gitbook/assets/17 (11).png differ diff --git a/docs/.gitbook/assets/18 (7).png b/docs/.gitbook/assets/18 (7).png index 73cf43746c..ae45392e5a 100644 Binary files a/docs/.gitbook/assets/18 (7).png and b/docs/.gitbook/assets/18 (7).png differ diff --git a/docs/.gitbook/assets/18 (8).png b/docs/.gitbook/assets/18 (8).png index 73cf43746c..ae45392e5a 100644 Binary files a/docs/.gitbook/assets/18 (8).png and b/docs/.gitbook/assets/18 (8).png differ diff --git a/docs/.gitbook/assets/2 (14).png b/docs/.gitbook/assets/2 (14).png new file mode 100644 index 0000000000..6827a25d6d Binary files /dev/null and b/docs/.gitbook/assets/2 (14).png differ diff --git a/docs/.gitbook/assets/2 (15).png b/docs/.gitbook/assets/2 (15).png new file mode 100644 index 0000000000..6827a25d6d Binary files /dev/null and b/docs/.gitbook/assets/2 (15).png differ diff --git a/docs/.gitbook/assets/3 (10).png b/docs/.gitbook/assets/3 (10).png index c6a24fd37c..1a11f8a4ae 100644 Binary files a/docs/.gitbook/assets/3 (10).png and b/docs/.gitbook/assets/3 (10).png differ diff --git a/docs/.gitbook/assets/3 (12).png b/docs/.gitbook/assets/3 (12).png new file mode 100644 index 0000000000..1a11f8a4ae Binary files /dev/null and b/docs/.gitbook/assets/3 (12).png differ diff --git a/docs/.gitbook/assets/4 (10).png b/docs/.gitbook/assets/4 (10).png index f58f70d2d4..e4fbc61f76 100644 Binary files a/docs/.gitbook/assets/4 (10).png and b/docs/.gitbook/assets/4 (10).png differ diff --git a/docs/.gitbook/assets/4 (9).png b/docs/.gitbook/assets/4 (9).png index 02f0b81bab..e4fbc61f76 100644 Binary files a/docs/.gitbook/assets/4 (9).png and b/docs/.gitbook/assets/4 (9).png differ diff --git a/docs/.gitbook/assets/5 (12).png b/docs/.gitbook/assets/5 (12).png new file mode 100644 index 0000000000..dcae2c7da6 Binary files /dev/null and b/docs/.gitbook/assets/5 (12).png differ diff --git a/docs/.gitbook/assets/5 (13).png b/docs/.gitbook/assets/5 (13).png new file mode 100644 index 0000000000..dcae2c7da6 Binary files /dev/null and b/docs/.gitbook/assets/5 (13).png differ diff --git a/docs/.gitbook/assets/6 (11).png b/docs/.gitbook/assets/6 (11).png new file mode 100644 index 0000000000..95288d46d4 Binary files /dev/null and b/docs/.gitbook/assets/6 (11).png differ diff --git a/docs/.gitbook/assets/6 (12).png b/docs/.gitbook/assets/6 (12).png new file mode 100644 index 0000000000..95288d46d4 Binary files /dev/null and b/docs/.gitbook/assets/6 (12).png differ diff --git a/docs/.gitbook/assets/7 (11).png b/docs/.gitbook/assets/7 (11).png new file mode 100644 index 0000000000..4b8268b611 Binary files /dev/null and b/docs/.gitbook/assets/7 (11).png differ diff --git a/docs/.gitbook/assets/7 (12).png b/docs/.gitbook/assets/7 (12).png new file mode 100644 index 0000000000..4b8268b611 Binary files /dev/null and b/docs/.gitbook/assets/7 (12).png differ diff --git a/docs/.gitbook/assets/8 (12).png b/docs/.gitbook/assets/8 (12).png new file mode 100644 index 0000000000..7d195ba077 Binary files /dev/null and b/docs/.gitbook/assets/8 (12).png differ diff --git a/docs/.gitbook/assets/8 (13).png b/docs/.gitbook/assets/8 (13).png new file mode 100644 index 0000000000..7d195ba077 Binary files /dev/null and b/docs/.gitbook/assets/8 (13).png differ diff --git a/docs/.gitbook/assets/9 (10).png b/docs/.gitbook/assets/9 (10).png new file mode 100644 index 0000000000..7f94b5691d Binary files /dev/null and b/docs/.gitbook/assets/9 (10).png differ diff --git a/docs/.gitbook/assets/9 (11).png b/docs/.gitbook/assets/9 (11).png new file mode 100644 index 0000000000..7f94b5691d Binary files /dev/null and b/docs/.gitbook/assets/9 (11).png differ diff --git a/docs/.gitbook/assets/image (1) (15).png b/docs/.gitbook/assets/image (1) (15).png new file mode 100644 index 0000000000..d5fc36057b Binary files /dev/null and b/docs/.gitbook/assets/image (1) (15).png differ diff --git a/docs/.gitbook/assets/image (1).png b/docs/.gitbook/assets/image (1).png index d5fc36057b..8bbc766be5 100644 Binary files a/docs/.gitbook/assets/image (1).png and b/docs/.gitbook/assets/image (1).png differ diff --git a/docs/.gitbook/assets/image (2) (13).png b/docs/.gitbook/assets/image (2) (13).png new file mode 100644 index 0000000000..a0663a4057 Binary files /dev/null and b/docs/.gitbook/assets/image (2) (13).png differ diff --git a/docs/.gitbook/assets/image (2).png b/docs/.gitbook/assets/image (2).png index a0663a4057..a5ac75df9e 100644 Binary files a/docs/.gitbook/assets/image (2).png and b/docs/.gitbook/assets/image (2).png differ diff --git a/docs/.gitbook/assets/image (3) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1).png b/docs/.gitbook/assets/image (3) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1).png new file mode 100644 index 0000000000..ade9ae13a4 Binary files /dev/null and b/docs/.gitbook/assets/image (3) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1).png differ diff --git a/docs/.gitbook/assets/image (3) (1) (1) (1) (1) (1) (1) (1).png b/docs/.gitbook/assets/image (3) (1) (1) (1) (1) (1) (1) (1).png index ade9ae13a4..ecebc76904 100644 Binary files a/docs/.gitbook/assets/image (3) (1) (1) (1) (1) (1) (1) (1).png and b/docs/.gitbook/assets/image (3) (1) (1) (1) (1) (1) (1) (1).png differ diff --git a/docs/.gitbook/assets/image (3) (1) (1) (1) (1) (1) (1).png b/docs/.gitbook/assets/image (3) (1) (1) (1) (1) (1) (1).png index ecebc76904..13eea5a6ff 100644 Binary files a/docs/.gitbook/assets/image (3) (1) (1) (1) (1) (1) (1).png and b/docs/.gitbook/assets/image (3) (1) (1) (1) (1) (1) (1).png differ diff --git a/docs/.gitbook/assets/image (3) (1) (1) (1) (1) (1).png b/docs/.gitbook/assets/image (3) (1) (1) (1) (1) (1).png index 13eea5a6ff..2f623aff02 100644 Binary files a/docs/.gitbook/assets/image (3) (1) (1) (1) (1) (1).png and b/docs/.gitbook/assets/image (3) (1) (1) (1) (1) (1).png differ diff --git a/docs/.gitbook/assets/image (3) (1) (1) (1) (1).png b/docs/.gitbook/assets/image (3) (1) (1) (1) (1).png index 2f623aff02..734d730eba 100644 Binary files a/docs/.gitbook/assets/image (3) (1) (1) (1) (1).png and b/docs/.gitbook/assets/image (3) (1) (1) (1) (1).png differ diff --git a/docs/.gitbook/assets/image (3) (1) (1) (1).png b/docs/.gitbook/assets/image (3) (1) (1) (1).png index 734d730eba..7802b32b61 100644 Binary files a/docs/.gitbook/assets/image (3) (1) (1) (1).png and b/docs/.gitbook/assets/image (3) (1) (1) (1).png differ diff --git a/docs/.gitbook/assets/image (3) (1) (1).png b/docs/.gitbook/assets/image (3) (1) (1).png index 7802b32b61..fc06579210 100644 Binary files a/docs/.gitbook/assets/image (3) (1) (1).png and b/docs/.gitbook/assets/image (3) (1) (1).png differ diff --git a/docs/.gitbook/assets/image (3) (1).png b/docs/.gitbook/assets/image (3) (1).png index fc06579210..6922c1ab59 100644 Binary files a/docs/.gitbook/assets/image (3) (1).png and b/docs/.gitbook/assets/image (3) (1).png differ diff --git a/docs/.gitbook/assets/image (3).png b/docs/.gitbook/assets/image (3).png index 6922c1ab59..0fa910aa12 100644 Binary files a/docs/.gitbook/assets/image (3).png and b/docs/.gitbook/assets/image (3).png differ diff --git a/docs/.gitbook/assets/image (366) (1).png b/docs/.gitbook/assets/image (366) (1).png new file mode 100644 index 0000000000..ddeafac288 Binary files /dev/null and b/docs/.gitbook/assets/image (366) (1).png differ diff --git a/docs/.gitbook/assets/image (366).png b/docs/.gitbook/assets/image (366).png index ddeafac288..e204726935 100644 Binary files a/docs/.gitbook/assets/image (366).png and b/docs/.gitbook/assets/image (366).png differ diff --git a/docs/.gitbook/assets/image (404).png b/docs/.gitbook/assets/image (404).png new file mode 100644 index 0000000000..7577fa75a5 Binary files /dev/null and b/docs/.gitbook/assets/image (404).png differ diff --git a/docs/.gitbook/assets/image (405).png b/docs/.gitbook/assets/image (405).png new file mode 100644 index 0000000000..c467b6b3d8 Binary files /dev/null and b/docs/.gitbook/assets/image (405).png differ diff --git a/docs/.gitbook/assets/image (406).png b/docs/.gitbook/assets/image (406).png new file mode 100644 index 0000000000..44ee45aca9 Binary files /dev/null and b/docs/.gitbook/assets/image (406).png differ diff --git a/docs/.gitbook/assets/image (407).png b/docs/.gitbook/assets/image (407).png new file mode 100644 index 0000000000..3f13fac3d6 Binary files /dev/null and b/docs/.gitbook/assets/image (407).png differ diff --git a/docs/.gitbook/assets/image (408).png b/docs/.gitbook/assets/image (408).png new file mode 100644 index 0000000000..a67f25afbe Binary files /dev/null and b/docs/.gitbook/assets/image (408).png differ diff --git a/docs/.gitbook/assets/image (409).png b/docs/.gitbook/assets/image (409).png new file mode 100644 index 0000000000..03cf6e1ec9 Binary files /dev/null and b/docs/.gitbook/assets/image (409).png differ diff --git a/docs/.gitbook/assets/image (410).png b/docs/.gitbook/assets/image (410).png new file mode 100644 index 0000000000..5b18e70066 Binary files /dev/null and b/docs/.gitbook/assets/image (410).png differ diff --git a/docs/.gitbook/assets/image (411).png b/docs/.gitbook/assets/image (411).png new file mode 100644 index 0000000000..c2784dfdb8 Binary files /dev/null and b/docs/.gitbook/assets/image (411).png differ diff --git a/docs/.gitbook/assets/image (412).png b/docs/.gitbook/assets/image (412).png new file mode 100644 index 0000000000..d53bc364ac Binary files /dev/null and b/docs/.gitbook/assets/image (412).png differ diff --git a/docs/.gitbook/assets/image (413).png b/docs/.gitbook/assets/image (413).png new file mode 100644 index 0000000000..8d5e1e9b88 Binary files /dev/null and b/docs/.gitbook/assets/image (413).png differ diff --git a/docs/.gitbook/assets/image (414).png b/docs/.gitbook/assets/image (414).png new file mode 100644 index 0000000000..fd6ef02629 Binary files /dev/null and b/docs/.gitbook/assets/image (414).png differ diff --git a/docs/.gitbook/assets/image (415).png b/docs/.gitbook/assets/image (415).png new file mode 100644 index 0000000000..3958865997 Binary files /dev/null and b/docs/.gitbook/assets/image (415).png differ diff --git a/docs/.gitbook/assets/image (416).png b/docs/.gitbook/assets/image (416).png new file mode 100644 index 0000000000..d9ebe6336d Binary files /dev/null and b/docs/.gitbook/assets/image (416).png differ diff --git a/docs/.gitbook/assets/image.png b/docs/.gitbook/assets/image.png index e204726935..321ae9e296 100644 Binary files a/docs/.gitbook/assets/image.png and b/docs/.gitbook/assets/image.png differ diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 7b12438ece..b5b9dee7e9 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -52,6 +52,7 @@ * [🗄 Standard Registry](guardian/standard-registry/README.md) * [🛠 Schemas](guardian/standard-registry/schemas/README.md) * [📂 Available Schema Types](guardian/standard-registry/schemas/available-schema-types.md) + * [📂 Property Glossary](guardian/standard-registry/schemas/property-glossary.md) * [ℹ System/Policy Schemas](guardian/standard-registry/schemas/system-policy-schemas.md) * [💻 Creating Schema using UI](guardian/standard-registry/schemas/creating-system-schema-using-ui.md) * [⚙ Schema APIs](guardian/standard-registry/schemas/schema-creation-using-apis/README.md) @@ -89,6 +90,8 @@ * [⚙ Schema Differentiation APIs](guardian/standard-registry/schemas/schema-differentiation/schema-differentiation-apis/README.md) * [Returns Result of Schema comparison](guardian/standard-registry/schemas/schema-differentiation/schema-differentiation-apis/returns-result-of-schema-comparison.md) * [Exports Schema Differentiation Results](guardian/standard-registry/schemas/schema-differentiation/schema-differentiation-apis/exports-schema-differentiation-results.md) + * [📁 Example Data](guardian/standard-registry/schemas/example-data/README.md) + * [💻 Adding Example data using UI](guardian/standard-registry/schemas/example-data/adding-example-data-using-ui.md) * [📂 Schema Tree](guardian/standard-registry/schemas/schema-tree/README.md) * [💻 Schema Tree UI](guardian/standard-registry/schemas/schema-tree/schema-tree-ui.md) * [⚙ Returning Schema Tree](guardian/standard-registry/schemas/schema-tree/returning-schema-tree.md) @@ -219,6 +222,8 @@ * [Policy Review](guardian/standard-registry/policies/apis-for-asynchronous-execution/policy-review.md) * [💻 Creating Roles and Groups using Policy Configurator UI](guardian/standard-registry/policies/roles-and-groups.md) * [💻 Configuring Multi Policy using UI](guardian/standard-registry/policies/page-1.md) + * [📁 Record/Replay](guardian/standard-registry/policies/record-replay/README.md) + * [💻 Policy execution record and replay using UI](guardian/standard-registry/policies/record-replay/policy-execution-record-and-replay-using-ui.md) * [📁 Multi Policy Differentiation](guardian/standard-registry/policies/policy-differentiation/README.md) * [💻 Multi Policy Differentiation using UI](guardian/standard-registry/policies/policy-differentiation/policy-differentiation-using-ui.md) * [⚙ Multi Policy Differentiation APIs](guardian/standard-registry/policies/policy-differentiation/policy-differentiation-apis/README.md) @@ -315,6 +320,13 @@ * [⛓ TrustChain reports](guardian/standard-registry/policies/library-of-policy-examples/trustchain-reports.md) * [➗ MRV aggregation and splitting for minting tokens](guardian/standard-registry/policies/library-of-policy-examples/mrv-aggregation-and-splitting-for-minting-tokens.md) * [💻 Demo on Integrating external policies using UI](guardian/standard-registry/policies/demo-on-integrating-external-policies-using-ui.md) + * [📁 Project Comparison](guardian/standard-registry/project-comparison/README.md) + * [💻 Project Comparison using UI](guardian/standard-registry/project-comparison/project-comparison-using-ui.md) + * [⚙ Project Comparison APIs](guardian/standard-registry/project-comparison/project-comparison-apis/README.md) + * [Comparing Documents](guardian/standard-registry/project-comparison/project-comparison-apis/comparing-documents.md) + * [Retrieves all categories](guardian/standard-registry/project-comparison/project-comparison-apis/retrieves-all-categories.md) + * [Retrieves all Properties](guardian/standard-registry/project-comparison/project-comparison-apis/retrieves-all-properties.md) + * [Search Projects by filters](guardian/standard-registry/project-comparison/project-comparison-apis/search-projects-by-filters.md) * [🔑 Selective Disclosure](guardian/standard-registry/selective-disclosure/README.md) * [📔 User Guide](guardian/standard-registry/selective-disclosure/user-guide.md) * [🔐 Selective Disclosure Demo](guardian/standard-registry/selective-disclosure/selective-disclosure-demo.md) @@ -385,6 +397,16 @@ * [📱 Mobile Support for Data Interface](guardian/users/mobile-support-for-data-interface/README.md) * [📱 Mobile Operation for the user](guardian/users/mobile-support-for-data-interface/mobile-operation-for-the-user.md) * [🛠 User Profile Setup](guardian/users/user-profile-setup.md) + * [🤖 AI Search](guardian/users/ai-search/README.md) + * [💻 AI Search using UI](guardian/users/ai-search/ai-search-using-ui.md) + * [⚙ AI Search APIs](guardian/users/ai-search/ai-search-apis/README.md) + * [Returns response](guardian/users/ai-search/ai-search-apis/returns-response.md) + * [Rebuilds vector based on policy data](guardian/users/ai-search/ai-search-apis/rebuilds-vector-based-on-policy-data.md) + * [🔎 Guided Search of Methodologies](guardian/users/guided-search-of-methodologies/README.md) + * [💻 Search using UI](guardian/users/guided-search-of-methodologies/search-using-ui.md) + * [⚙ Search APIs](guardian/users/guided-search-of-methodologies/search-apis/README.md) + * [Retrieves list of all categories](guardian/users/guided-search-of-methodologies/search-apis/retrieves-list-of-all-categories.md) + * [List of policies that are best suited for given parameters](guardian/users/guided-search-of-methodologies/search-apis/list-of-policies-that-are-best-suited-for-given-parameters.md) * [🪙 Tokens](guardian/tokens/README.md) * [💻 Creating Token using UI](guardian/tokens/creating-token-using-ui.md) * [📒 Token Template](guardian/tokens/token-template/README.md) @@ -513,3 +535,5 @@ * [Performance Improvement](guardian-in-production/performance-improvement.md) * [Cloud Infrastructure](guardian-in-production/cloud-infrastructure.md) * [Independent Packaged Deployment](guardian-in-production/independent-packaged-deployment.md) +* [Import](import/README.md) + * [Policy execution record and replay](import/policy-execution-record-and-replay.md) diff --git a/docs/guardian/demo-guide/carbon-emissions/ghgp-corporate-standard-v2.md b/docs/guardian/demo-guide/carbon-emissions/ghgp-corporate-standard-v2.md index b8e3d94925..ef6e439d99 100644 --- a/docs/guardian/demo-guide/carbon-emissions/ghgp-corporate-standard-v2.md +++ b/docs/guardian/demo-guide/carbon-emissions/ghgp-corporate-standard-v2.md @@ -48,7 +48,7 @@ The Guardian GHGP Corporate Policy offers a unique technical opportunity for com ### Policy Workflow -
+
### Policy Guide @@ -153,7 +153,7 @@ The Organization is responsible for inputting key data and information and assig
-
+
diff --git a/docs/guardian/demo-guide/carbon-offsets/cdm-acm0001-flaring-or-use-of-landfill-gas.md b/docs/guardian/demo-guide/carbon-offsets/cdm-acm0001-flaring-or-use-of-landfill-gas.md index 60b1f44b9c..2d573866d6 100644 --- a/docs/guardian/demo-guide/carbon-offsets/cdm-acm0001-flaring-or-use-of-landfill-gas.md +++ b/docs/guardian/demo-guide/carbon-offsets/cdm-acm0001-flaring-or-use-of-landfill-gas.md @@ -90,7 +90,7 @@ Certified Emission Reduction (CER) credits, each equivalent to one tonne of CO2. ### Workflow -
+
### IPFS Timestamp diff --git a/docs/guardian/demo-guide/carbon-offsets/cdm-acm0002-grid-connected-electricity-generation-from-renewable-sources.md b/docs/guardian/demo-guide/carbon-offsets/cdm-acm0002-grid-connected-electricity-generation-from-renewable-sources.md index f3da7c36ca..6623cc5fe1 100644 --- a/docs/guardian/demo-guide/carbon-offsets/cdm-acm0002-grid-connected-electricity-generation-from-renewable-sources.md +++ b/docs/guardian/demo-guide/carbon-offsets/cdm-acm0002-grid-connected-electricity-generation-from-renewable-sources.md @@ -94,7 +94,7 @@ Certified Emission Reduction (CER) credits, each equivalent to one tonne of CO2. ### Workflow -
+
### IPFS Timestamp diff --git a/docs/guardian/demo-guide/carbon-offsets/cdm-acm0006-electricity-and-heat-generation-from-biomass.md b/docs/guardian/demo-guide/carbon-offsets/cdm-acm0006-electricity-and-heat-generation-from-biomass.md index a003e30761..9ddc521993 100644 --- a/docs/guardian/demo-guide/carbon-offsets/cdm-acm0006-electricity-and-heat-generation-from-biomass.md +++ b/docs/guardian/demo-guide/carbon-offsets/cdm-acm0006-electricity-and-heat-generation-from-biomass.md @@ -31,7 +31,7 @@ In the modern landscape of emission reduction initiatives, the value of transpar ### Policy Workflow -
+
### Policy Guide diff --git a/docs/guardian/readme/getting-started/installation/building-from-source-and-run-using-docker/README.md b/docs/guardian/readme/getting-started/installation/building-from-source-and-run-using-docker/README.md index 7c58b71bbf..d2a6f412ff 100644 --- a/docs/guardian/readme/getting-started/installation/building-from-source-and-run-using-docker/README.md +++ b/docs/guardian/readme/getting-started/installation/building-from-source-and-run-using-docker/README.md @@ -126,16 +126,16 @@ This configuration allows you to leave untouched all the data referring to Mainn * 4.1.2 For setup IPFS local node you need to set variables in the same file `./configs/.env.develop.guardian.system` ``` -IPFS_NODE_ADDRESS="..." # Default IPFS_NODE_ADDRESS="http://localhost:5001" -IPFS_PUBLIC_GATEWAY='...' # Default IPFS_PUBLIC_GATEWAY='https://localhost:8080/ipfs/${cid}' +IPFS_NODE_ADDRESS="..." # Default IPFS_NODE_ADDRESS="http://ipfs-node:5001" +IPFS_PUBLIC_GATEWAY='...' # Default IPFS_PUBLIC_GATEWAY='http://ipfs-node:8080/ipfs/${cid}' IPFS_PROVIDER="local" ``` {% hint style="info" %} Note: -1. Default IPFS\_NODE\_ADDRESS="[http://localhost:5001](http://localhost:5001/)" -2. Default IPFS\_PUBLIC\_GATEWAY="[https://localhost:8080/ipfs/${cid}](https://localhost:8080/ipfs/$%7Bcid%7D)" +1. Default IPFS\_NODE\_ADDRESS="[http://ipfs-node:5001](http://ipfs-node:5001)" +2. Default IPFS\_PUBLIC\_GATEWAY="[http://ipfs-node:8080/ipfs/${cid}](http://ipfs-node:8080/ipfs/$%7Bcid%7D%22)" {% endhint %} #### 4.2 Setting up IPFS Web3Storage node: diff --git a/docs/guardian/standard-registry/policies/record-replay/README.md b/docs/guardian/standard-registry/policies/record-replay/README.md new file mode 100644 index 0000000000..f1f1e03278 --- /dev/null +++ b/docs/guardian/standard-registry/policies/record-replay/README.md @@ -0,0 +1,7 @@ +# 📁 Record/Replay + +**“**Record/Run” options provide facilities to capture policy execution events, save and/or export them in a records file, which can then be replayed in the context of a different policy. + +{% hint style="info" %} +Note: This functionality currently works in a Dry Run mode only. +{% endhint %} diff --git a/docs/guardian/standard-registry/policies/record-replay/policy-execution-record-and-replay-using-ui.md b/docs/guardian/standard-registry/policies/record-replay/policy-execution-record-and-replay-using-ui.md new file mode 100644 index 0000000000..293ff932bd --- /dev/null +++ b/docs/guardian/standard-registry/policies/record-replay/policy-execution-record-and-replay-using-ui.md @@ -0,0 +1,89 @@ +# 💻 Policy execution record and replay using UI + +## 1. Record + +The recording starts on the press of the ‘Record’ button. + +{% hint style="info" %} +Note: It is strongly recommended to initiate recording at the very beginning of the policy execution, otherwise issues may be encountered at the ‘Replay’ stage. +{% endhint %} + +![image1.png](<../../../../.gitbook/assets/0 (11).png>) + +### 1.1 Menu + +
+ +1. “Stop” - ends the recording and downloads the capture file. + +![image3.png](<../../../../.gitbook/assets/2 (15).png>) + +2. Actions - shows/hides the list of recorded steps/events. + +![image4.png](<../../../../.gitbook/assets/3 (12).png>) + +## 2. Replay + +Pressing the ‘Run’ button will initiate replay of the previously recorded file. + +![image5.png](<../../../../.gitbook/assets/4 (10).png>) + +![image6.png](<../../../../.gitbook/assets/5 (13).png>) + +### 2.1 Menu + +
+ +1. Fast Forward - quick transition to the next step (skipping the replay of the recorded pause). + +![image8.png](<../../../../.gitbook/assets/7 (12).png>) + +2. Stop - ends the replay + +![image9.png](<../../../../.gitbook/assets/8 (13).png>) + +3. Actions - shows/hides the list of replayed steps/events + +![image10.png](<../../../../.gitbook/assets/9 (11).png>) + +### 2.2 Error + +![image11.png](<../../../../.gitbook/assets/10 (12).png>) + +In the case of an error the following actions are possible + +1. Retry - attempts to repeat the errored step/event + +![image12.png](<../../../../.gitbook/assets/11 (11).png>) + +2. Skip - skips the errored step/event and execute the next one + +![image13.png](<../../../../.gitbook/assets/12 (11).png>) + +### 2.3 Results + +A summary dialogue is shown at the end of the replay. This dialogue contains the information about the tokens and document created during the policy execution, and the extend to which these artifacts are similar to those produced during the original execution (when the ‘records’ file was created). + +![image14.png](<../../../../.gitbook/assets/13 (11).png>) + +The ‘details’ page shows detailed breakdown of differences between the corresponding documents or tokens. + +![image15.png](<../../../../.gitbook/assets/14 (9).png>) + +**Schemas** + +Changes in the Schemas menu + +![image16.png](<../../../../.gitbook/assets/15 (11).png>) + +1. Example – the facility to add example values for the schema fields + +![image17.png](<../../../../.gitbook/assets/16 (10).png>) + +1. Preview – show a preview of how users will see the policy form during the execution of the policy + +![image18.png](<../../../../.gitbook/assets/17 (11).png>) + +In Dry Run mode it is possible to quickly fill in the fields using the provided example values from the schema. This feature is most useful for testing/demonstrations or for experimenting and learning Guardian capabilities. + +![image19.png](<../../../../.gitbook/assets/18 (8).png>) diff --git a/docs/guardian/standard-registry/policies/search-block/search-block-using-ui.md b/docs/guardian/standard-registry/policies/search-block/search-block-using-ui.md index b7398860d6..a63918ccf2 100644 --- a/docs/guardian/standard-registry/policies/search-block/search-block-using-ui.md +++ b/docs/guardian/standard-registry/policies/search-block/search-block-using-ui.md @@ -30,7 +30,7 @@ Clicking “Apply” button will transfer (or apply) the configuration of the fo **Note:** Original settings of the base policy block will be lost if ‘Apply’ action is executed. {% endhint %} -
+
### 2.3 Search Results Layout Display diff --git a/docs/guardian/standard-registry/project-comparison/README.md b/docs/guardian/standard-registry/project-comparison/README.md new file mode 100644 index 0000000000..4105db332e --- /dev/null +++ b/docs/guardian/standard-registry/project-comparison/README.md @@ -0,0 +1,3 @@ +# 📁 Project Comparison + +The Project Data Comparison feature is designed to facilitate the comparison of various VC (Verifiable Credentials) documents, created under specific policy schemas designated as 'Project Schemas'. The Project Data Comparison feature offers a robust and user-friendly platform for analyzing and contrasting various projects, aiding users in making informed decisions based on comprehensive data comparison. diff --git a/docs/guardian/standard-registry/project-comparison/project-comparison-apis/README.md b/docs/guardian/standard-registry/project-comparison/project-comparison-apis/README.md new file mode 100644 index 0000000000..eb5d7fdae3 --- /dev/null +++ b/docs/guardian/standard-registry/project-comparison/project-comparison-apis/README.md @@ -0,0 +1,2 @@ +# ⚙ Project Comparison APIs + diff --git a/docs/guardian/standard-registry/project-comparison/project-comparison-apis/comparing-documents.md b/docs/guardian/standard-registry/project-comparison/project-comparison-apis/comparing-documents.md new file mode 100644 index 0000000000..03a7244c56 --- /dev/null +++ b/docs/guardian/standard-registry/project-comparison/project-comparison-apis/comparing-documents.md @@ -0,0 +1,31 @@ +# Comparing Documents + +{% swagger method="post" path="" baseUrl="/analytics/compare/documents" summary="Compare documents. Only users with the Standard Registry role are allowed to make the request." %} +{% swagger-description %} +Compare documents. Only users with the Standard Registry role are allowed to make the request. +{% endswagger-description %} + +{% swagger-parameter in="body" name="documentIds" type="String" required="true" %} +Document Identifiers to compare +{% endswagger-parameter %} + +{% swagger-response status="200: OK" description="Successful Operation" %} +``` +{ + "documents": {}, + "left": {}, + "right": {}, + "total": {} +} +``` +{% endswagger-response %} + +{% swagger-response status="500: Internal Server Error" description="Internal Server Error" %} +``` +{ + "code": 0, + "message": "string" +} +``` +{% endswagger-response %} +{% endswagger %} diff --git a/docs/guardian/standard-registry/project-comparison/project-comparison-apis/retrieves-all-categories.md b/docs/guardian/standard-registry/project-comparison/project-comparison-apis/retrieves-all-categories.md new file mode 100644 index 0000000000..0a8c5a5f42 --- /dev/null +++ b/docs/guardian/standard-registry/project-comparison/project-comparison-apis/retrieves-all-categories.md @@ -0,0 +1,20 @@ +# Retrieves all categories + +{% swagger method="get" path="" baseUrl="/policies/categories" summary="Get all categories" %} +{% swagger-description %} +Get all categories +{% endswagger-description %} + +{% swagger-response status="200: OK" description="Successful Operation" %} + +{% endswagger-response %} + +{% swagger-response status="500: Internal Server Error" description="Internal Server Error" %} +``` +{ + "code": 0, + "message": "string" +} +``` +{% endswagger-response %} +{% endswagger %} diff --git a/docs/guardian/standard-registry/project-comparison/project-comparison-apis/retrieves-all-properties.md b/docs/guardian/standard-registry/project-comparison/project-comparison-apis/retrieves-all-properties.md new file mode 100644 index 0000000000..f99200ff66 --- /dev/null +++ b/docs/guardian/standard-registry/project-comparison/project-comparison-apis/retrieves-all-properties.md @@ -0,0 +1,20 @@ +# Retrieves all Properties + +{% swagger method="get" path="" baseUrl="/projects/properties" summary="Get all properties" %} +{% swagger-description %} +Get all properties +{% endswagger-description %} + +{% swagger-response status="200: OK" description="Successful Operation" %} + +{% endswagger-response %} + +{% swagger-response status="500: Internal Server Error" description="Internal Server Error" %} +``` +{ + "code": 0, + "message": "string" +} +``` +{% endswagger-response %} +{% endswagger %} diff --git a/docs/guardian/standard-registry/project-comparison/project-comparison-apis/search-projects-by-filters.md b/docs/guardian/standard-registry/project-comparison/project-comparison-apis/search-projects-by-filters.md new file mode 100644 index 0000000000..eec92b37a9 --- /dev/null +++ b/docs/guardian/standard-registry/project-comparison/project-comparison-apis/search-projects-by-filters.md @@ -0,0 +1,36 @@ +# Search Projects by filters + +{% swagger method="post" path="" baseUrl="/projects/search" summary="Search projects by filters" %} +{% swagger-description %} +Search projects by filters +{% endswagger-description %} + +{% swagger-parameter in="body" name="q" type="String" required="true" %} +The question of the methodology +{% endswagger-parameter %} + +{% swagger-response status="200: OK" description="Successful Operation" %} +``` +[ + { + "id": "string", + "policyId": "string", + "policyName": "string", + "registered": "string", + "title": "string", + "companyName": "string", + "sectoralScope": "string" + } +] +``` +{% endswagger-response %} + +{% swagger-response status="500: Internal Server Error" description="Internal Server Error" %} +``` +{ + "code": 0, + "message": "string" +} +``` +{% endswagger-response %} +{% endswagger %} diff --git a/docs/guardian/standard-registry/project-comparison/project-comparison-using-ui.md b/docs/guardian/standard-registry/project-comparison/project-comparison-using-ui.md new file mode 100644 index 0000000000..408c587c75 --- /dev/null +++ b/docs/guardian/standard-registry/project-comparison/project-comparison-using-ui.md @@ -0,0 +1,43 @@ +# 💻 Project Comparison using UI + +## Project Schemas + +Project Schemas are predefined templates used to create VC documents. These schemas are selected for a specific policy and serve as the structural basis for generating project data. + +
+ +In the current schema setup, specific ‘property fields’ are explicitly designated for the comparison process. These fields play a crucial role in evaluating and contrasting different projects. + +
+ +These property fields were incorporated into the database as part of migration v.2-17-0, utilizing the 'policy-properties.csv' file. This integration ensures that the comparison process is seamless and data-driven. + +## Project Overview + +This section includes both category and policy filters, allowing users to sift through projects based on specific criteria. + +
+ +### Filter Logic + +Within a single group, filters apply a logical 'OR' to search for relevant projects. Between different groups, a logical 'AND' is used. This dual logic ensures a comprehensive filtering process. + +### Search Enhancement + +The functionality is further expanded by enabling searches based on project titles. + +
+ +### Result Dashboard + +Users are presented with a dashboard showcasing projects that align with their chosen filters. + +### Project Selection for Comparison + +Users can select multiple projects for a side-by-side comparison by clicking the 'plus' button. After selection, accessing the 'open the comparison' button redirects them to a detailed comparison view. + +### Projects Comparison Page + +On this page, users will find a comparative table displaying selected project fields. This visual representation allows for an easy and intuitive comparison of different projects, highlighting similarities and differences. + +
diff --git a/docs/guardian/standard-registry/schemas/available-schema-types.md b/docs/guardian/standard-registry/schemas/available-schema-types.md index 9b2520154d..7fc537c750 100644 --- a/docs/guardian/standard-registry/schemas/available-schema-types.md +++ b/docs/guardian/standard-registry/schemas/available-schema-types.md @@ -1,5 +1,21 @@ # 📂 Available Schema Types +## **Embracing GBBC Specifications for Universal Data Comparability** + +In the Guardian, the Property Glossary serves a pivotal role in harmonizing data across the entire ecosystem. Leveraging the standards set forth in the [GBBC dMRV Specification](https://gbbcouncil.org/wp-content/uploads/2023/09/Digital-Measurement-Reporting-Verification-dMRV-Framework.pdf) (and beyond), the glossary establishes a unified framework for interpreting and mapping data, ensuring that information collected through various schema formats remains consistent, comparable, and searchable. + +## **The Need for Standardized Data Mapping** + +The complexity of environmental reporting and digital asset management is compounded when dealing with varied methodologies and schema designs. To address this, the Guardian implements a standardized approach to data mapping, allowing for effective comparison and analysis of project data, regardless of its original schema format. This standardization is vital for: + +1. **Ensuring Consistency:** Regardless of how data is formatted in individual schemas, standardizing property definitions ensures a consistent approach to interpreting and comparing data. +2. **Facilitating Comparability:** By using a common language for data properties, the Guardian enables users to effectively compare and analyze data from similar or different methodologies. +3. **Enhancing Searchability:** Standardized properties allow for more efficient data retrieval, making it easier to locate specific information across various projects and schemas. + +## **Role of the Property Glossary** + +The Property Glossary in MGS is more than just a list of definitions; it is a tool for aligning data across the platform. It includes a table of **Standardized Property Definitions.** Drawing from the GBBC Specification, the glossary provides clear definitions for each property, ensuring a common understanding across the platform. + **Introduction** In the realm of digital environmental assets and carbon offset tokens, data is king. The essence of creating verifiable and trustworthy digital assets lies in the quality and structure of the underlying data. This is where the concept of "Schema Types" comes into play. Schemas serve as the backbone of data organization, ensuring that every piece of information adheres to a predefined format, thereby maintaining consistency, accuracy, and reliability. diff --git a/docs/guardian/standard-registry/schemas/creating-system-schema-using-ui.md b/docs/guardian/standard-registry/schemas/creating-system-schema-using-ui.md index bda0f54481..d1b090fe3e 100644 --- a/docs/guardian/standard-registry/schemas/creating-system-schema-using-ui.md +++ b/docs/guardian/standard-registry/schemas/creating-system-schema-using-ui.md @@ -12,9 +12,17 @@ In addition to the basic Schema details we also have an option to add Field and ![](<../../../.gitbook/assets/image (9) (1) (2) (1).png>) +We can also add Property dropdown field to each field in Schema. these properties are defined by IWA and are in standardized format. + +
+ +We can select respective property for that specific field by searching by entering starting alphabet: + +
+ We can also customize the Field keys and Field Title by clicking on Advanced Tab. -![](<../../../.gitbook/assets/image (3) (1) (1) (1) (1) (1) (1) (1) (1).png>) +![](<../../../.gitbook/assets/image (3) (1) (1) (1) (1) (1) (1) (1) (1) (1).png>) Instead of creating a new Schema from scratch, there is also an option to import it via File or via IPFS. diff --git a/docs/guardian/standard-registry/schemas/example-data/README.md b/docs/guardian/standard-registry/schemas/example-data/README.md new file mode 100644 index 0000000000..e9744be025 --- /dev/null +++ b/docs/guardian/standard-registry/schemas/example-data/README.md @@ -0,0 +1,2 @@ +# 📁 Example Data + diff --git a/docs/guardian/standard-registry/schemas/example-data/adding-example-data-using-ui.md b/docs/guardian/standard-registry/schemas/example-data/adding-example-data-using-ui.md new file mode 100644 index 0000000000..cd2cff8018 --- /dev/null +++ b/docs/guardian/standard-registry/schemas/example-data/adding-example-data-using-ui.md @@ -0,0 +1,17 @@ +# 💻 Adding Example data using UI + +1. Example: To add example values to a schema, we need to click on "Example" option as shown below: + +
+ +Once you click on the above button, you will be able to fill all schema fields with sample values: + +
+ +2. Preview – shows a preview of how users will see the policy form during the execution of the policy + +
+ +In Dry Run mode it is possible to quickly fill in the fields using the provided example values from the schema. This feature is most useful for testing/demonstrations or for experimenting and learning Guardian capabilities. + +
diff --git a/docs/guardian/standard-registry/schemas/property-glossary.md b/docs/guardian/standard-registry/schemas/property-glossary.md new file mode 100644 index 0000000000..69cacfcaaa --- /dev/null +++ b/docs/guardian/standard-registry/schemas/property-glossary.md @@ -0,0 +1,3 @@ +# 📂 Property Glossary + +
PropertyTypeDescription
AccountableImpactOrganization.idstringUnique identifier
AccountableImpactOrganization.namestringName of the entity
AccountableImpactOrganization.descriptionstringDescription of the entity
AccountableImpactOrganization.addressesarrayCollection of addresses
AccountableImpactOrganization.ownersarrayCollection of owners
AccountableImpactOrganization.countryUnknown TypeCountry of the address
AccountableImpactOrganization.regionUnknown TypeRegion where project is located
AccountableImpactOrganization.informationLinkUnknown TypeLink with project info
AccountableImpactOrganization.mediaLinksarrayCollection of media links
AccountableImpactOrganization.attestationsarrayCollection of attestations
AccountableImpactOrganization.activityImpactModulesarrayCollection of activity impact modules
ActivityImpactModule.idstringUnique identifier
ActivityImpactModule.aioIdstring
ActivityImpactModule.namestringName of the entity
ActivityImpactModule.classificationCategoryUnknown TypeProject classification category - Carbon Avoidance, Reduction, Removal
ActivityImpactModule.classificationMethodUnknown TypeClassification method - Natural, Technological, Both
ActivityImpactModule.benefitCategoryUnknown TypeBenefit category - Climate Action, Zero Hunger, etc.
ActivityImpactModule.projectScopeUnknown TypeProject scope - Agriculture, Carbon Capture, etc.
ActivityImpactModule.projectTypeUnknown TypeProject type - Afforestation, Cookstoves, etc.
ActivityImpactModule.projectScaleUnknown TypeProject scale - Micro, Small, Medium, Large
ActivityImpactModule.arbIdstringCA Air Resources Board ID
ActivityImpactModule.geographicLocationUnknown TypeGeographic location of project
ActivityImpactModule.firstYearIssuancestringFirst year credits issued
ActivityImpactModule.registryProjectIdstringProject ID on registry
ActivityImpactModule.developersarrayCollection of developers
ActivityImpactModule.sponsorsarrayCollection of sponsors
ActivityImpactModule.claimSourcesarrayCollection of claim sources
ActivityImpactModule.impactClaimsarrayCollection of impact claims
ActivityImpactModule.mrvExtensionsarrayCollection of MRV extensions
ActivityImpactModule.validationsarrayCollection of validations
ActivityImpactModule.attestationsarrayCollection of attestations
ActivityImpactModule.accountableImpactOrganizationUnknown Type
Address.addressTypeUnknown TypeType of address - Physical, Legal, Mailing
Address.addressLinesarrayAddress lines
Address.citystringCity of the address
Address.statestringState of the address
Address.zipstring
Address.countrystringCountry of the address
Any.typeUrlstring
Any.valuearray
Attestation.tagUnknown Type
Attestation.typeUnknown Type
Attestation.proofTypeUnknown Type
Attestation.attestorstring
Attestation.signatureUnknown Type
Audits.auditDateUnknown TypeLast audit date
Audits.auditReportsarrayCollection of audit report links
CRU.idstringUnique identifier
CRU.quantitystringQuantity of credit
CRU.unitUnknown TypeUnit of credit
CRU.ownerIdstring
CRU.listingAgentIdstring
CRU.coreCarbonPrinciplesUnknown Type
CRU.climateLabelsarrayCollection of climate labels
CRU.statusUnknown TypeStatus of credit - Active, Inactive, etc.
CRU.referencedCreditUnknown TypeDetails of referenced credit
CRU.appliedToIdstringIdentifier credit was applied to
CRU.processedClaimIdstringProcessed claim ID
CRU.issuerIdstringIssuer identifier
CRU.processedClaimUnknown Type
CheckpointResult.idstringUnique identifier
CheckpointResult.checkpointIdstringCheckpoint identifier
CheckpointResult.linkToVerificationDataUnknown TypeLink to verification data
CheckpointResult.dateRangeUnknown Type
CheckpointResult.efBeforestringEnvironmental factor before activity
CheckpointResult.efAfterstringEnvironmental factor after activity
CheckpointResult.mrvExtensionsarrayCollection of MRV extensions
ClaimSource.idstringUnique identifier
ClaimSource.aimIdstring
ClaimSource.namestringName of the entity
ClaimSource.descriptionstringDescription of the entity
ClaimSource.locationUnknown Type
ClaimSource.sourceTypeUnknown TypeType of claim source - Sensor, Application, Reference
ClaimSource.unitOfMeasureUnknown TypeUnit of measurement
ClaimSource.sourceIdentifierstringIdentifier for claim source
ClaimSource.mrvExtensionsarrayCollection of MRV extensions
ClimateLabel.idstringUnique identifier
ClimateLabel.namestringName of the entity
ClimateLabel.descriptionstringDescription of the entity
CoBenefit.unSdgUnknown Type
CoBenefit.descriptionstringDescription of the entity
CoreCarbonPrinciples.assetIdstringAsset identifier
CoreCarbonPrinciples.issuanceDateUnknown TypeCredit issuance date
CoreCarbonPrinciples.vintagestringVintage year of credit
CoreCarbonPrinciples.generationTypeUnknown TypeHow credit was generated - Actual, Estimated, etc.
CoreCarbonPrinciples.verificationStandardUnknown TypeVerification standard used
CoreCarbonPrinciples.mitigationActivityUnknown TypeMitigation activity details
CoreCarbonPrinciples.durabilityUnknown Type
CoreCarbonPrinciples.replacementUnknown Type
CoreCarbonPrinciples.parisAgreementComplianceUnknown Type
CoreCarbonPrinciples.quantifiedSdgImpactsarrayQuantified SDG impacts
CoreCarbonPrinciples.adaptationCoBenefitsarrayAdaptation co-benefits
Credential.contextarray
Credential.idstringUnique identifier
Credential.typearray
Credential.issuerstring
Credential.issuanceDatestringCredit issuance date
Credential.credentialSubjectUnknown Type
Credential.proofUnknown Type
CredentialSubject.idstringUnique identifier
CredentialSubject.propertyarray
DataExtension.keystring
DataExtension.valuestring
DataExtension.dataarrayJSON data for typed extension
Date.dateTimeUnknown Type
Date.dateStringstring
DatePoint.dateUnknown Type
DatePoint.timeStampUnknown Type
DateRange.startDateUnknown Type
DateRange.endDateUnknown Type
Degradable.percentageintegerDegradation percentage
Degradable.factorintegerDegradation factor
Degradable.degradationTypeUnknown TypeType of degradation - Linear, Exponential
DigitalSignature.typeUnknown Type
DigitalSignature.jwsstring
DigitalSignature.vcUnknown Type
DigitalSignature.signatureCaseUnknown Type
Durability.storageTypeUnknown TypeStorage type - Biological, Geological, etc.
Durability.yearsintegerExpected duration in years
Durability.degradableUnknown TypeDegradability details
Durability.reversalMitigationUnknown Type
GeographicLocation.longitudestring
GeographicLocation.latitudestring
GeographicLocation.geoJsonOrKmlstring
GeographicLocation.geographicLocationFileUnknown Type
ImpactClaim.idstringUnique identifier
ImpactClaim.aimIdstring
ImpactClaim.processedClaimIdstringProcessed claim ID
ImpactClaim.unitUnknown TypeUnit of credit
ImpactClaim.quantitystringQuantity of credit
ImpactClaim.coBenefitsarrayCollection of co-benefits
ImpactClaim.checkpointsarrayCollection of checkpoints
ImpactClaim.mrvExtensionsarrayCollection of MRV extensions
ImpactClaim.activityImpactModuleUnknown Type
ImpactClaimCheckpoint.idstringUnique identifier
ImpactClaimCheckpoint.claimIdstring
ImpactClaimCheckpoint.claimSourceIdsarray
ImpactClaimCheckpoint.projectDeveloperIdstring
ImpactClaimCheckpoint.efBeforestringEnvironmental factor before activity
ImpactClaimCheckpoint.efAfterstringEnvironmental factor after activity
ImpactClaimCheckpoint.checkpointDateRangeUnknown TypeCheckpoint date range
ImpactClaimCheckpoint.verifiedLinkToCheckpointDataUnknown TypeLink to checkpoint data
ImpactClaimCheckpoint.mrvExtensionsarrayCollection of MRV extensions
ImpactClaimCheckpoint.spanDataPackageUnknown Type
MRVRequirements.measurementSpecificationUnknown Type
MRVRequirements.specificationLinkUnknown Type
MRVRequirements.precisionUnknown Type
MRVRequirements.claimPeriodUnknown Type
Manifest.idstringUnique identifier
Manifest.versionstring
Manifest.aimIdstring
Manifest.claimIdstring
Manifest.projectDeveloperIdstring
Manifest.createdUnknown Type
Manifest.mrvExtensionsarrayCollection of MRV extensions
Manifest.sdpFilesarrayCollection of SDP files
MitigationActivity.categoryUnknown Type
MitigationActivity.methodUnknown Type
MrvExtension.mrvExtensionContextUnknown TypeContext for MRV extension - AIM, Claim, etc.
MrvExtension.typedExtensionUnknown TypeTyped MRV extension
MrvExtension.untypedExtensionUnknown TypeUntyped MRV extension
MrvExtension.extensionCaseUnknown Type
PACompliance.caUnknown Type
PACompliance.letterOfApprovalUnknown TypeLink to approval letter
PrecisionMix.lowinteger
PrecisionMix.mediuminteger
PrecisionMix.highinteger
ProcessedClaim.idstringUnique identifier
ProcessedClaim.vpaIdstring
ProcessedClaim.impactClaimIdstring
ProcessedClaim.creditIdstringCredit identifier
ProcessedClaim.unitUnknown TypeUnit of credit
ProcessedClaim.quantitystringQuantity of credit
ProcessedClaim.coBenefitsarrayCollection of co-benefits
ProcessedClaim.mrvExtensionsarrayCollection of MRV extensions
ProcessedClaim.checkpointResultsarrayCollection of checkpoint results
ProcessedClaim.issuanceRequestUnknown Type
ProcessedClaim.verificationProcessAgreementUnknown Type
ProcessedClaim.impactClaimUnknown Type
ProcessedClaim.assetUnknown Type
Proof.typeUnknown Type
Proof.createdstring
Proof.proofPurposestring
Proof.verificationMethodstring
Proof.challengestring
Proof.domainstring
Proof.jwsstring
QualityStandard.namestringName of the entity
QualityStandard.descriptionstringDescription of the entity
QualityStandard.standardUnknown Type
QualityStandard.methodologyAndToolsarray
QualityStandard.versionstring
QualityStandard.coBenefitsarrayCollection of co-benefits
QualityStandard.standardLinkUnknown Type
REC.idstringUnique identifier
REC.recTypeUnknown TypeREC type - IREC, NERC
REC.validJurisdictionstringValid jurisdiction for REC
REC.quantitystringQuantity of credit
REC.unitUnknown TypeUnit of credit
REC.ownerIdstring
REC.listingAgentIdstring
REC.climateLabelsarrayCollection of climate labels
REC.statusUnknown TypeStatus of credit - Active, Inactive, etc.
REC.referencedRecUnknown Type
REC.appliedToIdstringIdentifier credit was applied to
REC.processedClaimIdstringProcessed claim ID
REC.issuerIdstringIssuer identifier
REC.processedClaimUnknown Type
ReferencedCredit.idstringUnique identifier
ReferencedRec.idstringUnique identifier
Replacement.replacesIdstring
Replacement.replacementDateUnknown TypeDate credit was replaced
Replacement.notesstring
ReversalMitigation.reversalRiskUnknown TypeRisk of reversal - Zero, Low, Material
ReversalMitigation.insuranceTypeUnknown TypeInsurance type - Buffer Pool, Refund, etc.
ReversalMitigation.insurancePolicyOwnerUnknown TypeOwner of insurance policy
ReversalMitigation.insurancePolicyLinkUnknown TypeLink to insurance policy
SdpFile.namestringName of the entity
SdpFile.typeUnknown Type
SdpFile.descriptionstringDescription of the entity
SdpFile.claimSourceIdstringClaim source ID
SdpFile.claimSourceAttestationstringClaim source attestation
SdpFile.mrvExtensionsarrayCollection of MRV extensions
Signatory.idstringUnique identifier
Signatory.namestringName of the entity
Signatory.descriptionstringDescription of the entity
Signatory.signatoryRoleenum"IssuingRegistry",
"ValidationAndVerificationBody",
"ProjectOwner",
"VerificationPlatformProvider"
Signatory.signatureUnknown Type
SpanDataPackage.manifestUnknown TypeManifest for span data package
Tag.namestringName of the entity
Tag.contextarray
Tag.descriptionstringDescription of the entity
Tag.datastringJSON data for typed extension
Timestamp.secondsinteger
Timestamp.nanosinteger
TypedExtension.dataSchemastringSchema URL for typed extension
TypedExtension.documentationstringDocumentation URL for extension
TypedExtension.datastringJSON data for typed extension
UntypedExtension.namestringName of the entity
UntypedExtension.versionstring
UntypedExtension.descriptionstringDescription of the entity
UntypedExtension.documentationstringDocumentation URL for extension
UntypedExtension.dataExtensionsarray
Validation.validationDateUnknown TypeValidation date
Validation.validatingPartyIdstringValidating party ID
Validation.validationMethodstringValidation method used
Validation.validationExpirationDateUnknown TypeValidation expiration date
Validation.validationStepsarrayCollection of validation steps
ValidationStep.validationStepNamestringName of validation step
ValidationStep.validationStepDescriptionstringDescription of validation step
ValidationStep.validationStepStatusUnknown TypeStatus of validation step - Not Started, In Progress, Completed
ValidationStep.validationStepDocumentLinkUnknown Type
VerificationProcessAgreement.idstringUnique identifier
VerificationProcessAgreement.namestringName of the entity
VerificationProcessAgreement.descriptionstringDescription of the entity
VerificationProcessAgreement.signatoriesarrayCollection of agreement signatories
VerificationProcessAgreement.qualityStandardUnknown TypeQuality standard used
VerificationProcessAgreement.mrvRequirementsUnknown TypeMRV requirements
VerificationProcessAgreement.agreementDateUnknown Type
VerificationProcessAgreement.estimatedAnnualCreditsstringEstimated annual credits
VerificationProcessAgreement.aimIdstring
VerificationProcessAgreement.auditScheduleUnknown TypeAudit schedule - Annual, Biannual, etc.
VerificationProcessAgreement.auditsUnknown TypeAudit details
VerificationProcessAgreement.activityImpactModuleUnknown Type
VerificationProcessAgreement.processedClaimsarray
VerifiedLink.idstringUnique identifier
VerifiedLink.uristring
VerifiedLink.descriptionstringDescription of the entity
VerifiedLink.hashProofstring
VerifiedLink.hashAlgorithmUnknown Type
diff --git a/docs/guardian/standard-registry/schemas/system-policy-schemas.md b/docs/guardian/standard-registry/schemas/system-policy-schemas.md index eb14134900..044222eb7d 100644 --- a/docs/guardian/standard-registry/schemas/system-policy-schemas.md +++ b/docs/guardian/standard-registry/schemas/system-policy-schemas.md @@ -17,7 +17,7 @@ To display System / Policy Schemas in the GUI, we have added a toggle in the Sch Whenever an account is created, System Schemas are generated automatically. -
+
{% hint style="info" %} Note: By default System Schemas cannot be edited/deleted. diff --git a/docs/guardian/tokens/retirement-contract/README.md b/docs/guardian/tokens/retirement-contract/README.md index acdf1ffcc4..2ae515b85d 100644 --- a/docs/guardian/tokens/retirement-contract/README.md +++ b/docs/guardian/tokens/retirement-contract/README.md @@ -6,7 +6,7 @@ The ‘retirement contracts’ are responsible for the mechanics of matching ‘ The high-level flow of the actions which configure token retirement is illustrated on the sequence diagram below: -
+
Guardian contains a full production retirement system implementation which allows the configuration of arbitrary pools of tokens (two-sided, one-sides, with arbitrary ‘exchange rates’ etc), issued by different SRs, operating on different Guardian instances, to be configured for retirement with arbitrary additional rules further controlling their lifecycle. diff --git a/docs/guardian/tokens/retirement-contract/creating-contract-using-ui.md b/docs/guardian/tokens/retirement-contract/creating-contract-using-ui.md index 6bd3bec7f2..596c5318f4 100644 --- a/docs/guardian/tokens/retirement-contract/creating-contract-using-ui.md +++ b/docs/guardian/tokens/retirement-contract/creating-contract-using-ui.md @@ -43,7 +43,7 @@ Also you can check “without approval” to set retirement tokens as immediate 7\) _SRs_ can check/approve/reject/ban requests for the wiper role in the wipe contract (Requests operation in wipe contract). -
+
8\) To execute the retirement Guardian users which hold USER role navigate to the ‘Retire’ tab and click on ‘Retire’ button, choose appropriate pool and set token count/serials diff --git a/docs/guardian/tokens/token-template/how-to-create-token-template.md b/docs/guardian/tokens/token-template/how-to-create-token-template.md index 84ea7502ad..04268202ab 100644 --- a/docs/guardian/tokens/token-template/how-to-create-token-template.md +++ b/docs/guardian/tokens/token-template/how-to-create-token-template.md @@ -2,4 +2,4 @@ We have a new tab as Token for all Blocks. This tab is used to add token template and set value for each token option. These values will be filled by User if it is null. -
+
diff --git a/docs/guardian/users/ai-search/README.md b/docs/guardian/users/ai-search/README.md new file mode 100644 index 0000000000..dfb0e9347a --- /dev/null +++ b/docs/guardian/users/ai-search/README.md @@ -0,0 +1,3 @@ +# 🤖 AI Search + +AI Search is a specialized tool designed to assist users in identifying the most appropriate methodologies for their needs. Developed using an advanced embedding model, vector storage, OpenAI, and the LangChain framework, this tool provides a prompt/response model distinct from traditional chat interfaces. diff --git a/docs/guardian/users/ai-search/ai-search-apis/README.md b/docs/guardian/users/ai-search/ai-search-apis/README.md new file mode 100644 index 0000000000..caf015f6b2 --- /dev/null +++ b/docs/guardian/users/ai-search/ai-search-apis/README.md @@ -0,0 +1,2 @@ +# ⚙ AI Search APIs + diff --git a/docs/guardian/users/ai-search/ai-search-apis/rebuilds-vector-based-on-policy-data.md b/docs/guardian/users/ai-search/ai-search-apis/rebuilds-vector-based-on-policy-data.md new file mode 100644 index 0000000000..0bba917372 --- /dev/null +++ b/docs/guardian/users/ai-search/ai-search-apis/rebuilds-vector-based-on-policy-data.md @@ -0,0 +1,11 @@ +# Rebuilds vector based on policy data + +{% swagger method="put" path="" baseUrl="/ai-suggestions/rebuild-vector" summary="Rebuilds vector based on policy data in the DB" %} +{% swagger-description %} +Rebuilds vector based on policy data in the DB +{% endswagger-description %} + +{% swagger-response status="200: OK" description="Successful Operation" %} + +{% endswagger-response %} +{% endswagger %} diff --git a/docs/guardian/users/ai-search/ai-search-apis/returns-response.md b/docs/guardian/users/ai-search/ai-search-apis/returns-response.md new file mode 100644 index 0000000000..eba3a79057 --- /dev/null +++ b/docs/guardian/users/ai-search/ai-search-apis/returns-response.md @@ -0,0 +1,26 @@ +# Returns response + +{% swagger method="get" path="" baseUrl="/ai-suggestions/ask" summary="Returns AI response to the current question" %} +{% swagger-description %} +Returns AI response to the current question +{% endswagger-description %} + +{% swagger-parameter in="path" name="q" type="String" required="true" %} +The question of choosing a methodology +{% endswagger-parameter %} + +{% swagger-response status="200: OK" description="Successful Operation" %} +``` +"string" +``` +{% endswagger-response %} + +{% swagger-response status="500: Internal Server Error" description="Internal Server Error" %} +``` +{ + "code": 0, + "message": "string" +} +``` +{% endswagger-response %} +{% endswagger %} diff --git a/docs/guardian/users/ai-search/ai-search-using-ui.md b/docs/guardian/users/ai-search/ai-search-using-ui.md new file mode 100644 index 0000000000..18a56b2386 --- /dev/null +++ b/docs/guardian/users/ai-search/ai-search-using-ui.md @@ -0,0 +1,51 @@ +# 💻 AI Search using UI + +## 1. Policy Fields + +Several new fields have been incorporated into policy formulation to enhance AI search capabilities. These fields include: + +1. Sectoral scope +2. Project Scale +3. Conditions for applicability +4. URL on policy details page +5. Typical projects, Description +6. Important parameters (at validation / monitored) +7. Applied Technology by Type +8. Mitigation Activity Type +9. Sub Type + +
+ +## 2. .env file parameters + +The .env file contains the following parameters: + +| Parameter | Meaning | +| --------------------- | ---------------------------------------------------------- | +| OPENAI\_API\_KEY | OpenAI API Key | +| GPT\_VERSION | GPT version; by default, it is set to 'gpt-3.5-turbo' | +| VECTOR\_STORAGE\_PATH | The path where vector will be stored | +| DOCS\_STORAGE\_PATH | The path where generated methodology files will be stored. | + +{% hint style="info" %} +These parameters are essential for configuring the AI Search tool. +{% endhint %} + +## 3. Vector Construction + +Vector construction is a pivotal process that involves compiling policy data and extracting descriptions from policy schemas. This process ensures the AI Search tool accurately interprets and utilizes policy-related information. + +Every time a user publishes a policy, the vector is rebuilt through the following step-by-step process: + +* Retrieving the required data from the newly added fields in the "create policy" modal window for each published policy from the database +* Retrieving the descriptions from the policy schemas and adding them to the resultant policy files. Descriptions containing fewer than 5 words are avoided to exclude unnecessary data for the language model. +* Creating separate files based on the fetched data, with each file containing the information describing one policy. Additionally, a file named metadata.txt is created, which contains shared data about all policies. +* Generating a new vector to replace the previous one. + +Once the vector is ready, standard registry users can utilize the AI search feature to find the most suitable methodology: + +
+ +Every response contains text and may include tiles with methodology data if the language model identifies relevant methodologies to suggest. Each tile comprises the policy name, a short description, and two links: as shown below: + +
diff --git a/docs/guardian/users/guided-search-of-methodologies/README.md b/docs/guardian/users/guided-search-of-methodologies/README.md new file mode 100644 index 0000000000..2366fdab09 --- /dev/null +++ b/docs/guardian/users/guided-search-of-methodologies/README.md @@ -0,0 +1,5 @@ +# 🔎 Guided Search of Methodologies + +Guided Search is a comprehensive feature designed as a complementary tool to AI Search, specifically tailored to streamline the process of finding methodologies. This tool distinguishes itself by enabling users to search methodologies based on categories and methodology names, offering a more directed and intuitive search experience. By utilizing Guided Search, users can navigate the complex landscape of methodologies with ease, ensuring that they find the most appropriate and relevant policies for their needs. + +
diff --git a/docs/guardian/users/guided-search-of-methodologies/search-apis/README.md b/docs/guardian/users/guided-search-of-methodologies/search-apis/README.md new file mode 100644 index 0000000000..985c5c8ff2 --- /dev/null +++ b/docs/guardian/users/guided-search-of-methodologies/search-apis/README.md @@ -0,0 +1,2 @@ +# ⚙ Search APIs + diff --git a/docs/guardian/users/guided-search-of-methodologies/search-apis/list-of-policies-that-are-best-suited-for-given-parameters.md b/docs/guardian/users/guided-search-of-methodologies/search-apis/list-of-policies-that-are-best-suited-for-given-parameters.md new file mode 100644 index 0000000000..e302bcbd64 --- /dev/null +++ b/docs/guardian/users/guided-search-of-methodologies/search-apis/list-of-policies-that-are-best-suited-for-given-parameters.md @@ -0,0 +1,32 @@ +# List of policies that are best suited for given parameters + +{% swagger method="post" path="" baseUrl="/policies/filtered-policies" summary="Get policies by categories and text" %} +{% swagger-description %} +Get policies by categories and text +{% endswagger-description %} + +{% swagger-parameter in="body" name="categoryIds" type="String" required="true" %} +Category Identifiers +{% endswagger-parameter %} + +{% swagger-parameter in="body" name="text" type="String" required="true" %} +Filter Text +{% endswagger-parameter %} + +{% swagger-response status="200: OK" description="Successful Operation" %} +``` +[ + "string" +] +``` +{% endswagger-response %} + +{% swagger-response status="500: Internal Server Error" description="Internal Server Error" %} +``` +{ + "code": 0, + "message": "string" +} +``` +{% endswagger-response %} +{% endswagger %} diff --git a/docs/guardian/users/guided-search-of-methodologies/search-apis/retrieves-list-of-all-categories.md b/docs/guardian/users/guided-search-of-methodologies/search-apis/retrieves-list-of-all-categories.md new file mode 100644 index 0000000000..945c1a4a64 --- /dev/null +++ b/docs/guardian/users/guided-search-of-methodologies/search-apis/retrieves-list-of-all-categories.md @@ -0,0 +1,24 @@ +# Retrieves list of all categories + +{% swagger method="get" path="" baseUrl="/policies/categories" summary="Get all categories" %} +{% swagger-description %} +Get all categories +{% endswagger-description %} + +{% swagger-response status="200: OK" description="Successful Operation" %} +``` +[ + "string" +] +``` +{% endswagger-response %} + +{% swagger-response status="500: Internal Server Error" description="Internal Server Error" %} +``` +{ + "code": 0, + "message": "string" +} +``` +{% endswagger-response %} +{% endswagger %} diff --git a/docs/guardian/users/guided-search-of-methodologies/search-using-ui.md b/docs/guardian/users/guided-search-of-methodologies/search-using-ui.md new file mode 100644 index 0000000000..366d375441 --- /dev/null +++ b/docs/guardian/users/guided-search-of-methodologies/search-using-ui.md @@ -0,0 +1,23 @@ +# 💻 Search using UI + +Guided Search can be performed by two ways: + +1. Category based search +2. Integration with Methodology name search + +## 1. Category-Based Search: + +* Within a single group of categories, Guided Search utilizes a logical 'OR' approach. This means that if a methodology falls under any one of the specified categories within the group, it will be included in the search results. +* Across different groups of categories, a logical 'AND' criterion is applied. In this scenario, only methodologies that meet all category criteria across different groups will be shown in the results. + +## 2. Integration with Methodology Name Search: + +* Guided Search further enhances its functionality by allowing the combination of category-based searches with specific methodology name searches. This dual-search capability enables users to refine their search results more precisely. + +
+ +## 3. Search Result: + +The result of using Guided Search is a tailored list of policies that align closely with the user's specified categories and methodology names. This makes the process of finding relevant methodologies both efficient and user-friendly. + +
diff --git a/docs/guardian/users/user-profile-setup.md b/docs/guardian/users/user-profile-setup.md index 436ecd7afd..eda8fc94b4 100644 --- a/docs/guardian/users/user-profile-setup.md +++ b/docs/guardian/users/user-profile-setup.md @@ -30,8 +30,6 @@ At this step the user needs to fill out the operator id and key fields or click
-\\ -
## User profile @@ -41,6 +39,3 @@ After submitting the wizard and successful account setup user will see the infor
- -\ -\\ diff --git a/docs/import/README.md b/docs/import/README.md new file mode 100644 index 0000000000..81fc424af1 --- /dev/null +++ b/docs/import/README.md @@ -0,0 +1,2 @@ +# Import + diff --git a/docs/import/policy-execution-record-and-replay.md b/docs/import/policy-execution-record-and-replay.md new file mode 100644 index 0000000000..0a8802f46a --- /dev/null +++ b/docs/import/policy-execution-record-and-replay.md @@ -0,0 +1,121 @@ +# Policy execution record and replay + +**Record** + +**“**Record/Run” options provide facilities to capture policy execution events, save and/or export them in a records file, which can then be replayed in the context of a different policy. + +Note: this functionality currently works in a Dry Run mode only. + +1. **Record** + +The recording starts on the press of the ‘Record’ button. + +Important note: it is strongly recommended to initiate recording at the very beginning of the policy execution, otherwise issues may be encountered at the ‘Replay’ stage. + +![image1.png](<../.gitbook/assets/0 (10).png>) + +* + 1. **Menu** + +![image2.png](<../.gitbook/assets/1 (11).png>) + +1. “Stop” - ends the recoding and downloads the capture file. + +![image3.png](<../.gitbook/assets/2 (14).png>) + +1. Actions shows/hides the list of recorded steps/events. + +![image4.png](<../.gitbook/assets/3 (10).png>) + +1. **Replay** + +Pressing the ‘Run’ button will initiate replay of the previously recorded file. + +![image5.png](<../.gitbook/assets/4 (9).png>) + +![image6.png](<../.gitbook/assets/5 (12).png>) + +* + 1. **Menu** + +![image7.png](<../.gitbook/assets/6 (11).png>) + +1. Fast Forward - quick transition to the next step (skipping the replay of the recorded pause). + +![image8.png](<../.gitbook/assets/7 (11).png>) + +1. Stop - ends the replay + +![image9.png](<../.gitbook/assets/8 (12).png>) + +1. Actions - shows/hides the list of replayed steps/events + +![image10.png](<../.gitbook/assets/9 (10).png>) + +* + 1. **Error** + +![image11.png](<../.gitbook/assets/10 (11).png>) + +In the case of an error the following actions are possible + +1. Retry - attempt to repeat the errored step/event + +![image12.png](<../.gitbook/assets/11 (9).png>) + +1. Skip - skip the errored step/event and execute the next one + +![image13.png](<../.gitbook/assets/12 (10).png>) + +* + 1. **Results** + +A summary dialogue is shown at the end of the replay. This dialogue contains the information about the tokens and document created during the policy execution, and the extend to which these artifacts are similar to those produced during the original execution (when the ‘records’ file was created). + +![image14.png](<../.gitbook/assets/13 (10).png>) + +The ‘details’ page shows detailed breakdown of differences between the corresponding documents or tokens. + +![image15.png](<../.gitbook/assets/14 (8).png>) + +1. **API** + +Get _/record/{policyId}/status_ – return current status for the selected policy (recording or replay is in progress) + +Post _/record/{policyId}/recording/start_ – begin the recording for the selected policy + +Post _/record/{policyId}/recording/stop_ – end the recording for the selected policy + +Get _/record/{policyId}/recording/actions_ – list of the recorded policies + +Post _/record/{policyId}/running/start_ – replay of the selected file for the selected policy + +Post _/record/{policyId}/running/stop_ – stop replay + +Get _/record/{policyId}/running/results_ – summary result of the replay + +Get _/record/{policyId}/running/details_ – detailed comparison of the replay and the original results + +Post _/record/{policyId}/running/fast-forward_ – quick transition to the next step/event + +Post _/record/{policyId}/running/retry_ – attempt to retry the step/event - to be used in the case of prior error + +Post _/record/{policyId}/running/skip_ – attempt to ‘step over’ (i.e. skip) the step/event - to be used in the case of prior error + +**Schemas** + +Changes in the Schemas menu + +![image16.png](<../.gitbook/assets/15 (10).png>) + +1. Example – the facility to add example values for the schema fields + +![image17.png](<../.gitbook/assets/16 (8).png>) + +1. Preview – show a preview of how users will see the policy form during the execution of the policy + +![image18.png](<../.gitbook/assets/17 (10).png>) + +In Dry Run mode it is possible to quickly fill in the fields using the provided example values from the schema. This feature is most useful for testing/demonstrations or for experimenting and learning Guardian capabilities. + +![image19.png](<../.gitbook/assets/18 (7).png>) diff --git a/frontend/angular.json b/frontend/angular.json index b5ebee9dbb..5d4bba42dc 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -27,7 +27,7 @@ "type": "initial" }, { - "maximumError": "30kb", + "maximumError": "35kb", "maximumWarning": "15kb", "type": "anyComponentStyle" } @@ -48,7 +48,7 @@ "type": "initial" }, { - "maximumError": "30kb", + "maximumError": "35kb", "maximumWarning": "15kb", "type": "anyComponentStyle" } diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 7a7d30dfe9..11c8ba6b62 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -34,6 +34,7 @@ import { ModulesListComponent } from './modules/policy-engine/modules-list/modul import { ToolsListComponent } from './modules/policy-engine/tools-list/tools-list.component'; import { SearchPoliciesComponent } from './modules/analytics/search-policies/search-policies.component'; import { AboutViewComponent } from './views/admin/about-view/about-view.component'; +import { RecordResultsComponent } from './modules/policy-engine/record/record-results/record-results.component'; const USER_IS_NOT_RA = "Page is avaliable for admin only"; @@ -187,6 +188,7 @@ const routes: Routes = [ { path: 'compare', component: CompareComponent, canActivate: [ServicesStatusGuard] }, { path: 'search', component: SearchPoliciesComponent, canActivate: [StandardRegistryGuard, ServicesStatusGuard] }, + { path: 'record-results', component: RecordResultsComponent, canActivate: [StandardRegistryGuard, ServicesStatusGuard] }, { path: 'branding', component: BrandingComponent, canActivate: [StandardRegistryGuard, ServicesStatusGuard] }, diff --git a/frontend/src/app/app.component.scss b/frontend/src/app/app.component.scss index 2c8d6f0119..5289b6f8a4 100644 --- a/frontend/src/app/app.component.scss +++ b/frontend/src/app/app.component.scss @@ -186,6 +186,15 @@ body { opacity: .0; } +::ng-deep body .record-tooltip { + word-wrap: normal; + word-break: normal; + white-space: pre; + min-width: 200px; + max-width: 400px; + font-size: 14px; +} + @media (max-width: 810px) { .app { diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index e4e5b9c3dd..986edb0c4e 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -30,13 +30,16 @@ export class AppComponent { const mapRequest = (value?: string) => { httpClient .jsonp( - `https://maps.googleapis.com/maps/api/js${value ? '?key=' + value : '' + `https://maps.googleapis.com/maps/api/js${ + value ? '?key=' + value : '' }`, 'callback' ) - .subscribe(); + .subscribe(() => { + this.mapService.mapLoaded = true; + }); }; - mapService.getApiKey().subscribe(mapRequest, () => mapRequest()); + mapService.getApiKey().subscribe(mapRequest); this.matIconRegistry.addSvgIconLiteral('policy-module', this.domSanitizer.bypassSecurityTrustHtml(` diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index ad26e363a8..d6572c58c2 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -75,6 +75,7 @@ import { MeecoVCSubmitDialogComponent } from './components/meeco-vc-submit-dialo import { AboutViewComponent } from './views/admin/about-view/about-view.component'; import { CompareStorage } from './services/compare-storage.service'; import { ToolsService } from './services/tools.service'; +import { RecordService } from './services/record.service'; @NgModule({ declarations: [ @@ -152,6 +153,7 @@ import { ToolsService } from './services/tools.service'; WizardService, SuggestionsService, NotificationService, + RecordService, CompareStorage, { provide: GET_SCHEMA_NAME, diff --git a/frontend/src/app/modules/analytics/analytics.module.ts b/frontend/src/app/modules/analytics/analytics.module.ts index 83f15b0cde..c279693c09 100644 --- a/frontend/src/app/modules/analytics/analytics.module.ts +++ b/frontend/src/app/modules/analytics/analytics.module.ts @@ -14,6 +14,7 @@ import { AppRoutingModule } from 'src/app/app-routing.module'; import { CompareDocumentComponent } from './compare-document/compare-document.component'; import { CompareComponent } from './compare/compare.component'; import { CompareToolComponent } from './compare-tool/compare-tool.component'; +import { CompareRecordComponent } from './compare-record/compare-record.component'; @NgModule({ declarations: [ @@ -25,7 +26,8 @@ import { CompareToolComponent } from './compare-tool/compare-tool.component'; SearchPoliciesComponent, SearchPolicyDialog, CompareDocumentComponent, - CompareToolComponent + CompareToolComponent, + CompareRecordComponent ], imports: [ CommonModule, @@ -44,7 +46,8 @@ import { CompareToolComponent } from './compare-tool/compare-tool.component'; SearchPoliciesComponent, SearchPolicyDialog, CompareDocumentComponent, - CompareToolComponent + CompareToolComponent, + CompareRecordComponent ] }) export class CompareModule { } diff --git a/frontend/src/app/modules/analytics/compare-document/compare-document.component.html b/frontend/src/app/modules/analytics/compare-document/compare-document.component.html index 0aeef3b7f7..433eed7543 100644 --- a/frontend/src/app/modules/analytics/compare-document/compare-document.component.html +++ b/frontend/src/app/modules/analytics/compare-document/compare-document.component.html @@ -41,7 +41,7 @@ -
Policy Blocks
+
Policy Documents
diff --git a/frontend/src/app/modules/analytics/compare-document/compare-document.component.ts b/frontend/src/app/modules/analytics/compare-document/compare-document.component.ts index b37d3d3f2f..1a7d9cce32 100644 --- a/frontend/src/app/modules/analytics/compare-document/compare-document.component.ts +++ b/frontend/src/app/modules/analytics/compare-document/compare-document.component.ts @@ -80,9 +80,6 @@ export class CompareDocumentComponent implements OnInit { private _pOffset = 30; constructor() { - } - - ngOnInit() { this.minWidth = 1600; this.headers = []; this.size = 0; @@ -91,6 +88,10 @@ export class CompareDocumentComponent implements OnInit { this.treeContext = null; } + ngOnInit() { + + } + ngOnChanges(changes: SimpleChanges): void { if (this.value) { this.onInit(); diff --git a/frontend/src/app/modules/analytics/compare-record/compare-record.component.html b/frontend/src/app/modules/analytics/compare-record/compare-record.component.html new file mode 100644 index 0000000000..faf5f84c2b --- /dev/null +++ b/frontend/src/app/modules/analytics/compare-record/compare-record.component.html @@ -0,0 +1,304 @@ +
+
+ Documents are {{totalRate}}% the same. +
+ +
+
+ + {{item.name}} + + + {{item.rate}} + +
+
+ +
+ + + + +
Description
+
+
+
+ +
+
+
+ +
+
+
+
+ + + +
Policy Documents
+
+ +
+
+
+
+
+ Documents are equal. +
+
+
+
+
+ Documents are equal except for dynamic fields. +
+
+
+
+
+ Documents are of the same type and are partially equal. +
+
+
+
+
+ Documents are absent in the other Policy. +
+
+
+ +
+ +
+ +
{{row.number}}
+
+ remove + add +
+ + +
+
+
+ {{treeItemContext.data.docIndex}} +
+
+ {{treeItemContext.data.docIcon}} +
+
{{treeItemContext.data.docName}}
+
+
+ +
+
+ unfold_more + unfold_less +
+
+
+ + + +
+ +
+
+
+
+
+
+ +
+
+ + + + {{ column.label }} + + + + {{ row.data[column.name] }} + + + + + + +
+
+ +
+
+
+
+
+ + +
+
+
Documents
+
{{data.documents}}
+
+
+
Tokens
+
{{data.tokens}}
+
+ +
+
+ + +
+ +
+
Document Rate:
+
{{data.documentRate}}
+
+ +
+ + + + + +
+
Type:
+
{{data.type}}
+
+ +
+
Schema:
+
{{data.schema}}
+
+ + + +
+ +
+
+ Documents: +
+ unfold_more + unfold_less +
+
+
+
+
+ {{field.label}}: +
+
+
+ search +
+ + {{field.value}} + +
+
+
+
+ +
+ + +
+
\ No newline at end of file diff --git a/frontend/src/app/modules/analytics/compare-record/compare-record.component.scss b/frontend/src/app/modules/analytics/compare-record/compare-record.component.scss new file mode 100644 index 0000000000..53214049e2 --- /dev/null +++ b/frontend/src/app/modules/analytics/compare-record/compare-record.component.scss @@ -0,0 +1,898 @@ +.content { + display: block; + position: relative; +} + +.info-container { + display: grid; + grid-template-columns: calc(50% - 18px) 35px calc(50% - 18px); + padding-left: 30px; +} + +.total-info { + padding: 10px 22px; + font-weight: bold; + font-size: 20px; + margin-bottom: 5px; + color: #686868; + + span { + color: #e70000; + } + + &[total="100"] span { + color: #008d0c; + } +} + +.header { + font-weight: bold; + color: #3f51b5; + font-size: 20px; +} + +.item-headers { + display: grid; + position: sticky; + z-index: 4; + left: 0; + top: 0; + padding-left: 53px; + padding-right: 33px; + height: 22px; + background: #fff; + opacity: 0.95; + border-bottom: 1px solid #eee; + overflow: hidden; + + .item-header { + border-left: 1px solid #bbb; + border-right: 1px solid #bbb; + font-size: 12px; + color: #444; + padding-left: 12px; + padding-right: 100px; + overflow: hidden; + position: relative; + margin-right: -2px; + } + + .item-header-rate { + top: 0; + right: 16px; + position: absolute; + font-weight: 500; + text-align: end; + } + + .item-header-color-green { + color: #008d0c; + } + + .item-header-color-yellow { + color: #c79604; + } + + .item-header-color-red { + color: #e70000; + } + + .item-header-name { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + width: 100%; + display: block; + } +} + +.compare-btn { + width: 25px; + height: 25px; + border-radius: 6px; + background: rgb(252 252 252); + border: 1px solid rgb(220 220 220); + position: absolute; + top: 14px; + left: 3px; + cursor: pointer; + + mat-icon { + font-size: 22px; + position: absolute; + left: 2px; + top: 1px; + } +} + +.left-compare-result { + grid-column-start: 1; + grid-row-start: 2; + grid-row-end: 3; + padding-top: 10px; + padding-bottom: 10px; + border-left: 12px solid #fff; +} + +.right-compare-result { + grid-column-start: 3; + grid-row-start: 2; + grid-row-end: 3; + padding-top: 10px; + padding-bottom: 10px; + border-left: 12px solid #fff; +} + +.middle-compare-result { + grid-column-start: 2; + grid-row-start: 2; + grid-row-end: 3; + border-left: 1px solid #9d9d9d; + border-right: 1px solid #9d9d9d; + background: #fff; + width: 35px; + box-sizing: border-box; +} + +.compare-result { + display: none; + position: relative; + min-height: 100px; + background: #fff; + + &[open="true"] { + display: block; + } +} + +.block-tree { + background: #fff; + border-radius: 6px; + padding: 7px 16px 7px 32px; + margin: 12px 8px 0px 2px; + cursor: default; + position: relative; + height: 35px; + -webkit-user-select: none; + user-select: none; + min-width: 125px; + display: inline-block; + box-sizing: border-box; + border: 1px solid #8b8b8b; + box-shadow: 1px 1px 3px 0px rgb(0 0 0 / 20%); + cursor: pointer; + max-width: 400px; + + .block-index { + position: absolute; + left: -12px; + top: 6px; + font-weight: bold; + color: #686868; + transform: translate(-100%, 0px); + + &[index-rate] { + color: #e70000; + } + + &[index-rate][index-rate="-1"] { + color: #686868; + } + + &[index-rate][index-rate="100%"] { + color: #008d0c; + } + } + + .block-icon { + position: absolute; + left: 9px; + top: 8px; + display: block; + font-size: 16px; + width: 18px; + height: 16px; + line-height: 16px; + overflow: hidden; + + mat-icon { + font-size: 16px; + height: 16px; + width: 16px; + } + } + + .block-type { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } +} + + +.result-info { + padding-left: 5px; + + .result-info-field { + display: grid; + grid-template-columns: 100px 50%; + } + + .result-info-field-name { + font-weight: bold; + color: #686868; + } +} + +.prop-container { + .prop { + display: grid; + grid-template-columns: 140px auto; + margin-bottom: 5px; + margin-left: 5px; + padding-left: 5px; + border-left: 1px solid #bbb; + padding-right: 10px; + + .prop-name { + font-weight: bold; + color: #686868; + } + + .prop-value { + text-overflow: ellipsis; + overflow: hidden; + width: 100%; + + &[rate] { + font-weight: bold; + color: #e70000; + } + + &[rate="100%"] { + font-weight: bold; + color: #008d0c; + } + } + } + + .prop-table { + margin-left: 5px; + padding-left: 5px; + border-left: 1px solid #bbb; + + .prop-table-header { + font-weight: bold; + color: #686868; + position: relative; + height: 26px; + + .prop-table-system-btn { + position: absolute; + top: 0px; + right: 11px; + cursor: pointer; + width: 24px; + height: 24px; + overflow: hidden; + border-radius: 4px; + + &:hover { + background: #f1f1f1; + } + } + } + + .prop-table-body { + display: table; + width: 100%; + box-sizing: border-box; + padding-right: 10px; + + .prop-table-row { + display: table-row; + + &[type="FULL"] { + background: #d3ffd5; + } + + &[type="PARTLY"] { + background: #ffface; + } + + &[type="NONE"] { + background: #f3f3f3; + } + + &[type="LEFT"], + &[type="RIGHT"] { + background: #ffe1e1; + } + + &[fantom="true"] { + opacity: 0.2; + } + + .prop-table-name { + display: table-cell; + font-weight: bold; + color: #686868; + border-top: 2px solid #fff; + padding-top: 2px; + padding-bottom: 2px; + box-sizing: border-box; + min-width: 175px; + height: 30px; + } + + .prop-table-value { + display: table-cell; + border-top: 2px solid #fff; + padding-top: 4px; + padding-bottom: 4px; + padding-right: 36px; + padding-left: 8px; + min-width: 240px; + position: relative; + + .compare-btn { + display: none; + position: absolute; + top: 0px; + right: 1px; + left: inherit; + bottom: inherit; + box-shadow: 0px 0px 0px 2px #fff; + } + + &[prop-type="schema"] .compare-btn { + display: block; + } + + // & *[prop-type="array"], + // & *[prop-type="object"] { + // display: none; + // } + + span { + position: absolute; + overflow: hidden; + white-space: nowrap; + top: 4px; + bottom: 4px; + right: 36px; + left: 8px; + text-overflow: ellipsis; + } + } + } + } + + &[system="false"] { + .prop-table-row[system="true"] { + display: none !important; + } + } + } + + .prop-delimiter { + border-top: 1px solid #999; + height: 12px; + width: 100px; + margin-top: 10px; + margin-left: 10px; + } +} + +.legend { + display: flex; + width: 100%; + background: #fff; + padding-top: 10px; + padding-bottom: 10px; + padding-left: 10px; + box-sizing: border-box; + + .legend-item { + display: flex; + position: relative; + padding-left: 30px; + padding-right: 50px; + background: white; + cursor: pointer; + + .legend-icon { + width: 20px; + height: 20px; + position: absolute; + top: 50%; + left: 0; + transform: translate(0px, -11px); + background: #fff; + border: 1px solid #fff; + border-radius: 50%; + box-sizing: border-box; + + &.legend-l1 { + background: #a2ffa5; + border: 1px solid #408142; + } + + &.legend-l2 { + background: linear-gradient(45deg, #a2ffa5 48%, #fff6ab 52%); + border: 1px solid #648d36; + } + + &.legend-l3 { + background: #fff6ab; + border: 1px solid #ab9f3e; + } + + &.legend-l4 { + background: #ffb5b7; + border: 1px solid #b15656; + } + } + + .legend-text { + max-width: 350px; + display: flex; + align-items: center; + } + } +} + +*[type-1="false"] .left-tree[type="FULL"][rate="100%"].hidden-item, +*[type-1="false"] .right-tree[type="FULL"][rate="100%"].hidden-item { + opacity: 0.15; +} + +*[type-2="false"] .left-tree[type="PARTLY"][rate="100%"].hidden-item, +*[type-2="false"] .right-tree[type="PARTLY"][rate="100%"].hidden-item { + opacity: 0.15; +} + +*[type-3="false"] .left-tree[type="FULL"]:not([rate="100%"]).hidden-item, +*[type-3="false"] .left-tree[type="PARTLY"]:not([rate="100%"]).hidden-item, +*[type-3="false"] .right-tree[type="FULL"]:not([rate="100%"]).hidden-item, +*[type-3="false"] .right-tree[type="PARTLY"]:not([rate="100%"]).hidden-item { + opacity: 0.15; +} + +*[type-4="false"] .left-tree[type="LEFT_AND_RIGHT"].hidden-item, +*[type-4="false"] .left-tree[type="LEFT"].hidden-item, +*[type-4="false"] .left-tree[type="RIGHT"].hidden-item, +*[type-4="false"] .right-tree[type="LEFT_AND_RIGHT"].hidden-item, +*[type-4="false"] .right-tree[type="LEFT"].hidden-item, +*[type-4="false"] .right-tree[type="RIGHT"].hidden-item { + opacity: 0.15; +} + +*[type-1="false"] .legend-l1.legend-icon { + background: linear-gradient(135deg, #f1f1f1 48%, #ababab 50%, #f1f1f1 52%) !important; +} + +*[type-2="false"] .legend-l2.legend-icon { + background: linear-gradient(135deg, #f1f1f1 48%, #ababab 50%, #f1f1f1 52%) !important; +} + +*[type-3="false"] .legend-l3.legend-icon { + background: linear-gradient(135deg, #f1f1f1 48%, #ababab 50%, #f1f1f1 52%) !important; +} + +*[type-4="false"] .legend-l4.legend-icon { + background: linear-gradient(135deg, #f1f1f1 48%, #ababab 50%, #f1f1f1 52%) !important; +} + +.merge-tree { + width: 100%; + min-height: 100px; + background: #ffffff; + background-size: 25px 25px; + background-image: radial-gradient(circle, #bbb 0px, #fff 2px); + overflow: auto; + transition: height 225ms cubic-bezier(0.4, 0, 0.2, 1); + + .report-item { + display: grid; + grid-template-columns: calc(50% - 18px) 35px calc(50% - 18px); + grid-template-rows: 48px auto; + position: relative; + padding-left: 30px; + + &[hidden="true"] { + display: none; + } + + &:hover .hidden-item { + opacity: 1 !important; + } + + &:hover { + background-color: rgb(229 229 229 / 50%); + box-shadow: 0px 11px 0px 0px rgb(229 229 229 / 50%); + } + + .report-number { + position: absolute; + left: 0px; + top: 18px; + width: 30px; + padding-right: 4px; + box-sizing: border-box; + color: #898989; + overflow: hidden; + pointer-events: none; + } + + .report-collapse { + position: absolute; + left: 21px; + top: 19px; + padding-right: 4px; + box-sizing: border-box; + color: #898989; + overflow: hidden; + z-index: 2; + width: 16px; + height: 16px; + border: 1px solid #898989; + background: #fff; + cursor: pointer; + display: none; + + mat-icon { + position: absolute; + pointer-events: none; + left: -1px; + top: -1px; + font-size: 16px; + display: none; + } + + &[collapse="1"], + &[collapse="2"] { + display: block; + } + + &[collapse="1"] .open-icon { + display: block; + } + + &[collapse="2"] .collapse-icon { + display: block; + } + } + } + + .report-item-number { + padding-left: 30px; + grid-template-columns: calc(50% - 33px) 35px calc(50% - 18px); + background: linear-gradient(90deg, #ffffff 29px, #b5b5b5 29px, #b5b5b5 30px, transparent 30px); + } + + + .left-tree { + grid-row-start: 1; + grid-column-start: 1; + position: relative; + overflow: hidden; + } + + .middle-tree { + grid-row-start: 1; + grid-column-start: 2; + position: relative; + width: 35px; + box-sizing: border-box; + overflow: visible; + border-left: 1px solid #9d9d9d; + border-right: 1px solid #9d9d9d; + background: #fff; + } + + .right-tree { + grid-row-start: 1; + grid-column-start: 3; + position: relative; + overflow: hidden; + } + + .left-tree[type="FULL"] .block-tree, + .right-tree[type="FULL"] .block-tree { + background: #a2ffa5; + } + + .left-tree[type="FULL"]:not([rate="100%"]) .block-tree, + .right-tree[type="FULL"]:not([rate="100%"]) .block-tree { + background: #fff6ab; + } + + .left-tree[type="PARTLY"] .block-tree, + .right-tree[type="PARTLY"] .block-tree { + background: #fff6ab; + } + + .left-tree[type="LEFT"] .block-tree, + .left-tree[type="RIGHT"] .block-tree, + .left-tree[type="LEFT_AND_RIGHT"] .block-tree, + .right-tree[type="LEFT"] .block-tree, + .right-tree[type="RIGHT"] .block-tree, + .right-tree[type="LEFT_AND_RIGHT"] .block-tree { + background: #ffb5b7; + } + + .left-tree[type="PARTLY"][rate="100%"] .block-tree, + .right-tree[type="PARTLY"][rate="100%"] .block-tree { + background: linear-gradient(45deg, #a2ffa5 48%, #fff6ab 52%); + } + + &:not([tree-size="2"]) { + .left-tree .block-tree { + background: #fff !important; + } + + .left-compare-result .prop-table-row { + background: #ffffff !important; + } + + .left-compare-result .prop-value { + color: rgba(0, 0, 0, .87) !important; + } + } + + .fantom-tree { + .block-tree { + border-color: rgb(0 0 0 / 10%) !important; + color: rgb(0 0 0 / 10%) !important; + background: #fffafa !important; + pointer-events: none !important; + box-shadow: none !important; + + .block-index { + color: rgb(0 0 0 / 10%) !important; + } + } + } + + .fantom-block * { + display: none !important; + } +} + +.report-item-small { + display: grid; + grid-template-columns: calc(50% - 18px) 35px calc(50% - 18px); + grid-template-rows: 0px auto; + position: relative; + padding-left: 30px; + min-height: 100px; + background: white !important; + + .compare-result { + min-height: auto !important; + } +} + +.merge-table { + transition: height 225ms cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + + .merge-table-content table { + min-width: 100%; + } + + .merge-table-content::ng-deep table td { + font-size: 16px; + line-height: 16px; + padding: 0 0px; + } + + .merge-table-content::ng-deep table tr { + height: 48px; + } + + .merge-table-content::ng-deep table td:first-of-type { + padding-left: 24px; + } + + .merge-table-content::ng-deep mat-row[type="FULL"]:not([rate="100%"]) { + background: #fffada; + } + + .merge-table-content::ng-deep mat-row[type="FULL"] { + background: #d0ffd6; + } + + .merge-table-content::ng-deep mat-row[type="PARTLY"] { + background: #fffada; + } + + .merge-table-content::ng-deep mat-row[type="LEFT"], + .merge-table-content::ng-deep mat-row[type="RIGHT"] { + background: #ffc5c6; + } + + .merge-table-content::ng-deep mat-row[type="PARTLY"][rate="100%"] { + background: linear-gradient(45deg, #d0ffd6 48%, #fffada 52%); + } + + .cdk-column-lvl { + max-width: 60px; + } + + .cdk-column-left_index { + max-width: 60px; + } + + .cdk-column-right_index { + max-width: 60px; + } + + .cdk-column-permission_rate { + max-width: 105px; + } + + .cdk-column-order_rate, + .cdk-column-index_rate, + .cdk-column-prop_rate, + .cdk-column-event_rate, + .cdk-column-artifacts_rate, + .cdk-column-total_rate { + max-width: 90px; + } + + + .table-value { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + width: 100%; + padding-right: 6px; + } + + .table-value[display-type="Rate"] { + height: 48px; + display: flex; + justify-content: center; + align-items: center; + box-sizing: border-box; + border: 1px solid white; + + &[value="100%"] { + background: #a2ffa545; + } + + &[value="0%"], + &[value="1%"], + &[value="2%"], + &[value="3%"], + &[value="4%"], + &[value="5%"], + &[value="6%"], + &[value="7%"], + &[value="8%"], + &[value="9%"], + &[value="10%"], + &[value="11%"], + &[value="12%"], + &[value="13%"], + &[value="14%"], + &[value="15%"], + &[value="16%"], + &[value="17%"], + &[value="18%"], + &[value="19%"], + &[value="20%"], + &[value="21%"], + &[value="22%"], + &[value="23%"], + &[value="24%"], + &[value="25%"], + &[value="26%"], + &[value="27%"], + &[value="28%"], + &[value="29%"], + &[value="30%"], + &[value="31%"], + &[value="32%"], + &[value="33%"], + &[value="34%"], + &[value="35%"], + &[value="36%"], + &[value="37%"], + &[value="38%"], + &[value="39%"], + &[value="40%"], + &[value="41%"], + &[value="42%"], + &[value="43%"], + &[value="44%"], + &[value="45%"], + &[value="46%"], + &[value="47%"], + &[value="48%"], + &[value="49%"], + &[value="50%"] { + background: #ffb5b7; + } + + &[value="51%"], + &[value="52%"], + &[value="53%"], + &[value="54%"], + &[value="55%"], + &[value="56%"], + &[value="57%"], + &[value="58%"], + &[value="59%"], + &[value="60%"], + &[value="61%"], + &[value="62%"], + &[value="63%"], + &[value="64%"], + &[value="65%"], + &[value="66%"], + &[value="67%"], + &[value="68%"], + &[value="69%"], + &[value="70%"], + &[value="71%"], + &[value="72%"], + &[value="73%"], + &[value="74%"], + &[value="75%"], + &[value="76%"], + &[value="77%"], + &[value="78%"], + &[value="79%"], + &[value="80%"], + &[value="81%"], + &[value="82%"], + &[value="83%"], + &[value="84%"], + &[value="85%"], + &[value="86%"], + &[value="87%"], + &[value="88%"], + &[value="89%"], + &[value="90%"], + &[value="91%"], + &[value="92%"], + &[value="93%"], + &[value="94%"], + &[value="95%"], + &[value="96%"], + &[value="97%"], + &[value="98%"], + &[value="99%"] { + background: #fff6ab87; + } + + &[value="-"] { + background: #ffb5b733; + } + } +} + +.mat-expansion-panel-header.mat-expanded { + height: 40px; +} + +.mat-expansion-panel-spacing { + margin: 8px 0; +} + +.mat-expansion-panel-header { + height: 40px; +} + +.scroll { + margin-left: 30px; + height: 16px; + overflow-x: auto; + + div { + height: 1px; + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/analytics/compare-record/compare-record.component.ts b/frontend/src/app/modules/analytics/compare-record/compare-record.component.ts new file mode 100644 index 0000000000..f2ef24d0ae --- /dev/null +++ b/frontend/src/app/modules/analytics/compare-record/compare-record.component.ts @@ -0,0 +1,487 @@ +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; +import { IResultContext } from '../interfaces/result-context.interface'; +import { ITreeContext } from '../interfaces/tree-context.interface'; +import { ITableColumns } from '../interfaces/table-columns.interface'; +import { ITreeItemContext } from '../interfaces/tree-item-context.interface'; + +interface IInfoContext { + documents: string; + tokens: string; +} + +interface IDocumentContext { + docIndex: string; + docIcon: string; + docName: string; +} + +interface IDocumentDetailsContext { + index: number; + id: string; + messageId: string; + type: string; + schema: string; + optionsRate: string; + documentRate: string; + owner: string; + totalRate: string; + fields: IFieldContext[]; + attributes: IFieldContext[]; +} + +interface IFieldContext { + fantom: boolean; + type: string; + lvl: number; + offset: number; + name: string; + propType: string; + value: string; + label: string; + system: boolean; + parent?: IFieldContext; +} + +@Component({ + selector: 'app-compare-record', + templateUrl: './compare-record.component.html', + styleUrls: ['./compare-record.component.scss'] +}) +export class CompareRecordComponent implements OnInit { + @Input('value') value!: any; + @Input() type: string = 'tree'; + @Input() eventsLvl: string = '1'; + @Input() propLvl: string = '2'; + @Input() childrenLvl: string = '2'; + @Input() idLvl: string = '1'; + + @Output() change = new EventEmitter(); + + public minWidth: number; + public headers: any[]; + + public size: number; + public totals: number[]; + public resultContext: IResultContext[] | null; + public treeContext: ITreeContext[] | null; + public totalRate: number; + + + public columns: ITableColumns[] = []; + public displayedColumns: string[] = []; + + public _panelOpenState = true; + public _type1 = true; + public _type2 = true; + public _type3 = true; + public _type4 = true; + public _gridStyle = ''; + public _systemProp = true; + private _pOffset = 30; + + constructor() { + this.minWidth = 1600; + this.headers = []; + this.size = 0; + this.totals = []; + this.resultContext = null; + this.treeContext = null; + } + + ngOnInit() { + } + + ngOnChanges(changes: SimpleChanges): void { + if (this.value) { + this.onInit(); + } + } + + onInit() { + this.size = this.value.size || 2; + this.totals = this.value.totals; + this.minWidth = 770 * this.size; + + const k = Math.round(100 / this.size); + this._gridStyle = `max(calc(${k}vw - 70px), 690px)`; + for (let i = 1; i < this.size; i++) { + this._gridStyle += ` 35px max(calc(${k}vw - 55px), 710px)`; + } + + this.totalRate = this.value?.total; + this.createHeaders(this.value); + this.resultContext = this.createResultContext(this.value); + this.treeContext = this.createTreeContext(this.value.documents?.report); + + this.columns = this.value.documents?.columns || []; + this.displayedColumns = this.columns + .filter(c => c.label) + .map(c => c.name); + } + + private createHeaders(data: any): void { + if (this.size > 2) { + this.headers = []; + this.headers.push({ + column: 0, + name: data.left.name, + color: 'none', + rate: '' + }); + for (let i = 0; i < data.rights.length; i++) { + const rate = data.totals[i]; + const right = data.rights[i]; + let color = 'none'; + if (rate > 80) { + color = 'green'; + } else if (rate > 50) { + color = 'yellow'; + } else { + color = 'red'; + } + this.headers.push({ + column: 2 * this.headers.length + 1, + name: right.name, + color: color, + rate: `${rate}%` + }); + } + } else { + this.headers = []; + } + } + + private createResultContext(data: any): IResultContext[] | null { + const results: IResultContext[] = new Array(this.size); + if (this.size > 2) { + for (let i = 0; i < this.size; i++) { + if (i === 0) { + results[i] = { + left: true, + right: false, + index: i, + data: data.left + } + } else { + results[i] = { + left: false, + right: true, + index: i, + data: data.rights[i - 1] + } + } + } + } else { + results[0] = { + left: true, + right: false, + index: 0, + data: data.left + } + results[1] = { + left: false, + right: true, + index: 1, + data: data.right + } + } + return results; + } + + private createTreeContext(documents?: any[]): ITreeContext[] | null { + if (!documents) { + return null; + } + + let max = 0; + const results: ITreeContext[] = []; + + for (let i = 1; i < documents.length; i++) { + const currentDoc = documents[i]; + const nextDoc = documents[i + 1]; + const contexts = this.createTreeItemContext(currentDoc); + const detailContexts = this.createTreeDetailsContext(currentDoc); + const item: ITreeContext = { + index: i - 1, + number: i, + lvl: currentDoc.lvl, + hidden: false, + collapse: (currentDoc && nextDoc && nextDoc.lvl > currentDoc.lvl) ? 1 : 0, + open: false, + offset: 0, + contexts, + detailContexts, + data: currentDoc + } + results.push(item); + max = Math.max(max, currentDoc.lvl); + } + + if (max > 10) { + this._pOffset = 20; + } + if (max > 15) { + this._pOffset = 15; + } + for (const item of results) { + item.offset = this._pOffset * item.lvl; + } + return results; + } + + private createTreeItemContext(row: any): ITreeItemContext[] { + const contexts: ITreeItemContext[] = new Array(this.size); + for (let j = 0; j < this.size; j++) { + const data: IDocumentContext = this.createDocumentContext(row, j); + const context: ITreeItemContext = { + type: '', + key: '', + rate: -1, + left: j === 0, + right: j !== 0, + index: j, + fantom: true, + data + } + this.setDocumentContextKey(row, j, context); + contexts[j] = context; + } + const first = contexts.find(c => !c.fantom); + if (first) { + for (const context of contexts) { + if (context.fantom) { + context.data = first.data; + } + } + } + return contexts; + } + + private createTreeDetailsContext(row: any): ITreeItemContext[] { + const contexts: ITreeItemContext[] = new Array(this.size); + for (let j = 0; j < this.size; j++) { + const data: IDocumentDetailsContext = this.createDetailsContext(row, j); + const context: ITreeItemContext = { + type: '', + key: '', + rate: -1, + left: j === 0, + right: j !== 0, + index: j, + fantom: true, + data + } + this.setDocumentContextKey(row, j, context); + contexts[j] = context; + } + return contexts; + } + + private setDocumentContextKey(row: any, index: number, context: ITreeItemContext): void { + if (index === 0) { + context.type = row[`type`]; + context.rate = row[`total_rate`]; + context.key = row['left_type']; + } else { + if (this.size === 2) { + context.type = row[`type`]; + context.rate = row[`total_rate`]; + context.key = row[`right_type`]; + } else { + context.type = row[`type_${index}`]; + context.rate = row[`total_rate_${index}`]; + context.key = row[`right_type_${index}`]; + } + } + context.fantom = !context.key; + } + + private createDocumentContext(row: any, index: number): IDocumentContext { + if (index === 0) { + return { + docIndex: '', + docIcon: 'description', + docName: row['left_schema'], + } + } else { + if (this.size === 2) { + return { + docIndex: '', + docIcon: 'description', + docName: row[`right_schema`], + } + } else { + return { + docIndex: '', + docIcon: 'description', + docName: row[`right_schema_${index}`], + } + } + } + } + + private createFieldContext(data: any, index: number): IFieldContext { + const fieldContext: IFieldContext = { + fantom: true, + type: index === 0 ? 'RIGHT' : 'LEFT', + lvl: 0, + offset: 0, + name: '', + propType: '', + value: '', + label: '', + system: false + } + let item: any; + let field: any; + let items: any[]; + if (this.size === 2) { + field = data; + item = data.items[index]; + items = data.items; + } else { + field = data[index]; + item = field?.item; + items = data.map((f: any) => f?.item); + } + if (field && item) { + fieldContext.fantom = false; + fieldContext.type = field.type; + fieldContext.lvl = item.lvl; + fieldContext.offset = 10 * item.lvl; + fieldContext.name = item.name; + fieldContext.propType = item.type; + fieldContext.value = item.value; + fieldContext.label = item.title || item.name; + if (fieldContext.propType === 'array') { + fieldContext.value = '[...]'; + } + if (fieldContext.propType === 'object') { + fieldContext.value = '{...}'; + } + } + if (fieldContext.fantom) { + const fantom = items.find((i: any) => i); + if (fantom) { + fieldContext.lvl = fantom.lvl; + fieldContext.offset = 10 * fantom.lvl; + fieldContext.name = fantom.name; + fieldContext.propType = fantom.type; + } + } + return fieldContext; + } + + private createDetailsContext(row: any, index: number): IDocumentDetailsContext { + const fieldContexts: IFieldContext[] = []; + for (const documentField of row.documents) { + const fieldContext = this.createFieldContext(documentField, index); + fieldContexts.push(fieldContext); + } + let parents = new Map(); + parents.set(1, undefined); + for (const item of fieldContexts) { + item.parent = parents.get(item.lvl); + parents.set(item.lvl + 1, item); + + item.system = + item.name === '@context' || + item.name === 'type' || + (!!item.parent && item.parent.system); + } + const attributeContexts: IFieldContext[] = []; + for (const option of row.options) { + const attributeContext = this.createFieldContext(option, index); + attributeContexts.push(attributeContext); + } + if (index === 0) { + return { + index, + id: row['left_id'], + messageId: row['left_message_id'], + type: row['left_type'], + schema: row['left_schema'], + owner: row['left_owner'], + optionsRate: row['options_rate'], + documentRate: row['document_rate'], + totalRate: row['total_rate'], + fields: fieldContexts, + attributes: attributeContexts + } + } else { + if (this.size === 2) { + return { + index, + id: row['right_id'], + messageId: row['right_message_id'], + type: row['right_type'], + schema: row['right_schema'], + owner: row['right_owner'], + optionsRate: row['options_rate'], + documentRate: row['document_rate'], + totalRate: row['total_rate'], + fields: fieldContexts, + attributes: attributeContexts + } + } else { + return { + index, + id: row[`right_id_${index}`], + messageId: row[`right_message_id_${index}`], + type: row[`right_type_${index}`], + schema: row[`right_schema_${index}`], + owner: row[`right_owner_${index}`], + optionsRate: row[`options_rate_${index}`], + documentRate: row[`document_rate_${index}`], + totalRate: row[`total_rate_${index}`], + fields: fieldContexts, + attributes: attributeContexts + } + } + } + } + + public _toInfoContext(data: any): IInfoContext { + return data; + } + + public _toDetailsContext(data: any): IDocumentDetailsContext { + return data; + } + + public compareSchema(prop: any) { + const schema1 = prop?.items[0]; + const schema2 = prop?.items[1]; + this.change.emit({ + type: 'schema', + schemaId1: schema1?.schemaId, + schemaId2: schema2?.schemaId + }) + } + + public onCollapse(item: ITreeContext): void { + if (!this.treeContext) { + return; + } + const hidden = item.collapse == 1; + if (hidden) { + item.collapse = 2; + } else { + item.collapse = 1; + } + for (let i = item.index + 1; i < this.treeContext.length; i++) { + const item2 = this.treeContext[i]; + if (item2.lvl > item.lvl) { + item2.hidden = hidden; + } else { + break; + } + } + } + + public onScroll(event: any) { + document.querySelectorAll('.left-tree').forEach(el => { + el.scrollLeft = event.target.scrollLeft; + }) + } +} diff --git a/frontend/src/app/modules/analytics/compare-tool/compare-tool.component.ts b/frontend/src/app/modules/analytics/compare-tool/compare-tool.component.ts index bd0b06aecf..3baa89e628 100644 --- a/frontend/src/app/modules/analytics/compare-tool/compare-tool.component.ts +++ b/frontend/src/app/modules/analytics/compare-tool/compare-tool.component.ts @@ -291,7 +291,6 @@ export class CompareToolComponent implements OnInit { propertiesContext.lvl = propertiesContext.item.lvl; propertiesContext.offset = 10 * propertiesContext.lvl; } - debugger; propertiesContexts.push(propertiesContext); } const variableContext: IResultContext = { diff --git a/frontend/src/app/modules/common/confirm-dialog/confirm-dialog.component.html b/frontend/src/app/modules/common/confirm-dialog/confirm-dialog.component.html index 53a19a4861..4a96c0ee2e 100644 --- a/frontend/src/app/modules/common/confirm-dialog/confirm-dialog.component.html +++ b/frontend/src/app/modules/common/confirm-dialog/confirm-dialog.component.html @@ -9,7 +9,7 @@

{{this.title}}

- - + +
\ No newline at end of file diff --git a/frontend/src/app/modules/common/confirm-dialog/confirm-dialog.component.ts b/frontend/src/app/modules/common/confirm-dialog/confirm-dialog.component.ts index a889af12e5..695fe80a81 100644 --- a/frontend/src/app/modules/common/confirm-dialog/confirm-dialog.component.ts +++ b/frontend/src/app/modules/common/confirm-dialog/confirm-dialog.component.ts @@ -1,5 +1,4 @@ import { Component, Inject } from '@angular/core'; -import { FormControl, Validators } from '@angular/forms'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; /** @@ -10,17 +9,21 @@ import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; styleUrls: ['./confirm-dialog.component.css'] }) export class ConfirmDialog { - title: string = ""; - description?: string; - descriptions?: string[]; + public title: string = ''; + public description?: string; + public descriptions?: string[]; + public submitButton: string = 'Ok'; + public cancelButton: string = 'Cancel'; constructor( public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any ) { this.title = data.title; - if(Array.isArray(data.description)) { - this.descriptions = data.description; + this.submitButton = data.submitButton || 'Ok'; + this.cancelButton = data.cancelButton || 'Cancel'; + if (Array.isArray(data.description)) { + this.descriptions = data.description; } else { this.description = data.description; } diff --git a/frontend/src/app/modules/policy-engine/directives/resizing.directive.ts b/frontend/src/app/modules/policy-engine/directives/resizing.directive.ts new file mode 100644 index 0000000000..ffed421e31 --- /dev/null +++ b/frontend/src/app/modules/policy-engine/directives/resizing.directive.ts @@ -0,0 +1,229 @@ +import { + Directive, + ElementRef, + EventEmitter, + HostBinding, + HostListener, + Input, + Output, +} from '@angular/core'; + +export interface ElementSize { + id: string; + size: string; +} + +export interface StopResizingEvent { + prev: ElementSize | null; + next: ElementSize | null; +} + +@Directive({ + selector: '[resizing]', +}) +export class ResizingDirective { + @Input() resizingDisabled: boolean = false; + @Input() vertical: boolean = false; + + isMoving: boolean = false; + + @Output() onStopResizing = new EventEmitter(); + + private _prevElement: HTMLElement | null; + private _currentElement: HTMLElement; + private _nextElement: HTMLElement | null; + + constructor(private elementRef: ElementRef) {} + + ngAfterViewChecked(): void { + this._currentElement = this.elementRef.nativeElement; + this._prevElement = this._currentElement + .previousElementSibling as HTMLElement | null; + this._nextElement = this._currentElement + .nextElementSibling as HTMLElement | null; + } + + @HostBinding('attr.active') get getActive() { + return this.isMoving; + } + + @HostListener('mousedown') + startMove() { + document.body.classList.add('inherit-cursor'); + document.body.classList.add('pointer-events-children-none'); + document.body.style.userSelect = 'none'; + document.body.style.cursor = this.vertical ? 'ns-resize' : 'ew-resize'; + + this.isMoving = true; + + let computedStylesPrevElement = this._prevElement + ? getComputedStyle(this._prevElement) + : null; + let computedStylesNextElement = this._nextElement + ? getComputedStyle(this._nextElement) + : null; + let computedStylesPrevElementSizes = this._prevElement + ? { + width: computedStylesPrevElement!.width, + height: computedStylesPrevElement!.height, + } + : null; + let computedStylesNextElementSizes = this._prevElement + ? { + width: computedStylesNextElement!.width, + height: computedStylesNextElement!.height, + } + : null; + + if (!this.vertical) { + if (this._prevElement) { + this._prevElement.style.width = + computedStylesPrevElementSizes!.width; + this._prevElement.style.flexBasis = + this._prevElement.style.height; + } + if (this._nextElement) { + this._nextElement.style.width = + computedStylesNextElementSizes!.width; + this._nextElement.style.flexBasis = + this._nextElement.style.height; + } + } + if (this.vertical) { + if (this._prevElement) { + this._prevElement.style.height = + computedStylesPrevElementSizes!.height; + this._prevElement.style.flexBasis = + this._prevElement.style.height; + } + if (this._nextElement) { + this._nextElement.style.height = + computedStylesNextElementSizes!.height; + this._nextElement.style.flexBasis = + this._nextElement.style.height; + } + } + } + + @HostListener('window:mouseup') + stopMove() { + if (!this.isMoving) { + return; + } + + document.body.classList.remove('inherit-cursor'); + document.body.classList.remove('pointer-events-children-none'); + document.body.style.userSelect = ''; + document.body.style.cursor = ''; + + this.isMoving = false; + + let prev = this._prevElement + ? { + id: this._prevElement.getAttribute('resizing-id') as string, + size: this.vertical + ? getComputedStyle(this._prevElement).height + : getComputedStyle(this._prevElement).width, + } + : null; + let next = this._nextElement + ? { + id: this._nextElement.getAttribute('resizing-id') as string, + size: this.vertical + ? getComputedStyle(this._nextElement).height + : getComputedStyle(this._nextElement).width, + } + : null; + this.onStopResizing.emit({ + prev, + next, + }); + } + + @HostListener('window:mousemove', ['$event']) + move(event: MouseEvent) { + if (!this.isMoving) { + return; + } + + let computedStylesPrevElement = this._prevElement + ? getComputedStyle(this._prevElement) + : null; + let computedStylesNextElement = this._nextElement + ? getComputedStyle(this._nextElement) + : null; + let computedStylesPrevElementSizes = this._prevElement + ? { + width: computedStylesPrevElement!.width, + height: computedStylesPrevElement!.height, + } + : null; + let computedStylesNextElementSizes = this._prevElement + ? { + width: computedStylesNextElement!.width, + height: computedStylesNextElement!.height, + } + : null; + + if (!this.vertical) { + if ( + parseInt(computedStylesPrevElementSizes!.width) + + event.movementX < + 0 || + parseInt(computedStylesNextElementSizes!.width) - + event.movementX < + 0 + ) { + return; + } + + if (this._prevElement) { + this._prevElement.style.width = + parseInt(computedStylesPrevElementSizes!.width) + + event.movementX + + 'px'; + this._prevElement.style.flexBasis = + this._prevElement.style.width; + } + + if (this._nextElement) { + this._nextElement.style.width = + parseInt(computedStylesNextElementSizes!.width) - + event.movementX + + 'px'; + this._nextElement.style.flexBasis = + this._nextElement.style.width; + } + } + if (this.vertical) { + if ( + parseInt(computedStylesPrevElementSizes!.height) + + event.movementY < + 0 || + parseInt(computedStylesNextElementSizes!.height) - + event.movementY < + 0 + ) { + return; + } + + if (this._prevElement) { + this._prevElement.style.height = + parseInt(computedStylesPrevElementSizes!.height) + + event.movementY + + 'px'; + this._prevElement.style.flexBasis = + this._prevElement.style.height; + } + + if (this._nextElement) { + this._nextElement.style.height = + parseInt(computedStylesNextElementSizes!.height) - + event.movementY + + 'px'; + this._nextElement.style.flexBasis = + this._nextElement.style.height; + } + } + } +} diff --git a/frontend/src/app/modules/policy-engine/helpers/import-file-dialog/import-file-dialog.component.html b/frontend/src/app/modules/policy-engine/helpers/import-file-dialog/import-file-dialog.component.html index 6760f44001..1236fc95de 100644 --- a/frontend/src/app/modules/policy-engine/helpers/import-file-dialog/import-file-dialog.component.html +++ b/frontend/src/app/modules/policy-engine/helpers/import-file-dialog/import-file-dialog.component.html @@ -3,8 +3,10 @@ close - +
diff --git a/frontend/src/app/modules/policy-engine/helpers/import-file-dialog/import-file-dialog.component.ts b/frontend/src/app/modules/policy-engine/helpers/import-file-dialog/import-file-dialog.component.ts index 387fa6c8b3..05d02a232e 100644 --- a/frontend/src/app/modules/policy-engine/helpers/import-file-dialog/import-file-dialog.component.ts +++ b/frontend/src/app/modules/policy-engine/helpers/import-file-dialog/import-file-dialog.component.ts @@ -10,19 +10,28 @@ import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; styleUrls: ['./import-file-dialog.component.css'] }) export class ImportFileDialog { - loading: boolean = false; + public loading: boolean = false; + public fileExtension: string; + public label: string; constructor( public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any ) { + if (data) { + this.fileExtension = data.fileExtension || 'theme'; + this.label = data.label || 'Import Theme .theme file'; + } else { + this.fileExtension = 'theme'; + this.label = 'Import Theme .theme file'; + } } - onNoClick(): void { + public onNoClick(): void { this.dialogRef.close(null); } - importFromFile(file: any) { + public importFromFile(file: any) { const reader = new FileReader() reader.readAsArrayBuffer(file); reader.addEventListener('load', (e: any) => { diff --git a/frontend/src/app/modules/policy-engine/helpers/preview-policy-dialog/preview-policy-dialog.component.html b/frontend/src/app/modules/policy-engine/helpers/preview-policy-dialog/preview-policy-dialog.component.html index 1ac6cf29bf..374faa0ebb 100644 --- a/frontend/src/app/modules/policy-engine/helpers/preview-policy-dialog/preview-policy-dialog.component.html +++ b/frontend/src/app/modules/policy-engine/helpers/preview-policy-dialog/preview-policy-dialog.component.html @@ -46,9 +46,9 @@
Version
{{ policy.version }}
- @@ -75,6 +75,13 @@
Standard Registry, {{policyGroups}}
+
+ +
+
Tools
+
{{tools}}
+
+
diff --git a/frontend/src/app/modules/policy-engine/helpers/preview-policy-dialog/preview-policy-dialog.component.ts b/frontend/src/app/modules/policy-engine/helpers/preview-policy-dialog/preview-policy-dialog.component.ts index d5e292e47c..561f188e37 100644 --- a/frontend/src/app/modules/policy-engine/helpers/preview-policy-dialog/preview-policy-dialog.component.ts +++ b/frontend/src/app/modules/policy-engine/helpers/preview-policy-dialog/preview-policy-dialog.component.ts @@ -21,6 +21,7 @@ export class PreviewPolicyDialog { public similar!: any[]; public module!: any; public tool!: any; + public tools!: string; constructor( public dialogRef: MatDialogRef, @@ -28,7 +29,6 @@ export class PreviewPolicyDialog { ) { if (data.policy) { const importFile = data.policy; - this.newVersions = importFile.newVersions || []; this.policy = importFile.policy; @@ -37,6 +37,9 @@ export class PreviewPolicyDialog { this.policyGroups += this.policy.policyRoles.join(', '); } + this.tools = + importFile?.tools?.map((item: { name: any }) => item.name).join(', '); + const schemas = importFile.schemas || []; const tokens = importFile.tokens || []; diff --git a/frontend/src/app/modules/policy-engine/policies/policies.component.html b/frontend/src/app/modules/policy-engine/policies/policies.component.html index 8b80c459f2..54e0adffa4 100644 --- a/frontend/src/app/modules/policy-engine/policies/policies.component.html +++ b/frontend/src/app/modules/policy-engine/policies/policies.component.html @@ -212,7 +212,9 @@ import_export Export policy +
+ +
+
-
@@ -281,307 +281,334 @@
-
-
-
-
-
- article - Blocks -
-
- - Modules -
-
- handyman - Tools +
+ + + + + + + + + + + + +
+
+
+
+ +
+ lists +
+
+
+ article + Blocks +
+
+ + Modules +
+
+ handyman + Tools +
-
-
- -
- - -
-
- arrow_drop_down - arrow_right - star - Favorites - ({{componentsList.favorites.length}}) +
+ +
+ + -
-
-
-
- {{item.icon}} - {{item.name}} +
+
+ arrow_drop_down + arrow_right + star + Favorites + ({{componentsList.favorites.length}}) +
+
+
+
+
+ {{item.icon}} + {{item.name}} +
+
+
+ {{item.icon}} + {{item.name}} + star
-
-
- {{item.icon}} - {{item.name}} - star
-
-
-
- arrow_drop_down - arrow_right - UI Components ({{componentsList.uiComponents.length}}) -
-
-
-
-
- {{item.icon}} - {{item.name}} +
+
+ arrow_drop_down + arrow_right + UI Components ({{componentsList.uiComponents.length}}) +
+
+
+
+
+ {{item.icon}} + {{item.name}} +
+
+
+ {{item.icon}} + {{item.name}} + star
-
-
- {{item.icon}} - {{item.name}} - star
-
-
-
- arrow_drop_down - arrow_right - Server Components ({{componentsList.serverBlocks.length}}) -
-
-
-
-
- {{item.icon}} - {{item.name}} +
+
+ arrow_drop_down + arrow_right + Server Components ({{componentsList.serverBlocks.length}}) +
+
+
+
+
+ {{item.icon}} + {{item.name}} +
+
+
+ {{item.icon}} + {{item.name}} + star
-
-
- {{item.icon}} - {{item.name}} - star
-
-
-
- arrow_drop_down - arrow_right - Addons ({{componentsList.addons.length}}) -
-
-
-
-
- {{item.icon}} - {{item.name}} +
+
+ arrow_drop_down + arrow_right + Addons ({{componentsList.addons.length}}) +
+
+
+
+
+ {{item.icon}} + {{item.name}} +
+
+
+ {{item.icon}} + {{item.name}} + star
-
-
- {{item.icon}} - {{item.name}} - star
-
-
-
- arrow_drop_down - arrow_right - unGrouped -
-
-
-
-
- {{item.icon}} - {{item.name}} +
+
+ arrow_drop_down + arrow_right + unGrouped +
+
+
+
+
+ {{item.icon}} + {{item.name}} +
+
+
+ {{item.icon}} + {{item.name}} + star
-
-
- {{item.icon}} - {{item.name}} - star
-
-
- -
-
- arrow_drop_down - arrow_right - star - Favorites - ({{modulesList.favorites.length}}) +
+ -
-
-
-
-
- +
+
+ arrow_drop_down + arrow_right + star + Favorites + ({{modulesList.favorites.length}}) +
+
+
+
+
+
+ +
+ {{item.name}}
- {{item.name}}
-
-
-
- +
+
+ +
+ {{item.name}} + + star + +
- {{item.name}} - - star - -
-
-
-
- arrow_drop_down - arrow_right - Default Modules ({{modulesList.defaultModules.length}}) -
-
-
-
-
-
- +
+
+ arrow_drop_down + arrow_right + Default Modules ({{modulesList.defaultModules.length}}) +
+
+
+
+
+
+ +
+ {{item.name}}
- {{item.name}}
-
-
-
- +
+
+ +
+ {{item.name}} + + star +
- {{item.name}} - - star -
-
-
-
- arrow_drop_down - arrow_right - Custom models ({{modulesList.customModules.length}}) -
-
-
-
-
-
- +
+
+ arrow_drop_down + arrow_right + Custom models ({{modulesList.customModules.length}}) +
+
+
+
+
+
+ +
+ {{item.name}}
- {{item.name}}
-
-
-
- +
+
+ +
+ {{item.name}} + + star +
- {{item.name}} - - star -
-
-
- -
-
- arrow_drop_down - arrow_right - Tools ({{modulesList.customTools.length}}) +
+ -
-
-
-
-
- handyman +
+
+ arrow_drop_down + arrow_right + Tools ({{modulesList.customTools.length}}) +
+
+
+
+
+
+ handyman +
+ {{item.name}}
- {{item.name}}
-
-
-
- handyman +
+
+ handyman +
+ {{item.name}} + + +
- {{item.name}} - - -
@@ -589,328 +616,382 @@
-
-
- -
-
- {{item}} -
-
- -
-
-
- article - Policy -
-
- - Module -
-
- handyman - Tool + + +
+ account_tree +
+
+ {{item}}
+
- -
- - {{item.localTag}} +
+
+
+ article + Policy
- -
-
- palette +
+ + Module
-
-
-
-
- {{getLegendText(rule)}} -
+
+ handyman + Tool +
+ + +
+ + {{item.localTag}}
-
-
-
{{theme.default.description || '-'}}
+ +
+
+ palette
-
-
-
-
-
+
+
+
+
+ {{getLegendText(rule)}} +
-
- {{syntaxGroup.name}} +
+
+
{{theme.default.description || '-'}}
+
+
+
+
+
+
+
+
+ {{syntaxGroup.name}} +
-
-
-
- - -
- - -
-
- -
-
- -
- -
-
-
- Description -
-
- Roles -
-
- Groups -
-
- Topics -
-
- Tokens -
-
- open_in_full - close_fullscreen -
-
-
-
- -
-
- -
-
- -
-
- -
-
- +
+
+ +
+ +
-
-
-
- Description -
-
- Inputs -
-
- Outputs -
-
- Variables -
-
- open_in_full - close_fullscreen -
+
+ + + +
+ description + + + + + + + + + +
+
-
-
- -
-
- -
-
- -
-
- + + + + +
+ article +
+
+
+ Description +
+
+ Roles +
+
+ Groups +
+
+ Topics +
+
+ Tokens +
+
+ open_in_full + close_fullscreen +
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
-
-
-
-
- -
-
-
- Properties -
-
- Events -
-
- Artifacts -
-
- Json -
-
- open_in_full - close_fullscreen +
+
+
+ Description +
+
+ Inputs +
+
+ Outputs +
+
+ Variables +
+
+ open_in_full + close_fullscreen +
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
-
-
-
-
- {{item}} + + + +
+ article +
+
+
+ Properties +
+
+ Events +
+
+ Artifacts +
+
+ Json +
+
+ open_in_full + close_fullscreen +
+
+
+
+
+
+ {{item}} +
+
+ +
+
+ +
+
+ +
+
+
- -
-
- -
-
- -
-
-
-
-
- -
-
-
-
- Events -
-
- open_in_full - close_fullscreen -
-
-
-
- +
+ +
+
+
+ Events +
+
+ open_in_full + close_fullscreen +
+
+
+
+ +
+
-
+
- -
+
diff --git a/frontend/src/app/modules/policy-engine/policy-configuration/policy-configuration/policy-configuration.component.scss b/frontend/src/app/modules/policy-engine/policy-configuration/policy-configuration/policy-configuration.component.scss index 6045342b68..8a8f690dd4 100644 --- a/frontend/src/app/modules/policy-engine/policy-configuration/policy-configuration/policy-configuration.component.scss +++ b/frontend/src/app/modules/policy-engine/policy-configuration/policy-configuration/policy-configuration.component.scss @@ -1,6 +1,7 @@ ::ng-deep body { --pc-main-grid-border: #b4c4cb; --pc-border-color: #bdbdbd; + --pc-active-resize-delimiter-color: #1f2262; --pc-active-icon-color: #3f51b5; --pc-no-active-color: #555; --pc-active-background-color: #eff0f9; @@ -138,14 +139,16 @@ border: 4px solid var(--pc-main-grid-border); box-sizing: border-box; display: flex; + gap: 2px; + background-color: var(--pc-main-grid-border); } .left-toolbar { height: 100%; - width: 225px; position: relative; - border-right: 4px solid var(--pc-main-grid-border); box-sizing: border-box; + background-color: white; + z-index: 1; } .center-container { @@ -153,6 +156,8 @@ position: relative; overflow: hidden; box-sizing: border-box; + z-index: 1; + background-color: white; } .tree-container { @@ -181,30 +186,30 @@ .right-toolbar { height: 100%; - width: 485px; + // width: 485px; position: relative; box-sizing: border-box; - border-left: 4px solid var(--pc-main-grid-border); display: flex; + gap: 2px; flex-direction: column; background: var(--pc-main-grid-border); + z-index: 1; + // min-width: 465px; } .right-top-toolbar { position: relative; width: 100%; - border-bottom: 4px solid var(--pc-main-grid-border); box-sizing: border-box; - min-height: 340px; - max-height: 340px; - height: 340px; + min-height: 31px; flex: 1; + z-index: 1; background: var(--pc-background-color); &[collapse-top="true"] { - max-height: 35px; - height: 35px; - min-height: 35px; + max-height: 31px; + height: 31px; + min-height: 31px; } &[collapse-bottom="true"] { @@ -213,9 +218,9 @@ } &[collapse-top="true"][collapse-bottom="true"] { - max-height: 35px; - height: 35px; - min-height: 35px; + max-height: 31px; + height: 31px; + min-height: 31px; } } @@ -223,6 +228,8 @@ box-sizing: border-box; position: relative; flex: 1; + min-height: 31px; + z-index: 1; background: var(--pc-background-color); &[collapse-top="true"] { @@ -475,6 +482,9 @@ padding: 9px 24px; cursor: pointer; user-select: none; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; .components-group-name-icon { color: var(--pc-favorite-color); @@ -915,8 +925,9 @@ } .properties { + width: 100%; --col-name-width: 155px; - --col-value-width: 285px; + // --col-value-width: 285px; background: var(--pc-prop-border-color); position: relative; @@ -928,6 +939,7 @@ .propRowCol { background: var(--pc-prop-border-color); width: 20px; + min-width: 20px; position: relative; cursor: pointer; user-select: none; @@ -1090,6 +1102,10 @@ font-size: 12px; } + .propRowCell:has(.remove-prop) { + padding-right: 30px; + } + .required-fields { position: relative !important; width: 8px !important; @@ -1571,4 +1587,58 @@ &[status="TOOL"] { color: var(--pc-tool-block-color); } +} + +.resizing, .resizing-vertical { + position: relative; + + .resizing__delimiter:hover, + &[active="true"] .resizing__delimiter { + background-color: var(--pc-active-resize-delimiter-color) !important; + } +} + +.resizing__delimiter { + background-color: var(--pc-main-grid-border); + position: absolute; + z-index: 0; +} + +.resizing { + height: 100%; + + .resizing__delimiter { + width: 4px; height: 100%; left: -2px; + } + + .resizing__delimiter:hover { + cursor: ew-resize; + } +} + +.resizing-vertical { + width: 100%; + + .resizing__delimiter { + width: 100%; + height: 4px; + top: -2px; + } + + .resizing__delimiter:hover { + cursor: ns-resize; + } +} + +::ng-deep { + .cdk-drag-placeholder { + &.left-toolbar, + &.center-container, + &.right-toolbar, + &.right-top-toolbar, + &.right-bottom-toolbar { + outline: 1px dashed black; + opacity: 0.5; + } + } } \ No newline at end of file diff --git a/frontend/src/app/modules/policy-engine/policy-configuration/policy-configuration/policy-configuration.component.ts b/frontend/src/app/modules/policy-engine/policy-configuration/policy-configuration/policy-configuration.component.ts index 4ddecd4f26..20bb150dd0 100644 --- a/frontend/src/app/modules/policy-engine/policy-configuration/policy-configuration/policy-configuration.component.ts +++ b/frontend/src/app/modules/policy-engine/policy-configuration/policy-configuration/policy-configuration.component.ts @@ -1,4 +1,4 @@ -import { CdkDropList } from '@angular/cdk/drag-drop'; +import { CdkDragDrop, CdkDropList, moveItemInArray } from '@angular/cdk/drag-drop'; import { ChangeDetectorRef, Component, HostListener, OnInit, ViewChild } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; @@ -38,6 +38,8 @@ import { SuggestionsService } from '../../../../services/suggestions.service'; import { ToolsService } from '../../../../services/tools.service'; import { AnalyticsService } from '../../../../services/analytics.service'; import { WizardMode, WizardService } from 'src/app/modules/policy-engine/services/wizard.service'; +import { StopResizingEvent } from '../../directives/resizing.directive'; +import { OrderOption } from '../../structures/interfaces/order-option.interface'; /** * The page for editing the policy and blocks. @@ -969,7 +971,7 @@ export class PolicyConfigurationComponent implements OnInit { this.changeDetector.detectChanges(); } - private setErrors(results: any) { + private setErrors(results: any, type: string) { const blocks = results.blocks || []; const modules = results.modules || []; const tools = results.tools || []; @@ -1000,12 +1002,12 @@ export class PolicyConfigurationComponent implements OnInit { } } } - this.errorsCount = this.errors.length; + this.errorsCount = this.errors.length + commonErrors.length; this.errorsMap = {}; for (const element of this.errors) { this.errorsMap[element.id] = element.errors; } - this.errorMessage(commonErrors); + this.errorMessage(commonErrors, type); } private jsonToObject(json: string): any { @@ -1062,10 +1064,10 @@ export class PolicyConfigurationComponent implements OnInit { this.updateCodeMirrorStyles(); } - private errorMessage(errors: string[]) { + private errorMessage(errors: string[], type: string) { if (errors && errors.length) { const text = errors.map((text) => `
${text}
`).join(''); - this.informService.errorShortMessage(text, 'The policy is invalid'); + this.informService.errorShortMessage(text, `The ${type} is invalid`); } } @@ -1252,7 +1254,7 @@ export class PolicyConfigurationComponent implements OnInit { const { policy, results } = data; const config = policy.config; this.policyTemplate.rebuild(config); - this.setErrors(results); + this.setErrors(results, 'policy'); this.onSelect(this.openFolder.root); this.loading = false; }, (e) => { @@ -1296,7 +1298,7 @@ export class PolicyConfigurationComponent implements OnInit { this.clearState(); this.loadData(); } else { - this.setErrors(errors); + this.setErrors(errors, 'policy'); this.loading = false; } }, (e) => { @@ -1487,7 +1489,7 @@ export class PolicyConfigurationComponent implements OnInit { this.modulesService.validate(module).subscribe((data: any) => { const { module, results } = data; this.moduleTemplate.rebuild(module); - this.setErrors(results); + this.setErrors(results, 'module'); this.onOpenRoot(this.moduleTemplate); this.onSelect(this.openFolder.root); this.loading = false; @@ -1558,7 +1560,7 @@ export class PolicyConfigurationComponent implements OnInit { this.toolsService.validate(tool).subscribe((data: any) => { const { tool, results } = data; this.toolTemplate.rebuild(tool); - this.setErrors(results); + this.setErrors(results, 'tool'); this.onOpenRoot(this.toolTemplate); this.onSelect(this.openFolder.root); this.loading = false; @@ -1677,4 +1679,47 @@ export class PolicyConfigurationComponent implements OnInit { } } } + + onDroppedSection(event: CdkDragDrop) { + moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); + this.options.save(); + } + + saveSizes(event: StopResizingEvent) { + return (item: OrderOption) => { + if (item.id === event.prev?.id) { + item.size = event.prev.size; + } + if (item.id === event.next?.id) { + item.size = event.next.size; + } + return item; + } + } + + stopResizingConfiguration(event: StopResizingEvent) { + this.options.configurationOrder = this.options.configurationOrder.map( + this.saveSizes(event) + ); + this.options.save(); + } + + stopResizingProperties(event: StopResizingEvent) { + this.options.propertiesOrder = this.options.propertiesOrder.map( + this.saveSizes(event) + ); + this.options.save(); + } + + onDragSection(event: any) { + document.body.classList.add('inherit-cursor'); + document.body.classList.add('pointer-events-children-none'); + document.body.style.cursor = 'grabbing'; + } + + onDropSection(event: any) { + document.body.classList.remove('inherit-cursor'); + document.body.classList.remove('pointer-events-children-none'); + document.body.style.cursor = ''; + } } diff --git a/frontend/src/app/modules/policy-engine/policy-engine.module.ts b/frontend/src/app/modules/policy-engine/policy-engine.module.ts index 35d5727336..4aa01e4f89 100644 --- a/frontend/src/app/modules/policy-engine/policy-engine.module.ts +++ b/frontend/src/app/modules/policy-engine/policy-engine.module.ts @@ -104,6 +104,11 @@ import { WizardService } from './services/wizard.service'; import { PoliciesComponent } from './policies/policies.component'; import { SearchBlocksComponent } from './helpers/search-blocks/search-blocks.component'; import { SelectSchema } from './helpers/select-schema/select-schema.component'; +import { RecordControllerComponent } from './record/record-controller/record-controller.component'; +import { RecordResultDialog } from './record/record-result-dialog/record-result-dialog.component'; +import { RecordResultsComponent } from './record/record-results/record-results.component'; +// Directives +import { ResizingDirective } from './directives/resizing.directive'; @NgModule({ declarations: [ @@ -192,7 +197,11 @@ import { SelectSchema } from './helpers/select-schema/select-schema.component'; PolicyWizardDialogComponent, MessagesReportBlockComponent, ViewerDialog, - SearchBlocksComponent + SearchBlocksComponent, + RecordControllerComponent, + RecordResultDialog, + RecordResultsComponent, + ResizingDirective ], imports: [ CommonModule, diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/documents-source-block/documents-source-block.component.ts b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/documents-source-block/documents-source-block.component.ts index e704c987e8..e17d0775bf 100644 --- a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/documents-source-block/documents-source-block.component.ts +++ b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/documents-source-block/documents-source-block.component.ts @@ -163,7 +163,7 @@ export class DocumentsSourceBlockComponent implements OnInit { return; } for (const doc of documents) { - if (doc.history) { + if (Array.isArray(doc.history)) { doc.history = doc.history .map((item: any) => Object.assign(item, { @@ -179,6 +179,9 @@ export class DocumentsSourceBlockComponent implements OnInit { }) ); } + if (Array.isArray(doc.serials)) { + doc.serials.sort((a: any, b: any) => a < b ? -1 : 1); + } } } diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/report-block/report-block.component.css b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/report-block/report-block.component.css index a9a1961a14..64c47e4913 100644 --- a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/report-block/report-block.component.css +++ b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/report-block/report-block.component.css @@ -11,7 +11,7 @@ .loading { background: #fff; position: fixed; - z-index: 99; + z-index: 90; top: var(--header-height-policy); left: 0; bottom: 0; @@ -106,6 +106,13 @@ a[disabled="true"] { color: #f44336; } +.mint-amount { +} + +.mint-error .mint-amount { + color: #f44336; +} + .filter-btn { background-color: #3f51b5; color: #fff; @@ -610,4 +617,4 @@ a.open-vp { *[vp-section-offset="true"] { margin: 0 54px 30px 104px; -} \ No newline at end of file +} diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/report-block/report-block.component.html b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/report-block/report-block.component.html index 1163cb1da7..c195f5c1f5 100644 --- a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/report-block/report-block.component.html +++ b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/report-block/report-block.component.html @@ -55,7 +55,13 @@
Token amount
-
{{item.mintDocument.amount}}
+
+ {{ item.mintDocument.amount }} + / + {{ item.mintDocument.expected }} + minted +
Mint date
diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/report-block/report-block.component.ts b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/report-block/report-block.component.ts index de48e8eb7e..801d32ea71 100644 --- a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/report-block/report-block.component.ts +++ b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/report-block/report-block.component.ts @@ -363,4 +363,8 @@ export class ReportBlockComponent implements OnInit { const secondDocumentIndex = (indexDocument - 1) < 0 ? itemDocuments.length + (indexDocument - 1) : (indexDocument - 1); this.onMultipleDocumentClick(itemDocuments[secondDocumentIndex], item); } + + mintError(item: any): boolean { + return item.amount !== item.expected + } } diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.html b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.html index 41e9401801..b9114d7994 100644 --- a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.html +++ b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.html @@ -7,15 +7,29 @@

{{title}}

{{description}}
- infoThere is some data to restore. You can restore latest values: + info + There is some data to restore. You can restore latest values:
+
+ info + There is dry run mode. You can fill document with test data: + +
- +
@@ -33,20 +47,32 @@

{{data.dialogContent}}

{{description}}
- infoThere is some data to restore. You can restore latest values: + info + There is some data to restore. You can restore latest values:
+
+ info + There is dry run mode. You can fill document with test data: + +
- diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.scss b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.scss index 8ae2a45858..fd94cea82e 100644 --- a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.scss +++ b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.scss @@ -117,12 +117,17 @@ } .restore-data { - margin-top: 10px; - margin-left: 40px; display: flex; align-items: center; gap: 10px; color: var(--primary-color); + margin-top: 10px; + margin-left: 25px; + margin-right: 50px; + padding-bottom: 15px; + padding-left: 15px; + border-bottom: 1px solid #c8c8c8; + margin-bottom: 10px; } .restore-data button.mat-raised-button { @@ -150,7 +155,7 @@ } .schema-form { - max-height: calc(100vh - 430px); + max-height: calc(100vh - 465px); padding: 20px 20px 0px 0; margin-bottom: 0; } @@ -198,15 +203,14 @@ } .page-btns { - display: block; width: 50%; display: flex; justify-content: space-between; margin-top: 20px; background-color: white; - padding: 20px 0; + padding: 8px 0 20px 0; position: fixed; - bottom: 0; + bottom: 4px; left: 50%; transform: translateX(-50%); z-index: 200; diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.ts b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.ts index d3c1051314..cf474eba5e 100644 --- a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.ts +++ b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.ts @@ -1,11 +1,10 @@ import { ChangeDetectorRef, Component, Input, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; -import { IUser } from '@guardian/interfaces'; +import { DocumentGenerator, IUser } from '@guardian/interfaces'; import { PolicyEngineService } from 'src/app/services/policy-engine.service'; import { PolicyHelper } from 'src/app/services/policy-helper.service'; import { ProfileService } from 'src/app/services/profile.service'; -import { global } from '@angular/compiler/src/util'; import { WebSocketService } from 'src/app/services/web-socket.service'; import { Router } from '@angular/router'; @@ -23,30 +22,31 @@ export class RequestDocumentBlockComponent implements OnInit { @Input('static') static!: any; @ViewChild("dialogTemplate") dialogTemplate!: TemplateRef; - isExist = false; - disabled = false; - loading: boolean = true; - socket: any; - dialogLoading: boolean = false; - dataForm: FormGroup; - schema: any; - hideFields: any; - type!: string; - content: any; - dialogContent: any; - dialogClass: any; - dialogRef: any; - ref: any; - title: any; - description: any; - presetDocument: any; - rowDocument: any; - needPreset: any; - presetFields: any; - presetReadonlyFields: any; - buttonClass: any; - user!: IUser; - restoreData: any; + public isExist = false; + public disabled = false; + public loading: boolean = true; + public socket: any; + public dialogLoading: boolean = false; + public dataForm: FormGroup; + public schema: any; + public hideFields: any; + public type!: string; + public content: any; + public dialogContent: any; + public dialogClass: any; + public dialogRef: any; + public ref: any; + public title: any; + public description: any; + public presetDocument: any; + public rowDocument: any; + public needPreset: any; + public presetFields: any; + public presetReadonlyFields: any; + public buttonClass: any; + public user!: IUser; + public restoreData: any; + public dryRunMode: boolean = false; constructor( private policyEngineService: PolicyEngineService, @@ -59,6 +59,7 @@ export class RequestDocumentBlockComponent implements OnInit { private changeDetectorRef: ChangeDetectorRef ) { this.dataForm = fb.group({}); + this.dryRunMode = true; } ngOnInit(): void { @@ -306,6 +307,11 @@ export class RequestDocumentBlockComponent implements OnInit { this.restoreData = null; } + onDryRun() { + const presetDocument = DocumentGenerator.generateDocument(this.schema); + this.preset(presetDocument); + } + handleCancelBtnEvent(value: boolean, data: any) { data.onCancel() } diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/policy-viewer/policy-viewer.component.html b/frontend/src/app/modules/policy-engine/policy-viewer/policy-viewer/policy-viewer.component.html index cb6df1cf9d..354a7b5417 100644 --- a/frontend/src/app/modules/policy-engine/policy-viewer/policy-viewer/policy-viewer.component.html +++ b/frontend/src/app/modules/policy-engine/policy-viewer/policy-viewer/policy-viewer.component.html @@ -1,4 +1,7 @@ -
+
@@ -17,7 +20,10 @@
-
edit View Config @@ -45,6 +51,26 @@
+
+ radio_button_checked + Record +
+ +
+ play_arrow + Run +
+ +
+
Policy View
@@ -64,12 +90,12 @@ -
@@ -139,6 +166,14 @@
+ +
diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/policy-viewer/policy-viewer.component.scss b/frontend/src/app/modules/policy-engine/policy-viewer/policy-viewer/policy-viewer.component.scss index 81c3613097..9bf2611fad 100644 --- a/frontend/src/app/modules/policy-engine/policy-viewer/policy-viewer/policy-viewer.component.scss +++ b/frontend/src/app/modules/policy-engine/policy-viewer/policy-viewer/policy-viewer.component.scss @@ -5,12 +5,113 @@ bottom: 0; top: 0; display: block; + border-top: 52px solid transparent; + + .policy-header { + position: fixed; + top: 125px; + left: 0; + right: 0; + height: 50px; + color: #707070; + border-bottom: 1px solid #f7f7f7; + display: flex; + box-sizing: border-box; + justify-content: space-between; + box-shadow: 0px 0px 5px 0px rgb(0 0 0 / 50%); + z-index: 100; + background: #ffffff; + padding: 12px 30px; + font-size: 20px; + + &[policy-status='DRY-RUN'] { + top: 170px; + } + + span { + height: 30px; + padding: 5px 0px; + box-sizing: border-box; + + &:not(.current-user-group__label) { + margin-right: 30px; + } + } + } + + a.go-back-link { + text-decoration: none; + color: #707070; + padding: 5px 30px 5px 26px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + + mat-icon { + color: #707070; + position: absolute; + top: 12px; + left: 10px; + font-size: 30px; + } + } + + .policy-container { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + overflow: auto; + + &[hidden-container='true'] { + opacity: 0; + z-index: -1; + pointer-events: none; + } + } + + &[status='DRY-RUN'] { + border: 4px solid #3f51b5; + border-top-width: 100px; + + .policy-header { + background: #3f51b5; + border-bottom: 1px solid #155f9b; + color: #fff; + } + + .go-back-link { + color: #fff; + } + + .go-back-link mat-icon { + color: #fff; + } + + .policy-container { + overflow: auto; + } + } + + &[recording='true'], + &[running='true'] { + border-right-width: 224px !important; + + .policy-header { + right: 224px !important; + } + } + + &[instance='false'] { + border-color: transparent !important; + } } .loading { background: #fff; position: fixed; - z-index: 99; + z-index: 101; top: var(--header-height); left: 0; bottom: 0; @@ -22,85 +123,28 @@ align-content: center; } -.not-exist { - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); - font-size: 20px; - color: darkgrey; -} - -a.go-back-link { - text-decoration: none; - color: #707070; - padding: 5px 30px 5px 26px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -} - -a.go-back-link mat-icon { - color: #707070; +.record-loading { + background: #fff; position: absolute; - top: 12px; - left: 10px; - font-size: 30px; -} - -.policy-header { - position: fixed; - top: 125px; + z-index: 99; + top: 0; left: 0; + bottom: 0; right: 0; - height: 50px; - color: #707070; - border-bottom: 1px solid #f7f7f7; display: flex; - box-sizing: border-box; - justify-content: space-between; - box-shadow: 0px 0px 5px 0px rgb(0 0 0 / 50%); - z-index: 100; - background: #ffffff; - padding: 12px 30px; - font-size: 20px; -} - -.policy-header[policy-status='DRY-RUN'] { - top: 170px; -} - -.policy-container { - position: absolute; - left: 0; - top: 50px; - right: 0; - bottom: 0; + align-items: center; + justify-items: center; + justify-content: center; + align-content: center; } -.policy-container[policy-status='DRY-RUN'] { +.not-exist { position: absolute; - left: 0; - top: 90px; - right: 0; - bottom: 0; - overflow: auto; -} - -.policy-container[hidden-container='true'] { - opacity: 0; - z-index: -1; - pointer-events: none; -} - -.policy-header span { - height: 30px; - padding: 5px 0px; - box-sizing: border-box; -} - -.policy-header span:not(.current-user-group__label) { - margin-right: 30px; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + font-size: 20px; + color: darkgrey; } .current-user-role { @@ -108,76 +152,6 @@ a.go-back-link mat-icon { white-space: nowrap; } -.debug-user { - padding: 10px; - cursor: pointer; - margin: 0 1px; - opacity: 0.9; -} - -.debug-user:hover { - background: #3f51b50d; -} - -.debug-user[active='true'] { - background: #3f51b5; - color: #fff; - font-weight: 500; -} -.debug-user[active='true'] .debug-user-did { - color: #fff; - font-weight: normal; -} - -.new-debug-user { - padding: 10px; - cursor: pointer; - color: rgb(0, 0, 238); - cursor: pointer; - text-decoration: underline; - font-size: 16px; -} - -.debug-user-name { - width: 350px; - overflow: hidden; - text-overflow: ellipsis; - font-size: 16px; - margin-bottom: 2px; -} - -.debug-user-did { - width: 350px; - overflow: hidden; - text-overflow: ellipsis; - font-size: 12px; - margin-bottom: 2px; - color: #3c3c3c; -} - -.dry-run-panel { - cursor: pointer; - position: relative; -} - -.content[status='DRY-RUN'] { - border: 4px solid #3f51b5; -} - -.content[status='DRY-RUN'] .policy-header { - background: #3f51b5; - border-bottom: 1px solid #155f9b; - color: #fff; -} - -.content[status='DRY-RUN'] .go-back-link { - color: #fff; -} - -.content[status='DRY-RUN'] .go-back-link mat-icon { - color: #fff; -} - .dry-run-content { position: fixed; top: 125px; @@ -189,118 +163,167 @@ a.go-back-link mat-icon { z-index: 150; font-size: 20px; background: #fff; - border-bottom: 1px solid #000000; box-shadow: 0px 0px 1px 1px rgb(179 179 179); padding: 0px 10px; -} -.dry-run-btn { - position: relative; - font-size: 14px; - padding: 6px 12px; - margin: 6px 6px 6px 0px; - color: #646464; - cursor: pointer; - user-select: none; -} + .dry-run-group-btn, + .dry-run-action-btn { + display: flex; + overflow: hidden; + height: 34px; + flex-direction: row; + border: 1px solid transparent; + margin: 5px 4px; + padding: 2px 2px 4px 6px; + font-size: 12px; + align-items: center; + align-content: center; + color: #3f51b5; + cursor: pointer; + padding-top: 4px; + position: relative; + user-select: none; + border-radius: 6px; + background: rgb(252 252 252); + border: 1px solid rgb(220 220 220); + box-sizing: border-box; + } -.dry-run-btn:hover { - background: #f7f8fc; -} + .dry-run-group-btn span, + .dry-run-action-btn span { + font-size: 12px; + text-align: center; + line-height: 14px; + user-select: none; + display: flex; + max-width: 65px; + min-width: 60px; + height: 28px; + align-items: center; + align-content: center; + justify-content: center; + justify-items: center; + padding: 0px 4px 0px 2px; + pointer-events: none; + } -.dry-run-btn[action='true'] { - color: #3f51b5; - border-bottom: 2px solid #3f51b5; -} + .dry-run-group-btn:hover, + .dry-run-action-btn:hover { + background: rgb(63 81 181 / 3%); + border: 1px solid #3f51b5; + } -.dry-run-group-btn, -.dry-run-action-btn { - display: flex; - overflow: hidden; - height: 34px; - flex-direction: row; - border: 1px solid transparent; - margin: 5px 4px; - padding: 2px 2px 4px 6px; - font-size: 12px; - align-items: center; - align-content: center; - color: #3f51b5; - cursor: pointer; - padding-top: 4px; - position: relative; - user-select: none; - border-radius: 6px; - background: rgb(252 252 252); - border: 1px solid rgb(220 220 220); - box-sizing: border-box; -} + .dry-run-group-btn[disabled="true"], + .dry-run-action-btn[disabled="true"] { + filter: grayscale(1); + opacity: 0.7; + pointer-events: none; + } -.dry-run-group-btn span, -.dry-run-action-btn span { - font-size: 12px; - text-align: center; - line-height: 14px; - user-select: none; - display: flex; - max-width: 65px; - min-width: 60px; - height: 28px; - align-items: center; - align-content: center; - justify-content: center; - justify-items: center; - padding: 0px 4px 0px 2px; -} + .delimiter { + width: 2px; + height: 30px; + border-left: 1px solid #7e7e7e; + margin-left: 12px; + margin-right: 10px; + margin-top: 7px; + } -.dry-run-group-btn:hover, -.dry-run-action-btn:hover { - background: rgb(63 81 181 / 3%); - border: 1px solid #3f51b5; -} + .dry-run-btn { + position: relative; + font-size: 14px; + padding: 6px 12px; + margin: 6px 6px 6px 0px; + color: #646464; + cursor: pointer; + user-select: none; + + &:hover { + background: #f7f8fc; + } + + &[action='true'] { + color: #3f51b5; + border-bottom: 2px solid #3f51b5; + } + } -.dry-run-group-btn span { - max-width: 65px; - min-width: 45px; -} + .dry-run-group-btn span { + max-width: 65px; + min-width: 45px; + } -.dry-run-group-btn { - padding-right: 21px; -} + .dry-run-group-btn { + padding-right: 21px; + } -.dry-run-group-btn .expand-group { - position: absolute; - top: 0; - right: 0px; - bottom: 0; - width: 18px; - overflow: hidden; - background: rgb(240 240 240); - border-top-right-radius: 6px; - border-bottom-right-radius: 6px; - border-left: 1px solid rgb(220 220 220); -} + .dry-run-group-btn .expand-group { + position: absolute; + top: 0; + right: 0px; + bottom: 0; + width: 18px; + overflow: hidden; + background: rgb(240 240 240); + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; + border-left: 1px solid rgb(220 220 220); + pointer-events: none; + } -.dry-run-group-btn .expand-group mat-icon { - position: absolute; - top: 2px; - font-size: 27px; - left: -5px; -} + .dry-run-group-btn .expand-group mat-icon { + position: absolute; + top: 2px; + font-size: 27px; + left: -5px; + } + + .record-btn { + color: #ff0000; + } -.delimiter { - width: 2px; - height: 30px; - border-left: 1px solid #7e7e7e; - margin-left: 12px; - margin-right: 10px; - margin-top: 7px; + .run-btn { + color: #00ad00; + } } -.dry-run-title { - padding: 12px 2px 12px 4px; - color: #707070; - font-size: 18px; +.debug-user { + padding: 10px; + cursor: pointer; + margin: 0 1px; + opacity: 0.9; + + &:hover { + background: #3f51b50d; + } + + &[active='true'] { + background: #3f51b5; + color: #fff; + font-weight: 500; + } + + &[active='true'] .debug-user-did { + color: #fff; + font-weight: normal; + } + + .debug-user-name { + width: 350px; + overflow: hidden; + text-overflow: ellipsis; + font-size: 16px; + margin-bottom: 2px; + } + + .debug-user-did { + width: 350px; + overflow: hidden; + text-overflow: ellipsis; + font-size: 12px; + margin-bottom: 2px; + color: #3c3c3c; + } } .table-container { @@ -334,6 +357,7 @@ a.go-back-link mat-icon { overflow: hidden; box-sizing: border-box; } + .btn-settings:hover { text-decoration: underline; } @@ -345,7 +369,7 @@ a.go-back-link mat-icon { font-size: 22px; } -.document-table .mat-column-createDate { +.document-table .mat-column-createDate { width: 260px; } @@ -440,14 +464,15 @@ a.go-back-link mat-icon { @media (max-width: 810px) { .policy-header { - position: sticky; - top: 0 /*var(--header-height)*/; - box-shadow: none; - border: none; + position: sticky !important; + top: 0px !important; + box-shadow: none !important; + border: none !important; + transform: translate(0px, -52px) !important; } .policy-header[policy-status='DRY-RUN'] { - top: 45px; + transform: translate(0px, -54px) !important; } .dry-run-content { @@ -458,4 +483,34 @@ a.go-back-link mat-icon { max-width: 170px; margin-right: 0; } + + .current-user-role { + display: none !important; + } + + .heder-policy-version, + .heder-policy-description { + display: none !important; + } + + .dry-run-btn { + display: none !important; + } + + .delimiter { + display: none !important; + } + + .dry-run-group-btn span, + .dry-run-action-btn span { + display: none !important; + } + + .dry-run-group-btn { + width: 57px !important; + } + + .dry-run-action-btn { + width: 38px !important; + } } \ No newline at end of file diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/policy-viewer/policy-viewer.component.ts b/frontend/src/app/modules/policy-engine/policy-viewer/policy-viewer/policy-viewer.component.ts index 223b3a373b..1f69c74f2c 100644 --- a/frontend/src/app/modules/policy-engine/policy-viewer/policy-viewer/policy-viewer.component.ts +++ b/frontend/src/app/modules/policy-engine/policy-viewer/policy-viewer/policy-viewer.component.ts @@ -1,4 +1,4 @@ -import { Component, OnDestroy, OnInit } from '@angular/core'; +import { ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; import { IUser, PolicyType } from '@guardian/interfaces'; @@ -10,6 +10,8 @@ import { ProfileService } from 'src/app/services/profile.service'; import { TokenService } from 'src/app/services/token.service'; import { WebSocketService } from 'src/app/services/web-socket.service'; import { VCViewerDialog } from 'src/app/modules/schema-engine/vc-dialog/vc-dialog.component'; +import { RecordService } from 'src/app/services/record.service'; +import { RecordControllerComponent } from '../../record/record-controller/record-controller.component'; /** * Component for choosing a policy and @@ -21,17 +23,17 @@ import { VCViewerDialog } from 'src/app/modules/schema-engine/vc-dialog/vc-dialo styleUrls: ['./policy-viewer.component.scss'] }) export class PolicyViewerComponent implements OnInit, OnDestroy { - policyId!: string; - policy: any | null; - policyInfo: any | null; - role!: any; - loading: boolean = true; - isConfirmed: boolean = false; - virtualUsers: any[] = [] - view: string = 'policy'; - documents: any[] = []; - columns: string[] = []; - columnsMap: any = { + public policyId!: string; + public policy: any | null; + public policyInfo: any | null; + public role!: any; + public loading: boolean = true; + public isConfirmed: boolean = false; + public virtualUsers: any[] = [] + public view: string = 'policy'; + public documents: any[] = []; + public columns: string[] = []; + public columnsMap: any = { transactions: [ 'createDate', 'type', @@ -51,33 +53,34 @@ export class PolicyViewerComponent implements OnInit, OnDestroy { 'document' ] }; - pageIndex: number; - pageSize: number; - documentCount: any; - groups: any[] = []; - isMultipleGroups: boolean = false; - userRole!: string; - userGroup!: string; + public pageIndex: number; + public pageSize: number; + public documentCount: any; + public groups: any[] = []; + public isMultipleGroups: boolean = false; + public userRole!: string; + public userGroup!: string; + public recordingActive: boolean = false; private subscription = new Subscription(); - public innerWidth: any; - public innerHeight: any; - - public get isDryRun(): boolean { return this.policyInfo && this.policyInfo.status === 'DRY-RUN'; } + @ViewChild('recordController') + public set recordController(value: RecordControllerComponent | undefined) { + this._recordController = value; + } + private _recordController!: RecordControllerComponent | undefined; + constructor( private profileService: ProfileService, private policyEngineService: PolicyEngineService, private wsService: WebSocketService, - private tokenService: TokenService, private route: ActivatedRoute, - private router: Router, private dialog: MatDialog, - private toastr: ToastrService + private cdRef: ChangeDetectorRef ) { this.policy = null; this.pageIndex = 0; @@ -86,8 +89,6 @@ export class PolicyViewerComponent implements OnInit, OnDestroy { } ngOnInit() { - this.innerWidth = window.innerWidth; - this.innerHeight = window.innerHeight; this.loading = true; this.subscription.add( this.route.queryParams.subscribe(queryParams => { @@ -129,6 +130,7 @@ export class PolicyViewerComponent implements OnInit, OnDestroy { this.policyInfo = null; this.isConfirmed = false; this.loading = true; + this.recordingActive = false; this.profileService.getProfile().subscribe((profile: IUser | null) => { this.isConfirmed = !!(profile && profile.confirmed); this.role = profile ? profile.role : null; @@ -146,28 +148,21 @@ export class PolicyViewerComponent implements OnInit, OnDestroy { loadPolicyById(policyId: string) { forkJoin([ - this.policyEngineService.policyBlock(policyId), this.policyEngineService.policy(policyId), - this.policyEngineService.getGroups(policyId), + this.policyEngineService.policyBlock(policyId), + this.policyEngineService.getGroups(policyId) ]).subscribe((value) => { - this.policy = value[0]; - this.policyInfo = value[1]; + this.policyInfo = value[0]; + this.policy = value[1]; this.groups = value[2] || []; + this.virtualUsers = []; this.isMultipleGroups = !!(this.policyInfo?.policyGroups && this.groups?.length); - this.userRole = this.policyInfo.userRole; this.userGroup = this.policyInfo.userGroup?.groupLabel || this.policyInfo.userGroup?.uuid; if (this.policyInfo?.status === PolicyType.DRY_RUN) { - this.policyEngineService.getVirtualUsers(this.policyInfo.id).subscribe((users) => { - this.virtualUsers = users; - setTimeout(() => { - this.loading = false; - }, 500); - }, (e) => { - this.loading = false; - }); + this.loadDryRunOptions(); } else { setTimeout(() => { this.loading = false; @@ -178,6 +173,17 @@ export class PolicyViewerComponent implements OnInit, OnDestroy { }); } + loadDryRunOptions() { + this.policyEngineService.getVirtualUsers(this.policyInfo.id).subscribe((value) => { + this.virtualUsers = value; + setTimeout(() => { + this.loading = false; + }, 500); + }, (e) => { + this.loading = false; + }); + } + setGroup(item: any) { this.loading = true; this.policyEngineService.setGroup(this.policyInfo.id, item ? item.uuid : null).subscribe(() => { @@ -254,13 +260,13 @@ export class PolicyViewerComponent implements OnInit, OnDestroy { openDocument(element: any) { let dialogRef; - if (this.innerWidth <= 810) { + if (window.innerWidth <= 810) { const bodyStyles = window.getComputedStyle(document.body); const headerHeight: number = parseInt(bodyStyles.getPropertyValue('--header-height')); dialogRef = this.dialog.open(VCViewerDialog, { - width: `${this.innerWidth.toString()}px`, + width: `${window.innerWidth.toString()}px`, maxWidth: '100vw', - height: `${this.innerHeight - headerHeight}px`, + height: `${window.innerHeight - headerHeight}px`, position: { 'bottom': '0' }, @@ -335,4 +341,34 @@ export class PolicyViewerComponent implements OnInit, OnDestroy { } return document; } + + public updatePolicy() { + forkJoin([ + this.policyEngineService.getVirtualUsers(this.policyId), + this.policyEngineService.policyBlock(this.policyId), + this.policyEngineService.policy(this.policyId), + ]).subscribe((value) => { + this.policy = null; + this.cdRef.detectChanges(); + this.virtualUsers = value[0]; + this.policy = value[1]; + this.policyInfo = value[2]; + this.isMultipleGroups = !!(this.policyInfo?.policyGroups && this.groups?.length); + this.userRole = this.policyInfo.userRole; + this.userGroup = this.policyInfo.userGroup?.groupLabel || this.policyInfo.userGroup?.uuid; + this.cdRef.detectChanges(); + }, (e) => { + this.loading = false; + }); + } + + public startRecord() { + this.recordingActive = true; + this._recordController?.startRecording(); + } + + public runRecord() { + this.recordingActive = true; + this._recordController?.runRecord(); + } } diff --git a/frontend/src/app/modules/policy-engine/record/record-controller/record-controller.component.html b/frontend/src/app/modules/policy-engine/record/record-controller/record-controller.component.html new file mode 100644 index 0000000000..f2d6ee381a --- /dev/null +++ b/frontend/src/app/modules/policy-engine/record/record-controller/record-controller.component.html @@ -0,0 +1,98 @@ +
+ +
+ +
+
+
+
+
+
REC
+
+
+
+ stop +
+ +
+ menu +
+
+ +
+
+ {{this.recordError}} +
+
+
{{item._index}}
+
{{item._title}}
+
{{item._time}}
+
+
+ +
+ + +
+
+
+
+
+
{{recordIndex}}/{{recordCount}}
+
+
+ +
+ play_arrow +
+
+ redo +
+ +
+ skip_next +
+
+ stop +
+
+ menu +
+
+ +
+
+ {{this.recordError}} +
+
+
{{item._index}}
+
{{item._title}}
+
{{item._time}}
+
+
+ +
+ +
+
\ No newline at end of file diff --git a/frontend/src/app/modules/policy-engine/record/record-controller/record-controller.component.scss b/frontend/src/app/modules/policy-engine/record/record-controller/record-controller.component.scss new file mode 100644 index 0000000000..725c21ffac --- /dev/null +++ b/frontend/src/app/modules/policy-engine/record/record-controller/record-controller.component.scss @@ -0,0 +1,258 @@ +.loading { + background: #fff; + position: fixed; + z-index: 101; + top: var(--header-height); + left: 0; + bottom: 0; + right: 0; + display: flex; + align-items: center; + justify-items: center; + justify-content: center; + align-content: center; +} + +.running-container, +.recording-container { + background: transparent; + position: fixed; + z-index: 160; + top: var(--header-height); + left: 0; + bottom: 0; + right: 0; + display: flex; + border-top: none !important; + pointer-events: none; +} + +.running-container { + border: 4px solid #07b907; + box-shadow: 0px -4px 0px 0px #07b907; +} + +.recording-container { + border: 4px solid #f00; + box-shadow: 0px -4px 0px 0px #f00; +} + +.running-menu, +.recording-menu { + position: absolute; + right: 0; + top: 0; + height: 40px; + background: #e1e1e1; + pointer-events: all; + display: flex; + border-bottom-left-radius: 4px; + box-shadow: rgb(0 0 0 / 25%) -1px 1px 6px 0px; + z-index: 2; + + .running-menu-label, + .recording-menu-label { + height: 40px; + min-width: 70px; + padding: 11px 12px 11px 10px; + box-sizing: border-box; + position: relative; + pointer-events: none; + + &::after{ + pointer-events: none !important; + position: absolute; + content: ""; + border-right: 1px solid #838383; + top: 8px; + bottom: 8px; + right: 4px; + width: 4px; + } + } + + .running-menu-btn, + .recording-menu-btn { + height: 40px; + width: 40px; + padding: 8px 8px 8px 8px; + box-sizing: border-box; + cursor: pointer; + + &:hover { + background: #cbcaca; + } + } +} + +.running-menu { + border-left: 1px solid #07b907; + border-bottom: 1px solid #07b907; +} + +.recording-menu { + border-left: 1px solid #f00; + border-bottom: 1px solid #f00; +} + +.recording-animation { + display: flex; + flex-direction: row; + align-items: center; + width: 100%; + pointer-events: none; + + .recording-circle { + background-color: red; + width: 1em; + height: 1em; + border-radius: 50%; + animation: ease pulse 2s infinite; + margin-right: 8px; + position: relative; + top: -1px; + } + + .recording-text { + color: #646464; + font-weight: 500; + font-size: 14px; + } + + @keyframes pulse { + 0% { + background-color: red; + } + + 50% { + background-color: #f06c6c; + } + + 100% { + background-color: red; + } + } +} + +.running-animation { + display: flex; + flex-direction: row; + align-items: center; + width: 100%; + + .running-circle { + width: 0; + height: 0; + margin-right: 8px; + position: relative; + top: -1px; + left: 4px; + border: 12px solid #07b907; + border-top-color: transparent; + border-right-color: transparent; + border-bottom-color: transparent; + border-top-width: 8px; + border-bottom-width: 8px; + border-right-width: 6px; + } + + .running-text { + color: #646464; + font-weight: 500; + font-size: 14px; + } +} + +.record-logs { + position: absolute; + top: 42px; + right: 0; + width: 325px; + background: #fff; + bottom: 30px; + pointer-events: all; + border: 1px solid #ff0000; + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + border-top-right-radius: 0px; + box-shadow: rgb(0 0 0 / 25%) -1px 1px 6px 0px; + background: linear-gradient(90deg, #eee 34px, #aaa 34px, #aaa 35px, #fff 35px); + z-index: 2; + + .record-title { + position: relative; + font-size: 14px; + padding: 18px 8px 14px 8px; + color: #646464; + font-weight: 500; + border-bottom: 1px solid #aaa; + } + + .record-item { + display: grid; + grid-template-columns: 35px auto min-content; + + .record-item-index { + padding: 2px 2px 2px 6px; + height: 20px; + } + + .record-item-method { + padding: 2px 4px 2px 8px; + height: 20px; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + + .record-item-time { + padding: 2px 4px 2px 4px; + height: 20px; + } + } + + .record-error { + border-radius: 3px; + border: 1px solid #dd0a0a; + padding: 5px; + color: #dd0a0a; + background: #fff2f2; + position: relative; + padding-left: 10px; + margin: 5px 5px 5px 40px; + box-sizing: border-box; + word-break: break-all; + } + + & .record-item[selected="true"] { + .record-item-method, + .record-item-time { + background: #e6ffe7; + } + } + + &[record-status="Error"] .record-item[selected="true"] { + .record-item-method, + .record-item-time { + background: #ffe6e6 !important; + } + } + + &[record-status="Stopped"] .record-item[selected="true"] { + .record-item-method, + .record-item-time { + background: #f6f7b3 !important; + } + } +} + +.running-overlay { + position: absolute; + left: 0; + right: 0; + bottom: 0; + top: 0; + z-index: 1; + pointer-events: all; +} \ No newline at end of file diff --git a/frontend/src/app/modules/policy-engine/record/record-controller/record-controller.component.ts b/frontend/src/app/modules/policy-engine/record/record-controller/record-controller.component.ts new file mode 100644 index 0000000000..9ed15670ea --- /dev/null +++ b/frontend/src/app/modules/policy-engine/record/record-controller/record-controller.component.ts @@ -0,0 +1,470 @@ +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; +import { Subscription } from 'rxjs'; +import { RecordService } from 'src/app/services/record.service'; +import { WebSocketService } from 'src/app/services/web-socket.service'; +import { ImportFileDialog } from '../../helpers/import-file-dialog/import-file-dialog.component'; +import { MatDialog } from '@angular/material/dialog'; +import { RecordResultDialog } from '../record-result-dialog/record-result-dialog.component'; +import { Router } from '@angular/router'; +import { ConfirmDialog } from 'src/app/modules/common/confirm-dialog/confirm-dialog.component'; + +@Component({ + selector: 'app-record-controller', + templateUrl: './record-controller.component.html', + styleUrls: ['./record-controller.component.scss'] +}) +export class RecordControllerComponent implements OnInit { + @Input('policyId') policyId!: string; + @Output('update') update = new EventEmitter(); + + @Input('active') active!: boolean; + @Output('activeChange') activeChange = new EventEmitter(); + + public loading: boolean = true; + public recording: boolean = false; + public running: boolean = false; + public recordId: string | null; + public recordItems: any[] = []; + public recordLoading: boolean = true; + public recordIndex: any; + public recordCount: any; + public recordStatus: string; + public recordError: string; + + private _showActions: boolean = false; + private _subscription = new Subscription(); + private _resultDialog: any; + private _overlay: any; + + constructor( + private wsService: WebSocketService, + private recordService: RecordService, + private router: Router, + private dialog: MatDialog + ) { + this._showActions = (localStorage.getItem('SHOW_RECORD_ACTIONS') || 'true') === 'true'; + this._overlay = localStorage.getItem('HIDE_RECORD_OVERLAY'); + } + + ngOnInit(): void { + this._subscription.add( + this.wsService.recordSubscribe((message => { + if (message.policyId === this.policyId) { + this.updateRecordLogs(message); + } + })) + ); + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes.policyId) { + if (this.policyId) { + this.loadStatus(); + } else { + this.loading = false; + } + } + } + + ngOnDestroy() { + this._subscription.unsubscribe(); + } + + public get overlay(): boolean { + return this._overlay !== this.recordId; + } + + public set overlay(value: any) { + if (this._overlay != value) { + this._overlay = value; + try { + localStorage.setItem('HIDE_RECORD_OVERLAY', String(this._overlay)); + } catch (error) { + console.error(error); + } + } + } + + public get showActions(): boolean { + return this._showActions; + } + + public set showActions(value: boolean) { + if (this._showActions != value) { + this._showActions = value; + try { + localStorage.setItem('SHOW_RECORD_ACTIONS', String(this._showActions)); + } catch (error) { + console.error(error); + } + } + } + + public startRecording() { + this.loading = true; + this.recordItems = []; + this.recordService.startRecording(this.policyId).subscribe((result) => { + this.recording = !!result; + this.updateActive(); + this.loading = false; + }, (e) => { + this.recording = false; + this.updateActive(); + this.loading = false; + }); + } + + public stopRecording() { + this.loading = true; + this.recordItems = []; + this.recordService.stopRecording(this.policyId).subscribe((fileBuffer) => { + this.recording = false; + this.running = false; + this.updateActive(); + this.loading = false; + const downloadLink = document.createElement('a'); + downloadLink.href = window.URL.createObjectURL( + new Blob([new Uint8Array(fileBuffer)], { + type: 'application/guardian-policy-record' + })); + downloadLink.setAttribute('download', `record_${Date.now()}.record`); + document.body.appendChild(downloadLink); + downloadLink.click(); + }, (e) => { + this.recording = false; + this.running = false; + this.updateActive(); + this.loading = false; + }); + } + + public runRecord() { + const dialogRef = this.dialog.open(ImportFileDialog, { + width: '500px', + autoFocus: false, + disableClose: true, + data: { + fileExtension: 'record', + label: 'Import record .record file' + } + }); + dialogRef.afterClosed().subscribe(async (arrayBuffer) => { + if (arrayBuffer) { + this.loading = true; + this.recordItems = []; + this.overlay = null; + this.recordService.runRecord(this.policyId, arrayBuffer).subscribe((result) => { + this.running = !!result; + this.updateActive(); + this.loading = false; + }, (e) => { + this.recording = false; + this.updateActive(); + this.loading = false; + }); + } else { + this.updateActive(); + } + }); + } + + public stopRunning() { + this.loading = true; + this.recordItems = []; + this.running = false; + this.recordService.stopRunning(this.policyId).subscribe((result) => { + this.running = false; + this.recordId = null; + this.updateActive(); + this.loading = false; + }, (e) => { + this.running = false; + this.recordId = null; + this.updateActive(); + this.loading = false; + }); + } + + public fastForward() { + this.recordService.fastForward(this.policyId, { + index: this.recordIndex + }).subscribe((record) => { + }, (e) => { + }); + } + + private loadStatus() { + this.loading = true; + this.recordService.getStatus(this.policyId).subscribe((record) => { + this.updateRecordLogs(record); + setTimeout(() => { + this.loading = false; + }, 500); + }, (e) => { + this.loading = false; + }); + } + + private updateRecordLogs(data: any) { + this.recording = false; + this.running = false; + this.recordId = null; + if (data) { + if (data.type === 'Running') { + this.running = true; + this.recordId = String(data.id); + this.recordIndex = data.index; + this.recordStatus = data.status; + this.recordError = data.error; + this.recordCount = data.count; + } + if (data.type === 'Recording') { + this.recording = true; + this.recordId = String(data.uuid); + this.recordIndex = -1; + this.recordStatus = data.status; + this.recordError = data.error; + } + if (this.recordStatus === 'Stopped') { + if (this.running) { + this.showResult(); + } + if (this.recording) { + this.recording = false; + this.running = false; + this.recordId = null; + } + } + if (this.recordStatus === 'Finished') { + this.recording = false; + this.running = false; + this.recordId = null; + } + } + + if (this.recording) { + this.recordLoading = true; + this.recordService.getRecordedActions(this.policyId).subscribe((items) => { + this.recordItems = items || []; + this.updateActionItems(); + this.recordLoading = false; + }, (e) => { + this.recordLoading = false; + }); + } + if (this.running) { + this.recordLoading = true; + this.recordService.getRunActions(this.policyId).subscribe((items) => { + this.recordItems = items || []; + this.updateActionItems(); + this.recordLoading = false; + }, (e) => { + this.recordLoading = false; + }); + } + + if (this.running) { + this.updatePolicy(); + } + + this.updateActive(); + } + + private getActionUser(item: any, userMap: Map): string { + if (item.method === 'START') { + const name = 'Administrator'; + userMap.set(item.user, name); + return name; + } + if (item.action === 'CREATE_USER') { + const name = `Virtual User ${userMap.size}`; + userMap.set(item.user, name); + return name; + } + if (userMap.has(item.user)) { + return userMap.get(item.user) as string; + } else { + return item.user; + } + } + + private getActionTitle(item: any, user: string): string { + if (item.method === 'START') { + return 'Start'; + } + if (item.method === 'STOP') { + return 'Stop'; + } + if (item.method === 'GENERATE') { + if (item.action === 'GENERATE_UUID') { + return 'Generate UUID'; + } + if (item.action === 'GENERATE_DID') { + return 'Generate DID'; + } + return 'Generate'; + } + if (item.method === 'ACTION') { + if (item.action === 'SELECT_POLICY_GROUP') { + return 'Select group'; + } + if (item.action === 'SET_BLOCK_DATA') { + if (item.target) { + return `Send data (${item.target})`; + } + return 'Send data'; + } + if (item.action === 'SET_EXTERNAL_DATA') { + return 'Send external data'; + } + if (item.action === 'CREATE_USER') { + return `Create user (${user})`; + } + if (item.action === 'SET_USER') { + return `Select user (${user})`; + } + return 'Action'; + } + return item.action || item.method; + } + + private getActionTooltip(item: any, user: string): string { + let tooltip = ''; + if (item.method) { + tooltip += `Method: ${item.method}\r\n`; + } + if (item.action) { + tooltip += `Action: ${item.action}\r\n`; + } + if (item.user) { + tooltip += `User: ${item.user}\r\n`; + } + if (user) { + tooltip += `User Name: ${user}\r\n`; + } + if (item.target) { + tooltip += `Target: ${item.target}\r\n`; + } + return tooltip; + } + + private updateActionItems(): void { + const start = this.recordItems[0]; + const startTime = start?.time; + const userMap = new Map(); + const lastIndex = Math.min(this.recordIndex, this.recordItems.length - 1); + for (let index = 0; index < this.recordItems.length; index++) { + const item = this.recordItems[index]; + const user = this.getActionUser(item, userMap); + item._time = this.convertMsToTime(item.time - startTime); + item._index = index + 1; + item._selected = index === lastIndex; + item._title = this.getActionTitle(item, user); + item._tooltip = this.getActionTooltip(item, user); + } + } + + private convertMsToTime(milliseconds: number): string { + if (Number.isNaN(milliseconds)) { + return '' + } + let seconds = Math.floor(milliseconds / 1000); + let minutes = Math.floor(seconds / 60); + let hours = Math.floor(minutes / 60); + + seconds = seconds % 60; + minutes = minutes % 60; + + return `${hours}:${this.padTo2Digits(minutes)}:${this.padTo2Digits(seconds)}`; + } + + private padTo2Digits(num: number): string { + return num.toString().padStart(2, '0'); + } + + private updateActive() { + this.active = this.recording || this.running; + this.activeChange.emit(this.active); + } + + private updatePolicy() { + this.update.emit(); + } + + public onShowActions() { + this.showActions = !this.showActions; + } + + public showResult() { + this._resultDialog = this.dialog.open(RecordResultDialog, { + width: '700px', + panelClass: 'g-dialog', + autoFocus: false, + disableClose: true, + data: { + recordId: this.recordId, + policyId: this.policyId + } + }); + this._resultDialog.afterClosed().subscribe(async (result: any) => { + if (result === 'Details') { + this.router.navigate(['/record-results'], { + queryParams: { + type: 'policy', + policyId: this.policyId, + } + }); + } else if (result === 'Finish') { + this.stopRunning() + } else { + return; + } + }); + } + + public onOverlay() { + const dialogRef = this.dialog.open(ConfirmDialog, { + width: '360px', + data: { + title: 'Confirm', + description: `You actions in the UI may influence the policy execution replay flow, and result in errors or other discrepancies. Do you wish to continue?`, + submitButton: 'Continue', + cancelButton: 'Cancel' + }, + disableClose: true, + }); + dialogRef.afterClosed().subscribe(result => { + if (result) { + this.overlay = this.recordId; + } + }); + } + + public retryStep() { + this.loading = true; + this.recordItems = []; + this.recordService.retryStep(this.policyId).subscribe((result) => { + this.running = !!result; + this.updateActive(); + this.loading = false; + }, (e) => { + this.recording = false; + this.updateActive(); + this.loading = false; + }); + } + + public skipStep() { + this.loading = true; + this.recordItems = []; + this.recordService.skipStep(this.policyId).subscribe((result) => { + this.running = !!result; + this.updateActive(); + this.loading = false; + }, (e) => { + this.recording = false; + this.updateActive(); + this.loading = false; + }); + } +} diff --git a/frontend/src/app/modules/policy-engine/record/record-result-dialog/record-result-dialog.component.html b/frontend/src/app/modules/policy-engine/record/record-result-dialog/record-result-dialog.component.html new file mode 100644 index 0000000000..1d77d45981 --- /dev/null +++ b/frontend/src/app/modules/policy-engine/record/record-result-dialog/record-result-dialog.component.html @@ -0,0 +1,56 @@ +
+
+
+
+ close +
+
+
+ {{title}} +
+
+
+
+
+ +
+ +
+
+ Common +
+ +
+
Created documents:
+
{{info.documents}}
+
+
+
Minted tokens:
+
{{info.tokens}}
+
+
+
Total rate:
+
{{total}}%
+
+ +
+ Documents +
+
+
+
{{item.type}}
+
{{item.schema}}
+
{{item.rate}}
+ +
+
+
+ + +
+
+
+
+
\ No newline at end of file diff --git a/frontend/src/app/modules/policy-engine/record/record-result-dialog/record-result-dialog.component.scss b/frontend/src/app/modules/policy-engine/record/record-result-dialog/record-result-dialog.component.scss new file mode 100644 index 0000000000..6244d5d846 --- /dev/null +++ b/frontend/src/app/modules/policy-engine/record/record-result-dialog/record-result-dialog.component.scss @@ -0,0 +1,99 @@ +.context { + min-height: 200px; + position: relative; +} + +.loading { + background: #fff; + position: absolute; + z-index: 101; + top: 0; + left: 0; + bottom: 0; + right: 0; + display: flex; + align-items: center; + justify-items: center; + justify-content: center; + align-content: center; +} + +.results-info { + display: flex; + margin-left: 18px; + padding-bottom: 6px; + + .results-info-header { + padding-right: 16px; + font-weight: bold; + color: #686868; + width: 150px; + } +} + +.results-header { + height: 46px; + padding: 14px 0px; + box-sizing: border-box; + color: #646464; + font-weight: 500; + font-size: 16px; +} + +.results-item { + display: grid; + grid-template-columns: 30px auto 50px 130px; + padding-bottom: 6px; +} + +.document-type { + flex: auto; + text-transform: uppercase; +} + +.document-rate { + margin-left: 6px; +} + +.document-view { + width: 110px; + margin-left: 18px; + + a { + color: #2C78F6; + cursor: pointer; + text-decoration: underline; + } +} + +.results-body { + padding: 0px 0px 32px 18px; + max-height: 200px; + overflow: auto; +} + +.dialog-actions { + padding: 8px 0; + display: flex; + flex-wrap: wrap; + min-height: 52px; + align-items: center; + box-sizing: content-box; + + button { + margin-right: 8px; + } +} + +*[rate-color] { + color: #e70000; + + &[rate-color="100"], + &[rate-color="100%"] { + color: #008d0c; + } + + &[value="-"] { + color: #e70000; + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/policy-engine/record/record-result-dialog/record-result-dialog.component.ts b/frontend/src/app/modules/policy-engine/record/record-result-dialog/record-result-dialog.component.ts new file mode 100644 index 0000000000..d3c135a851 --- /dev/null +++ b/frontend/src/app/modules/policy-engine/record/record-result-dialog/record-result-dialog.component.ts @@ -0,0 +1,95 @@ +import { Component, Inject } from '@angular/core'; +import { MatDialogRef, MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog'; +import { VCViewerDialog } from 'src/app/modules/schema-engine/vc-dialog/vc-dialog.component'; +import { RecordService } from 'src/app/services/record.service'; + +/** + * Dialog for creating theme. + */ +@Component({ + selector: 'record-result-dialog', + templateUrl: './record-result-dialog.component.html', + styleUrls: ['./record-result-dialog.component.scss'] +}) +export class RecordResultDialog { + public loading: boolean = false; + public started = false; + public title: string; + public text: string; + public results: any[]; + public policyId: any; + public recordId: any; + public total: any; + public info: any; + + constructor( + public dialog: MatDialog, + public dialogRef: MatDialogRef, + private recordService: RecordService, + @Inject(MAT_DIALOG_DATA) public data: any) { + + this.title = 'Playback completed'; + this.text = 'Playback completed'; + if (data) { + this.recordId = data.recordId; + this.policyId = data.policyId; + } else { + this.recordId = null; + this.policyId = null; + } + } + + ngOnInit() { + this.started = true; + this.loading = true; + this.recordService.getRecordResults(this.policyId).subscribe((results) => { + this.total = results?.total; + this.info = results?.info; + this.results = results?.documents; + if (Array.isArray(this.results)) { + for (const item of this.results) { + if (!item.rate || item.rate === '-') { + item.rate = '0%'; + } + } + } + setTimeout(() => { + this.loading = false; + }, 500); + }, (e) => { + this.loading = false; + }); + } + + onNoClick(): void { + this.dialogRef.close(null); + } + + openDocument(item: any): void { + const document = item.document; + const title = `${item.type.toUpperCase()} Document`; + const dialogRef = this.dialog.open(VCViewerDialog, { + width: '850px', + panelClass: 'g-dialog', + disableClose: true, + data: { + id: document.id, + dryRun: true, + document: document, + title: title, + type: 'JSON', + } + }); + + dialogRef.afterClosed().subscribe(async (result) => { + }); + } + + onDetails(): void { + this.dialogRef.close('Details'); + } + + onFinish(): void { + this.dialogRef.close('Finish'); + } +} diff --git a/frontend/src/app/modules/policy-engine/record/record-results/record-results.component.html b/frontend/src/app/modules/policy-engine/record/record-results/record-results.component.html new file mode 100644 index 0000000000..b13b0fbd02 --- /dev/null +++ b/frontend/src/app/modules/policy-engine/record/record-results/record-results.component.html @@ -0,0 +1,20 @@ +
+
+ +
+ + +
+ Before starting work you need to get DID here +
+
+ + +
No results found.
+
+ + +
+ +
+
\ No newline at end of file diff --git a/frontend/src/app/modules/policy-engine/record/record-results/record-results.component.scss b/frontend/src/app/modules/policy-engine/record/record-results/record-results.component.scss new file mode 100644 index 0000000000..1f02b85fe4 --- /dev/null +++ b/frontend/src/app/modules/policy-engine/record/record-results/record-results.component.scss @@ -0,0 +1,271 @@ +.content { + position: absolute; + left: 0; + right: 0; + bottom: 0; + top: 0; + display: block; + min-width: 1600px; +} + +.loading { + background: #fff; + position: fixed; + z-index: 99; + top: var(--header-height); + left: 0; + bottom: 0; + right: 0; + display: flex; + align-items: center; + justify-items: center; + justify-content: center; + align-content: center; +} + +.not-exist { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + font-size: 20px; + color: darkgrey; +} + +.actions-container { + z-index: 2; + display: flex; + justify-content: space-between; + flex-wrap: wrap; + padding: 10px 15px 8px 24px; +} + +.actions-container { + .toolbar-btn { + margin: 15px; + min-width: 120px; + cursor: pointer; + font-weight: 500; + text-align: left; + user-select: none; + position: relative; + border-radius: 8px; + padding: 0; + font-size: 14px; + padding-left: 48px !important; + padding-right: 30px; + } + + .toolbar-btn mat-icon { + position: absolute; + left: 14px; + top: 10px; + } + + .toolbar-btn.add mat-icon { + font-size: 30px; + left: 10px; + top: 7px; + } +} + +.policy-filters { + display: flex; + align-items: center; + flex-direction: row; + justify-content: space-between; + font-size: 16px; + + &__control { + margin-left: 20px; + + &:last-child { + margin-right: 20px; + } + } + + &::ng-deep { + .mat-stroked-button { + border-radius: 25px; + font-size: 16px; + width: 180px; + height: 50px; + margin-left: 20px; + } + + .mat-stroked-button:not(.mat-button-disabled) { + color: var(--button-primary-color); + border: 1px solid var(--button-primary-color); + } + + .mat-form-field-label { + top: 50%; + margin-top: 0; + color: #9e9e9e; + } + + .mat-form-field-wrapper { + padding-bottom: 0; + } + + .mat-form-field-infix { + padding: 1.3em 0 1em 0; + border-top: unset; + font-weight: 300; + } + + .mat-select-arrow-wrapper { + transform: unset; + } + } +} + +.no-results-found { + font-size: 24px; + color: #8A8A8A; + width: 100%; + text-align: center; + margin: 20px 0; +} + +.table-container { + min-width: 1600px; + min-height: 100px; + overflow-y: auto; + position: relative; + z-index: 1; + border-top: 1px solid #eee; + max-height: calc(100vh - var(--header-height) - 170px); + + .table { + width: 100%; + } + + .mat-column-selector { + width: 16px; + max-width: 16px; + padding-left: 26px; + + mat-checkbox { + cursor: pointer; + } + } + + .mat-column-status { + padding-left: 8px; + width: 155px; + max-width: 155px; + border-right: 1px solid #dddddd; + } + + .mat-column-tokens { + padding-left: 8px; + width: 80px; + max-width: 80px; + border-right: 1px solid #dddddd; + } + + .mat-column-schemas { + padding-left: 8px; + width: 90px; + max-width: 90px; + border-right: 1px solid #dddddd; + } + + .mat-column-version { + padding-left: 8px; + min-width: 75px; + max-width: 100px; + border-right: 1px solid #dddddd; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .mat-column-topic { + padding-left: 8px; + width: 150px; + max-width: 150px; + border-right: 1px solid #dddddd; + } + + .mat-column-description { + padding-left: 8px; + max-width: calc(100vw - 1270px); + min-width: 100px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .mat-column-name { + padding-left: 24px; + width: 140px; + max-width: 190px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .mat-column-tags { + max-width: 175px; + min-width: 175px; + width: 175px; + border-right: 1px solid #dddddd; + padding-left: 10px; + } + + .mat-column-rate { + max-width: 100px; + min-width: 100px; + width: 100px; + padding-left: 10px; + } + + .status-draft { + font-weight: 500; + color: #9c27b0; + } + + .status-dry-run { + font-weight: 500; + color: #3f51b5; + } + + .status-publish { + font-weight: 500; + color: #4caf50; + } + + .status-failed { + font-weight: 500; + color: #572424; + } + + .color-field { + padding-right: 8px; + font-weight: 500; + text-align: end; + + &.item-color-green { + color: #008d0c; + } + + &.item-color-yellow { + color: #c79604; + } + + &.item-color-red { + color: #e70000; + } + } + + *[readonly="true"] { + background: #f1f1f1 !important; + + & mat-checkbox { + filter: grayscale(1) !important; + pointer-events: none !important; + } + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/policy-engine/record/record-results/record-results.component.ts b/frontend/src/app/modules/policy-engine/record/record-results/record-results.component.ts new file mode 100644 index 0000000000..496ab02bbc --- /dev/null +++ b/frontend/src/app/modules/policy-engine/record/record-results/record-results.component.ts @@ -0,0 +1,54 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ProfileService } from 'src/app/services/profile.service'; +import { forkJoin } from 'rxjs'; +import { RecordService } from 'src/app/services/record.service'; + +/** + * Component for show record results + */ +@Component({ + selector: 'app-record-results', + templateUrl: './record-results.component.html', + styleUrls: ['./record-results.component.scss'] +}) +export class RecordResultsComponent implements OnInit { + public loading: boolean = true; + public policyId: string; + public owner: any; + public results: any; + + constructor( + public recordService: RecordService, + public profileService: ProfileService, + public route: ActivatedRoute, + public router: Router, + ) { + } + + ngOnInit() { + this.loading = true; + this.route.queryParams.subscribe(queryParams => { + this.loadData(); + }); + } + + private loadData() { + this.loading = true; + this.policyId = this.route.snapshot.queryParams.policyId; + + forkJoin([ + this.profileService.getProfile(), + this.recordService.getRecordDetails(this.policyId) + ]).subscribe(([profile, results]) => { + this.owner = profile?.did; + this.results = results; + setTimeout(() => { + this.loading = false; + }, 500); + }, (e) => { + this.loading = false; + console.error(e); + }); + } +} diff --git a/frontend/src/app/modules/policy-engine/structures/interfaces/order-option.interface.ts b/frontend/src/app/modules/policy-engine/structures/interfaces/order-option.interface.ts new file mode 100644 index 0000000000..11dbb44b0d --- /dev/null +++ b/frontend/src/app/modules/policy-engine/structures/interfaces/order-option.interface.ts @@ -0,0 +1,4 @@ +export interface OrderOption { + id: string, + size: string +} \ No newline at end of file diff --git a/frontend/src/app/modules/policy-engine/structures/storage/array-property.ts b/frontend/src/app/modules/policy-engine/structures/storage/array-property.ts index a3b96b050d..595c533595 100644 --- a/frontend/src/app/modules/policy-engine/structures/storage/array-property.ts +++ b/frontend/src/app/modules/policy-engine/structures/storage/array-property.ts @@ -45,4 +45,12 @@ export class ArrayProperty { this._value.push(item); return this._value; } + + public get value(): T[] { + return this._value; + } + + public set value(value: T[]) { + this._value = value; + } } \ No newline at end of file diff --git a/frontend/src/app/modules/policy-engine/structures/storage/config-options.ts b/frontend/src/app/modules/policy-engine/structures/storage/config-options.ts index f964f1e763..9a050f4c4e 100644 --- a/frontend/src/app/modules/policy-engine/structures/storage/config-options.ts +++ b/frontend/src/app/modules/policy-engine/structures/storage/config-options.ts @@ -1,3 +1,5 @@ +import { OrderOption } from "../interfaces/order-option.interface"; +import { ArrayProperty } from "./array-property"; import { BooleanProperty } from "./boolean-prop"; import { ObjectProperty } from "./object-prop"; @@ -32,6 +34,8 @@ export class Options { private readonly _favorites: ObjectProperty; private readonly _favoritesModules: ObjectProperty; private readonly _legendActive: BooleanProperty; + private readonly _configurationOrder: ArrayProperty; + private readonly _propertiesOrder: ArrayProperty; constructor() { const prefix = 'POLICY_CONFIG_'; @@ -65,6 +69,18 @@ export class Options { this._favorites = new ObjectProperty(prefix + 'FAVORITES', {}); this._favoritesModules = new ObjectProperty(prefix + 'FAVORITES_MODULES', {}); this._legendActive = new BooleanProperty(prefix + 'LEGEND', true); + this._configurationOrder = new ArrayProperty( + prefix + 'CONFIGURATION_ORDER', + [ + { id: 'blocks', size: '225px' }, + { id: 'tree', size: 'auto' }, + { id: 'properties', size: '465px' }, + ] + ); + this._propertiesOrder = new ArrayProperty(prefix + 'PROPERTIES_ORDER', [ + { id: 'policy', size: 'auto' }, + { id: 'block', size: 'auto' }, + ]); } public load() { @@ -99,6 +115,8 @@ export class Options { this.legendActive = this._legendActive.load(); this._favorites.load(); this._favoritesModules.load(); + this._configurationOrder.load(); + this._propertiesOrder.load(); } catch (error) { console.error(error); } @@ -136,6 +154,8 @@ export class Options { this._outputsModule.save(); this._variablesModule.save(); this._legendActive.save(); + this._configurationOrder.save(); + this._propertiesOrder.save(); } catch (error) { console.error(error); } @@ -253,6 +273,14 @@ export class Options { return this._legendActive.value; } + public get configurationOrder(): OrderOption[] { + return this._configurationOrder.value; + } + + public get propertiesOrder(): OrderOption[] { + return this._propertiesOrder.value; + } + public set components(value: boolean) { if (value) { this._components.value = true; @@ -444,6 +472,18 @@ export class Options { this._legendActive.value = value; } + public set configurationOrder(value: OrderOption[]) { + if (Array.isArray(value)) { + this._configurationOrder.value = value; + } + } + + public set propertiesOrder(value: OrderOption[]) { + if (Array.isArray(value)) { + this._propertiesOrder.value = value; + } + } + public getFavorite(name: string): boolean { return !!this._favorites.get(name); } diff --git a/frontend/src/app/modules/schema-engine/geojson-type/geojson-type.component.html b/frontend/src/app/modules/schema-engine/geojson-type/geojson-type.component.html index 5a41c8545d..db69907da9 100644 --- a/frontend/src/app/modules/schema-engine/geojson-type/geojson-type.component.html +++ b/frontend/src/app/modules/schema-engine/geojson-type/geojson-type.component.html @@ -23,8 +23,8 @@ -
- + diff --git a/frontend/src/app/modules/schema-engine/geojson-type/geojson-type.component.ts b/frontend/src/app/modules/schema-engine/geojson-type/geojson-type.component.ts index 701d3e8c11..2b5602d75d 100644 --- a/frontend/src/app/modules/schema-engine/geojson-type/geojson-type.component.ts +++ b/frontend/src/app/modules/schema-engine/geojson-type/geojson-type.component.ts @@ -1,4 +1,5 @@ import { + ChangeDetectorRef, Component, Input, OnChanges, @@ -9,6 +10,7 @@ import { FormControl } from '@angular/forms'; import { GeoJsonSchema, GeoJsonType } from '@guardian/interfaces'; import ajv from 'ajv'; import { Subject } from 'rxjs'; +import { MapService } from 'src/app/services/map.service'; import { ajvSchemaValidator } from 'src/app/validators/ajv-schema.validator'; @Component({ @@ -61,7 +63,10 @@ export class GeojsonTypeComponent implements OnInit, OnChanges { isJSON: boolean = false; jsonInput: string = ''; - constructor() {} + constructor( + public mapService: MapService, + private cdkRef: ChangeDetectorRef + ) {} ngOnChanges(changes: SimpleChanges): void { if (changes?.isDisabled && !changes?.isDisabled.firstChange) { @@ -489,4 +494,9 @@ export class GeojsonTypeComponent implements OnInit, OnChanges { this.control?.patchValue({}); } } + + authFailed() { + this.mapService.mapLoaded = false; + this.cdkRef.detectChanges(); + } } diff --git a/frontend/src/app/modules/schema-engine/schema-engine.module.ts b/frontend/src/app/modules/schema-engine/schema-engine.module.ts index 2ced4cfb50..317d0d37b3 100644 --- a/frontend/src/app/modules/schema-engine/schema-engine.module.ts +++ b/frontend/src/app/modules/schema-engine/schema-engine.module.ts @@ -1,7 +1,11 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; -import { NgxMatDatetimePickerModule, NgxMatNativeDateModule, NgxMatTimepickerModule, } from '@angular-material-components/datetime-picker'; +import { + NgxMatDatetimePickerModule, + NgxMatNativeDateModule, + NgxMatTimepickerModule, +} from '@angular-material-components/datetime-picker'; import { ClipboardModule } from '@angular/cdk/clipboard'; import { CodemirrorModule } from '@ctrl/ngx-codemirror'; import { GoogleMapsModule } from '@angular/google-maps'; @@ -24,6 +28,7 @@ import { ExportSchemaDialog } from './export-schema-dialog/export-schema-dialog. import { SchemaFieldConfigurationComponent } from './schema-field-configuration/schema-field-configuration.component'; import { EnumEditorDialog } from './enum-editor-dialog/enum-editor-dialog.component'; import { CompareSchemaDialog } from './compare-schema-dialog/compare-schema-dialog.component'; +import { SchemaFormDialog } from './schema-form-dialog/schema-form-dialog.component'; import { SchemaTreeComponent } from './schema-tree/schema-tree.component'; import { CopySchemaDialog } from './copy-schema-dialog/copy-schema-dialog'; @@ -45,6 +50,7 @@ import { CopySchemaDialog } from './copy-schema-dialog/copy-schema-dialog'; CompareSchemaDialog, GeojsonTypeComponent, SchemaTreeComponent, + SchemaFormDialog ], imports: [ CommonModule, @@ -70,6 +76,7 @@ import { CopySchemaDialog } from './copy-schema-dialog/copy-schema-dialog'; VCViewerDialog, ExportSchemaDialog, SchemaFieldConfigurationComponent, + SchemaFormDialog ], }) export class SchemaEngineModule { } diff --git a/frontend/src/app/modules/schema-engine/schema-form-dialog/schema-form-dialog.component.css b/frontend/src/app/modules/schema-engine/schema-form-dialog/schema-form-dialog.component.css new file mode 100644 index 0000000000..2c0fb87761 --- /dev/null +++ b/frontend/src/app/modules/schema-engine/schema-form-dialog/schema-form-dialog.component.css @@ -0,0 +1,56 @@ +.context { + padding: 0px 50px 30px 55px; + max-height: inherit; + overflow-y: auto; + box-sizing: border-box; +} + +.mat-button-base:not([started]) { + background-color: rgba(0,0,0,.12); +} + +form { + display: flex; + flex-direction: column; + width: 452px; + height: 540px; + overflow: visible; +} + +textarea { + min-height: 100px; +} + +.switch-btn { + height: 46px; + font-weight: 500; + font-size: 20px; + color: #fff; + box-sizing: border-box; + min-width: 250px; + text-align: center; + cursor: pointer; +} + +:host ::ng-deep .cdk-drop-list-dragging *:not(.cdk-drag-placeholder) { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} + +:host .restore-data { + flex: 1.7; +} + +:host button.restore-data-button { + padding: 0 27px; + height: 43px; + background-color: unset; +} + +:host .restore-data button mat-icon { + margin-left: 5px; +} + +.g-dialog-body { + position: relative; + padding: 8px 0px 0px 0px; +} \ No newline at end of file diff --git a/frontend/src/app/modules/schema-engine/schema-form-dialog/schema-form-dialog.component.html b/frontend/src/app/modules/schema-engine/schema-form-dialog/schema-form-dialog.component.html new file mode 100644 index 0000000000..d80c331f68 --- /dev/null +++ b/frontend/src/app/modules/schema-engine/schema-form-dialog/schema-form-dialog.component.html @@ -0,0 +1,35 @@ +
+
+
+
+ close +
+
+
+ Example +
+
+ Preview +
+
+
+ Save +
+
+
+
+
+ + +
+
+
diff --git a/frontend/src/app/modules/schema-engine/schema-form-dialog/schema-form-dialog.component.ts b/frontend/src/app/modules/schema-engine/schema-form-dialog/schema-form-dialog.component.ts new file mode 100644 index 0000000000..4eba2d83ad --- /dev/null +++ b/frontend/src/app/modules/schema-engine/schema-form-dialog/schema-form-dialog.component.ts @@ -0,0 +1,52 @@ +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { DocumentGenerator, Schema } from '@guardian/interfaces'; +import { FormBuilder, FormGroup } from '@angular/forms'; + +/** + * Dialog for creating and editing schemas. + */ +@Component({ + selector: 'schema-form-dialog', + templateUrl: './schema-form-dialog.component.html', + styleUrls: ['./schema-form-dialog.component.css'] +}) +export class SchemaFormDialog { + public schema: Schema; + public started: boolean = false; + public dataForm: FormGroup; + public presetDocument: any; + public hideFields: any; + public example: boolean = false; + + constructor( + public dialogRef: MatDialogRef, + private fb: FormBuilder, + @Inject(MAT_DIALOG_DATA) public data: any + ) { + this.schema = data.schema || null; + this.example = data.example || false; + this.dataForm = fb.group({}); + this.hideFields = {}; + if (this.example) { + const presetDocument = DocumentGenerator.generateDocument(this.schema); + this.presetDocument = presetDocument; + } else { + this.presetDocument = null; + } + } + + ngOnInit(): void { + setTimeout(() => { + this.started = true; + }); + } + + onClose() { + this.dialogRef.close(null); + } + + onSave() { + this.dialogRef.close(this.dataForm?.value); + } +} diff --git a/frontend/src/app/modules/schema-engine/schema-form/schema-form.component.css b/frontend/src/app/modules/schema-engine/schema-form/schema-form.component.css index 5ddd3a3257..65b0e8ffce 100644 --- a/frontend/src/app/modules/schema-engine/schema-form/schema-form.component.css +++ b/frontend/src/app/modules/schema-engine/schema-form/schema-form.component.css @@ -4,6 +4,11 @@ form { width: 100%; } +form[example="true"] .sub-schema { + pointer-events: none !important; + opacity: 0.7 !important; +} + .group-label { color: rgba(0, 0, 0, .6); } @@ -320,13 +325,11 @@ form { } .page-btns { - display: block; width: calc(100% - 36px); display: grid; grid-template-columns: 1fr 1fr; column-gap: 10px; margin-top: 20px; - background-color: white; padding: 20px 0; position: fixed; diff --git a/frontend/src/app/modules/schema-engine/schema-form/schema-form.component.html b/frontend/src/app/modules/schema-engine/schema-form/schema-form.component.html index 8c10c59fcc..e7b0068998 100644 --- a/frontend/src/app/modules/schema-engine/schema-form/schema-form.component.html +++ b/frontend/src/app/modules/schema-engine/schema-form/schema-form.component.html @@ -1,7 +1,18 @@ - + -
+
+
* Required
{{item.description}}
-
{{item.description}} {{item.description}} @@ -21,12 +34,20 @@
{{item.unit}}
- +
- +
@@ -86,7 +107,7 @@
-
+
arrow_circle_right @@ -226,7 +247,7 @@
-
+
arrow_circle_right @@ -270,9 +291,9 @@
-
+
- +
+ this.isAllFieldsHidden(field) + ) + ) + ); + } + private createFieldControl(field: SchemaField): any { const item: any = { ...field, @@ -237,6 +251,7 @@ export class SchemaFormComponent implements OnInit { if (!field.isArray && field.isRef) { item.fields = field.fields; + item.isAllFieldsHidden = this.isAllFieldsHidden(item); item.displayRequired = item.fields.some((refField: any) => refField.required); if (field.required || item.preset) { item.control = @@ -284,17 +299,18 @@ export class SchemaFormComponent implements OnInit { item.control = new FormArray([]); item.list = []; item.fields = field.fields; + item.isAllFieldsHidden = this.isAllFieldsHidden(item); if (item.preset && item.preset.length) { for (let index = 0; index < item.preset.length; index++) { const preset = item.preset[index]; - const listItem = this.createListControl(item, preset);//todo + const listItem = this.createListControl(item, preset); item.list.push(listItem); item.control.push(listItem.control); } this.options?.updateValueAndValidity(); this.change.emit(); } else if (field.required) { - const listItem = this.createListControl(item);//todo + const listItem = this.createListControl(item); item.list.push(listItem); item.control.push(listItem.control); diff --git a/frontend/src/app/modules/schema-engine/schema-tree/schema-tree.component.ts b/frontend/src/app/modules/schema-engine/schema-tree/schema-tree.component.ts index 1f4bfa33f8..1052ff3f66 100644 --- a/frontend/src/app/modules/schema-engine/schema-tree/schema-tree.component.ts +++ b/frontend/src/app/modules/schema-engine/schema-tree/schema-tree.component.ts @@ -251,13 +251,15 @@ export class SchemaTreeComponent implements OnInit { startMove() { this.isMoving = true; - window.document.body.classList.add('cursor-grabbing'); + document.body.classList.add('inherit-cursor'); + document.body.style.cursor = 'grabbing'; } @HostListener('window:mouseup') stopMove() { this.isMoving = false; - window.document.body.classList.remove('cursor-grabbing'); + document.body.classList.remove('inherit-cursor'); + document.body.style.cursor = ''; } @HostListener('window:mousemove', ['$event']) diff --git a/frontend/src/app/services/map.service.ts b/frontend/src/app/services/map.service.ts index 5ab2f260c8..98b6e405ad 100644 --- a/frontend/src/app/services/map.service.ts +++ b/frontend/src/app/services/map.service.ts @@ -8,6 +8,16 @@ import { API_BASE_URL } from './api'; @Injectable() export class MapService { private readonly url: string = `${API_BASE_URL}/map`; + private _mapLoaded: boolean = false; + + set mapLoaded(value: boolean) { + this._mapLoaded = value; + } + + get mapLoaded() { + return this._mapLoaded; + } + constructor(private http: HttpClient) {} public getApiKey(): Observable { diff --git a/frontend/src/app/services/record.service.ts b/frontend/src/app/services/record.service.ts new file mode 100644 index 0000000000..24936e5bed --- /dev/null +++ b/frontend/src/app/services/record.service.ts @@ -0,0 +1,72 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpResponse } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { API_BASE_URL } from './api'; + +/** + * Services for working from policy and separate blocks. + */ +@Injectable() +export class RecordService { + private readonly url: string = `${API_BASE_URL}/record`; + + constructor(private http: HttpClient) { + } + + public getStatus(policyId: string): Observable { + return this.http.get(`${this.url}/${policyId}/status`); + } + + public startRecording(policyId: string): Observable { + return this.http.post(`${this.url}/${policyId}/recording/start`, null); + } + + public stopRecording(policyId: string): Observable { + return this.http.post( + `${this.url}/${policyId}/recording/stop`, + null, + { + responseType: 'arraybuffer' + }); + } + + public getRecordedActions(policyId: string): Observable { + return this.http.get(`${this.url}/${policyId}/recording/actions`); + } + + public getRunActions(policyId: string): Observable { + return this.http.get(`${this.url}/${policyId}/recording/actions`); + } + + public runRecord(policyId: string, file: any): Observable { + return this.http.post(`${this.url}/${policyId}/running/start`, file, { + headers: { + 'Content-Type': 'binary/octet-stream' + } + }); + } + + public stopRunning(policyId: string): Observable { + return this.http.post(`${this.url}/${policyId}/running/stop`, null); + } + + public getRecordResults(policyId: string): Observable { + return this.http.get(`${this.url}/${policyId}/running/results`); + } + + public getRecordDetails(policyId: string): Observable { + return this.http.get(`${this.url}/${policyId}/running/details`); + } + + public fastForward(policyId: string, data: any): Observable { + return this.http.post(`${this.url}/${policyId}/running/fast-forward`, data); + } + + public retryStep(policyId: string): Observable { + return this.http.post(`${this.url}/${policyId}/running/retry`, null); + } + + public skipStep(policyId: string): Observable { + return this.http.post(`${this.url}/${policyId}/running/skip`, null); + } +} diff --git a/frontend/src/app/services/web-socket.service.ts b/frontend/src/app/services/web-socket.service.ts index 8c3d554679..6019a5dbb5 100644 --- a/frontend/src/app/services/web-socket.service.ts +++ b/frontend/src/app/services/web-socket.service.ts @@ -35,6 +35,7 @@ export class WebSocketService { private reconnectAttempts: number = 10; /// number of connection attempts private servicesReady: Subject; private profileSubject: Subject<{ type: string, data: any }>; + private recordUpdateSubject: Subject; private blockUpdateSubject: Subject; private userInfoUpdateSubject: Subject; private taskStatusSubject: Subject; @@ -55,6 +56,7 @@ export class WebSocketService { public readonly meecoVerifyVPFailed$: Observable = this.meecoVerifyVPFailedSubject.asObservable(); constructor(private auth: AuthService, private toastr: ToastrService, private router: Router) { + this.recordUpdateSubject = new Subject(); this.blockUpdateSubject = new Subject(); this.userInfoUpdateSubject = new Subject(); this.servicesReady = new Subject(); @@ -225,6 +227,10 @@ export class WebSocketService { } this.servicesReady.next(allStatesReady); break; + case MessageAPI.UPDATE_RECORD: { + this.recordUpdateSubject.next(data); + break; + } case MessageAPI.UPDATE_EVENT: { this.blockUpdateSubject.next(data); break; @@ -313,6 +319,14 @@ export class WebSocketService { return this.blockUpdateSubject.subscribe(next, error, complete); } + public recordSubscribe( + next?: ((id: any) => void), + error?: ((error: any) => void), + complete?: (() => void) + ): Subscription { + return this.recordUpdateSubject.subscribe(next, error, complete); + } + public subscribeUserInfo( next?: ((id: any) => void), error?: ((error: any) => void), diff --git a/frontend/src/app/views/schemas/schemas.component.html b/frontend/src/app/views/schemas/schemas.component.html index 28dd89e360..e372b0acdf 100644 --- a/frontend/src/app/views/schemas/schemas.component.html +++ b/frontend/src/app/views/schemas/schemas.component.html @@ -61,29 +61,29 @@
- - -
-
+ + + + + + + +
+ + + + + + + + + + + +
+ + + +
+
diff --git a/frontend/src/app/views/schemas/schemas.component.scss b/frontend/src/app/views/schemas/schemas.component.scss index c786d8c28b..d7a0409218 100644 --- a/frontend/src/app/views/schemas/schemas.component.scss +++ b/frontend/src/app/views/schemas/schemas.component.scss @@ -33,6 +33,13 @@ a { text-decoration: underline; } +.policy-link { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + max-width: 200px; +} + .schemas-table { width: 100%; } @@ -133,24 +140,39 @@ a { color: #3f51b5; font-weight: 500; cursor: pointer; - padding-top: 2px; - padding-left: 24px; position: relative; - height: 24px; - min-width: 24px; - display: inline-block; + width: 30px; + height: 30px; + min-width: 25px; + margin-left: 6px; + border-radius: 6px; overflow: hidden; - box-sizing: border-box; -} -.btn-settings:hover { - text-decoration: underline; -} -.btn-settings mat-icon { - position: absolute; - left: 0px; - top: 2px; - font-size: 22px; + &:hover { + background: #eee; + box-shadow: 0 0 0 5px #eee; + } + + & mat-icon { + font-size: 30px; + position: absolute; + left: 0; + top: 0px; + } + + &.btn-settings-des { + color: #b5b5b5; + cursor: not-allowed; + } +} + +.commands-line { + display: flex; + align-items: center; + + &>*:not(:first-child) { + margin-left: 18px; + } } .btn-settings.btn-publish { @@ -165,11 +187,6 @@ a { color: #f44336; } -.btn-settings.btn-delete-des { - color: #b5b5b5; - cursor: not-allowed; -} - .schemas-table .mat-column-tool, .schemas-table .mat-column-policy { padding-left: 20px; @@ -214,9 +231,9 @@ a { padding-left: 10px; } -.schemas-table .mat-column-document { - width: 15px; - max-width: 15px; +.schemas-table .mat-column-menu { + width: 80px; + max-width: 80px; padding-left: 10px; border-left: 1px solid #dddddd; border-right: 1px solid #dddddd; @@ -310,4 +327,69 @@ a { .toolbar-btn[readonly="true"] { background: #b5b5b5; cursor: not-allowed; +} + + + +.schema-menu-btn { + color: #3f51b5; + user-select: none; + font-size: 16px; + position: relative; + width: 220px; + + mat-icon { + color: #3f51b5; + font-size: 26px; + top: -2px; + position: relative; + } +} + +.schema-menu-btn-del { + color: #f44336; + user-select: none; + font-size: 16px; + position: relative; + width: 220px; + + mat-icon { + color: #f44336; + font-size: 26px; + top: -2px; + position: relative; + } +} + +.schema-menu-btn-des { + color: #b5b5b5; + cursor: not-allowed; + user-select: none; + font-size: 16px; + position: relative; + width: 220px; + + mat-icon { + color: #b5b5b5; + font-size: 26px; + top: -2px; + position: relative; + } +} + +.schema-menu-delimiter { + width: 100%; + height: 10px; + position: relative; + + &::after { + content: ""; + pointer-events: none; + position: absolute; + top: 5px; + left: 20px; + right: 20px; + bottom: 0px; + border-top: 1px solid #ccc; + } } \ No newline at end of file diff --git a/frontend/src/app/views/schemas/schemas.component.ts b/frontend/src/app/views/schemas/schemas.component.ts index 485d6c508e..152bb54708 100644 --- a/frontend/src/app/views/schemas/schemas.component.ts +++ b/frontend/src/app/views/schemas/schemas.component.ts @@ -13,11 +13,12 @@ import { TagsService } from '../../services/tag.service'; import { ConfirmationDialogComponent } from '../../modules/common/confirmation-dialog/confirmation-dialog.component'; import { SchemaDialog } from '../../modules/schema-engine/schema-dialog/schema-dialog.component'; import { ImportSchemaDialog } from '../../modules/schema-engine/import-schema/import-schema-dialog.component'; +import { ExportSchemaDialog } from '../../modules/schema-engine/export-schema-dialog/export-schema-dialog.component'; +import { CompareSchemaDialog } from '../../modules/schema-engine/compare-schema-dialog/compare-schema-dialog.component'; +import { SchemaFormDialog } from '../../modules/schema-engine/schema-form-dialog/schema-form-dialog.component'; import { SetVersionDialog } from '../../modules/schema-engine/set-version-dialog/set-version-dialog.component'; import { VCViewerDialog } from '../../modules/schema-engine/vc-dialog/vc-dialog.component'; import { SchemaViewDialog } from '../../modules/schema-engine/schema-view-dialog/schema-view-dialog.component'; -import { ExportSchemaDialog } from '../../modules/schema-engine/export-schema-dialog/export-schema-dialog.component'; -import { CompareSchemaDialog } from '../../modules/schema-engine/compare-schema-dialog/compare-schema-dialog.component'; import { ModulesService } from '../../services/modules.service'; import { ToolsService } from 'src/app/services/tools.service'; import { AlertComponent, AlertType } from 'src/app/modules/common/alert/alert.component'; @@ -41,22 +42,14 @@ const policySchemaColumns: string[] = [ 'tags', 'status', 'operation', - 'export', - 'tree', - 'edit', - 'clone-schema', - 'delete', - 'document', + 'menu', ]; const moduleSchemaColumns: string[] = [ 'type', 'status', 'operation', - 'export', - 'edit', - 'delete', - 'document', + 'menu', ]; const toolSchemaColumns: string[] = [ @@ -64,11 +57,7 @@ const toolSchemaColumns: string[] = [ 'type', 'status', 'operation', - 'export', - 'tree', - 'edit', - 'delete', - 'document', + 'menu', ]; const systemSchemaColumns: string[] = [ @@ -77,9 +66,7 @@ const systemSchemaColumns: string[] = [ 'entity', 'active', 'activeOperation', - 'editSystem', - 'deleteSystem', - 'document', + 'menu', ]; const tagSchemaColumns: string[] = [ @@ -87,9 +74,7 @@ const tagSchemaColumns: string[] = [ 'owner', 'status', 'tagOperation', - 'editTag', - 'deleteTag', - 'document', + 'menu', ]; /** @@ -415,6 +400,12 @@ export class SchemaConfigComponent implements OnInit { } loader.subscribe((schemasResponse: HttpResponse) => { this.page = SchemaHelper.map(schemasResponse.body || []); + for (const element of this.page as any[]) { + element.__policyId = this.policyIdByTopic[element.topicId]; + element.__policyName = this.policyNameByTopic[element.topicId] || ' - '; + element.__toolId = this.toolIdByTopic[element.topicId]; + element.__toolName = this.toolNameByTopic[element.topicId] || ' - '; + } this.count = (schemasResponse.headers.get('X-Total-Count') || this.page.length) as number; this.loadTagsData(); }, (e) => { @@ -499,6 +490,36 @@ export class SchemaConfigComponent implements OnInit { this.page = this.page.slice(); } + public ifCanDelete(element: Schema): boolean { + if (this.type === SchemaType.System) { + return !element.readonly && !element.active; + } else { + return element.status === 'DRAFT'; + } + } + + public ifCanCopy(element: Schema): boolean { + return ( this.type === SchemaType.Policy); + } + + public ifCanExport(element: Schema): boolean { + return ( + this.type === SchemaType.Policy || + this.type === SchemaType.Module || + this.type === SchemaType.Tool + ); + } + + public ifCanEdit(element: Schema): boolean { + if (this.type === SchemaType.System) { + return !element.readonly && !element.active; + } else if (this.type === SchemaType.Tag) { + return element.status === 'DRAFT'; + } else { + return element.status === 'DRAFT' || !this.readonly; + } + } + private createSchema(schema: Schema | null): void { if (!schema) { return; @@ -768,13 +789,32 @@ export class SchemaConfigComponent implements OnInit { }); } + public onOpenConfig(element: Schema): void { + return this.onEditDocument(element); + } + + public onOpenForm(schema: Schema, example: boolean): void { + const dialogRef = this.dialog.open(SchemaFormDialog, { + width: '950px', + panelClass: 'g-dialog', + disableClose: true, + data: { schema, example } + }); + dialogRef.afterClosed().subscribe(async (exampleDate: any) => { + if(exampleDate) { + schema.setExample(exampleDate); + this.updateSchema(schema.id, schema); + } + }); + } + public onOpenDocument(element: Schema): void { const dialogRef = this.dialog.open(VCViewerDialog, { width: '850px', panelClass: 'g-dialog', disableClose: true, data: { - document: element.document, + document: element?.document, title: 'Schema', type: 'JSON', } @@ -782,7 +822,25 @@ export class SchemaConfigComponent implements OnInit { dialogRef.afterClosed().subscribe(async (result) => { }); } - public onEditDocument(element: Schema): void { + public onEditSchema(element: Schema): void { + if (this.type === SchemaType.System && !element.readonly && !element.active) { + return this.onEditDocument(element); + } + if (this.type === SchemaType.Tag && element.status === 'DRAFT') { + return this.onEditDocument(element); + } + if (element.status === 'DRAFT') { + return this.onEditDocument(element); + } + if (element.isCreator && !this.readonly) { + return this.onNewVersion(element); + } + if (!element.isCreator && !this.readonly) { + return this.onCloneSchema(element); + } + } + + private onEditDocument(element: Schema): void { const dialogRef = this.dialog.open(SchemaDialog, { width: '950px', panelClass: 'g-dialog', @@ -849,7 +907,7 @@ export class SchemaConfigComponent implements OnInit { } } - public onNewVersion(element: Schema): void { + private onNewVersion(element: Schema): void { const dialogRef = this.dialog.open(SchemaDialog, { width: '950px', panelClass: 'g-dialog', @@ -869,7 +927,7 @@ export class SchemaConfigComponent implements OnInit { }); } - public onCloneSchema(element: Schema): void { + private onCloneSchema(element: Schema): void { const newDocument: any = { ...element }; delete newDocument._id; delete newDocument.id; @@ -1017,13 +1075,6 @@ export class SchemaConfigComponent implements OnInit { }); } - public onViewSchemaTree(element: Schema): void { - this.dialog.open(SchemaTreeComponent, { - data: element, - autoFocus: false - }) - } - public onActive(element: Schema): void { this.loading = true; this.schemaService.activeSystemSchema(element.id).subscribe((res) => { @@ -1058,4 +1109,11 @@ export class SchemaConfigComponent implements OnInit { } }); } + + public onViewSchemaTree(element: Schema): void { + this.dialog.open(SchemaTreeComponent, { + data: element, + autoFocus: false + }) + } } diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss index 1bfb2b8635..62d772e2e8 100644 --- a/frontend/src/styles.scss +++ b/frontend/src/styles.scss @@ -320,6 +320,10 @@ button.mat-button { border-radius: 15px !important; } -.cursor-grabbing, .cursor-grabbing * { - cursor: grabbing !important; +.inherit-cursor * { + cursor: inherit !important; +} + +.pointer-events-children-none > * { + pointer-events: none !important; } \ No newline at end of file diff --git a/guardian-service/package.json b/guardian-service/package.json index df9a837453..5e556917c3 100644 --- a/guardian-service/package.json +++ b/guardian-service/package.json @@ -15,8 +15,8 @@ "@azure/core-rest-pipeline": "1.12.1" }, "dependencies": { - "@guardian/common": "^2.19.1", - "@guardian/interfaces": "^2.19.1", + "@guardian/common": "^2.20.0-prerelease", + "@guardian/interfaces": "^2.20.0-prerelease", "@hashgraph/sdk": "2.34.1", "@mattrglobal/jsonld-signatures-bbs": "^1.1.2", "@meeco/cryppo": "^2.0.2", @@ -96,5 +96,6 @@ "test:local": "mocha tests/**/*.test.js --exit", "test:stability": "mocha tests/stability.test.js" }, - "version": "2.19.1" + "version": "2.20.0-prerelease", + "stableVersion": "2.19.1" } diff --git a/guardian-service/src/analytics/compare/comparators/document-comparator.ts b/guardian-service/src/analytics/compare/comparators/document-comparator.ts index 2607072323..6581938106 100644 --- a/guardian-service/src/analytics/compare/comparators/document-comparator.ts +++ b/guardian-service/src/analytics/compare/comparators/document-comparator.ts @@ -1,7 +1,7 @@ import { DatabaseServer } from '@guardian/common'; import { CSV } from '../../table/csv'; import { ReportTable } from '../../table/report-table'; -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions } from '../interfaces/compare-options.interface'; import { ICompareResult } from '../interfaces/compare-result.interface'; import { IMultiCompareResult } from '../interfaces/multi-compare-result.interface'; import { IReportTable } from '../interfaces/report-table.interface'; @@ -21,15 +21,10 @@ export class DocumentComparator { * Compare Options * @private */ - private readonly options: ICompareOptions; - - constructor(options?: ICompareOptions) { - this.options = { - propLvl: 2, - childLvl: 2, - eventLvl: 2, - idLvl: 2 - } + private readonly options: CompareOptions; + + constructor(options?: CompareOptions) { + this.options = options || CompareOptions.default; } /** @@ -353,7 +348,7 @@ export class DocumentComparator { * @private * @static */ - private static async loadDocument(id: string, options: ICompareOptions): Promise { + private static async loadDocument(id: string, options: CompareOptions): Promise { let document: any; document = await DatabaseServer.getVCById(id); @@ -412,7 +407,7 @@ export class DocumentComparator { cacheDocuments: Map, cacheSchemas: Map, id: string, - options: ICompareOptions + options: CompareOptions ): Promise { if (cacheDocuments.has(id)) { return cacheDocuments.get(id); @@ -463,7 +458,7 @@ export class DocumentComparator { * @public * @static */ - public static async createModelById(id: string, options: ICompareOptions): Promise { + public static async createModelById(id: string, options: CompareOptions): Promise { const cacheDocuments = new Map(); const cacheSchemas = new Map(); const documentModel = await DocumentComparator.createDocument( diff --git a/guardian-service/src/analytics/compare/comparators/hash-comparator.ts b/guardian-service/src/analytics/compare/comparators/hash-comparator.ts index 18a359ce98..e8817982f5 100644 --- a/guardian-service/src/analytics/compare/comparators/hash-comparator.ts +++ b/guardian-service/src/analytics/compare/comparators/hash-comparator.ts @@ -7,6 +7,7 @@ import { IWeightBlock } from '../interfaces/weight-block.interface'; import { IWeightItem } from '../interfaces/weight-item.interface'; import { CompareUtils } from '../utils/utils'; import { PolicyComparator } from './policy-comparator'; +import { CompareOptions, IChildrenLvl, IEventsLvl, IIdLvl, IKeyLvl, IPropertiesLvl } from '../interfaces/compare-options.interface'; /** * Weight Types @@ -31,12 +32,14 @@ export class HashComparator { /** * Options */ - public static readonly options = { - childLvl: 2, - eventLvl: 1, - idLvl: 0, - propLvl: 2 - }; + public static readonly options = new CompareOptions( + IPropertiesLvl.All, + IChildrenLvl.All, + IEventsLvl.All, + IIdLvl.None, + IKeyLvl.Default, + null + ); /** * Create policy model by zip file diff --git a/guardian-service/src/analytics/compare/comparators/module-comparator.ts b/guardian-service/src/analytics/compare/comparators/module-comparator.ts index 171e20c389..3197e5a2a1 100644 --- a/guardian-service/src/analytics/compare/comparators/module-comparator.ts +++ b/guardian-service/src/analytics/compare/comparators/module-comparator.ts @@ -1,6 +1,6 @@ import { BlockModel } from '../models/block.model'; import { BlocksRate } from '../rates/blocks-rate'; -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions, IChildrenLvl, IEventsLvl, IIdLvl, IKeyLvl, IPropertiesLvl } from '../interfaces/compare-options.interface'; import { ReportTable } from '../../table/report-table'; import { Status } from '../types/status.type'; import { IRateMap } from '../interfaces/rate-map.interface'; @@ -17,63 +17,24 @@ import { ModuleModel } from '../models/module.model'; * Component for comparing two modules */ export class ModuleComparator { - /** - * Properties - * 0 - Don't compare - * 1 - Only simple properties - * 2 - All properties - * @private - */ - private readonly propLvl: number; - - /** - * Children - * 0 - Don't compare - * 1 - Only child blocks of the first level - * 2 - All children - * @private - */ - private readonly childLvl: number; - - /** - * Events - * 0 - Don't compare - * 1 - All events - * @private - */ - private readonly eventLvl: number; - - /** - * UUID - * 0 - Don't compare - * 1 - All UUID - * @private - */ - private readonly idLvl: number; - /** * Compare Options * @private */ - private readonly options: ICompareOptions; + private readonly options: CompareOptions; - constructor(options?: ICompareOptions) { + constructor(options?: CompareOptions) { if (options) { - this.propLvl = options.propLvl; - this.childLvl = options.childLvl; - this.eventLvl = options.eventLvl; - this.idLvl = options.idLvl; + this.options = options; } else { - this.propLvl = 2; - this.childLvl = 2; - this.eventLvl = 1; - this.idLvl = 1; - } - this.options = { - propLvl: this.propLvl, - childLvl: this.childLvl, - eventLvl: this.eventLvl, - idLvl: this.idLvl, + this.options = new CompareOptions( + IPropertiesLvl.All, + IChildrenLvl.All, + IEventsLvl.All, + IIdLvl.All, + IKeyLvl.Default, + null + ); } } @@ -319,7 +280,7 @@ export class ModuleComparator { * @param options * @private */ - private compareTree(block1: BlockModel, block2: BlockModel, options: ICompareOptions): BlocksRate { + private compareTree(block1: BlockModel, block2: BlockModel, options: CompareOptions): BlocksRate { const rate = new BlocksRate(block1, block2); rate.calc(options); if (!block1 && !block2) { @@ -363,7 +324,7 @@ export class ModuleComparator { type: Status, children1: BlockModel[], children2: BlockModel[], - options: ICompareOptions + options: CompareOptions ): BlocksRate[] { let result: IRateMap[]; if (type === Status.FULL) { @@ -391,7 +352,7 @@ export class ModuleComparator { private compareArray( children1: IWeightModel[], children2: IWeightModel[], - options: ICompareOptions + options: CompareOptions ): IRate[] { const result = MergeUtils.partlyMerge(children1, children2); const rates: IRate[] = []; diff --git a/guardian-service/src/analytics/compare/comparators/policy-comparator.ts b/guardian-service/src/analytics/compare/comparators/policy-comparator.ts index 4b65ecbc79..f89648681b 100644 --- a/guardian-service/src/analytics/compare/comparators/policy-comparator.ts +++ b/guardian-service/src/analytics/compare/comparators/policy-comparator.ts @@ -1,7 +1,7 @@ import { DatabaseServer } from '@guardian/common'; import { CSV } from '../../table/csv'; import { ReportTable } from '../../table/report-table'; -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions, IChildrenLvl, IEventsLvl, IIdLvl, IKeyLvl, IPropertiesLvl } from '../interfaces/compare-options.interface'; import { ICompareResult } from '../interfaces/compare-result.interface'; import { IMultiCompareResult } from '../interfaces/multi-compare-result.interface'; import { IRate } from '../interfaces/rate.interface'; @@ -20,63 +20,24 @@ import { CompareUtils } from '../utils/utils'; * Component for comparing two policies */ export class PolicyComparator { - /** - * Properties - * 0 - Don't compare - * 1 - Only simple properties - * 2 - All properties - * @private - */ - private readonly propLvl: number; - - /** - * Children - * 0 - Don't compare - * 1 - Only child blocks of the first level - * 2 - All children - * @private - */ - private readonly childLvl: number; - - /** - * Events - * 0 - Don't compare - * 1 - All events - * @private - */ - private readonly eventLvl: number; - - /** - * UUID - * 0 - Don't compare - * 1 - All UUID - * @private - */ - private readonly idLvl: number; - /** * Compare Options * @private */ - private readonly options: ICompareOptions; + private readonly options: CompareOptions; - constructor(options?: ICompareOptions) { + constructor(options?: CompareOptions) { if (options) { - this.propLvl = options.propLvl; - this.childLvl = options.childLvl; - this.eventLvl = options.eventLvl; - this.idLvl = options.idLvl; + this.options = options; } else { - this.propLvl = 2; - this.childLvl = 2; - this.eventLvl = 1; - this.idLvl = 1; - } - this.options = { - propLvl: this.propLvl, - childLvl: this.childLvl, - eventLvl: this.eventLvl, - idLvl: this.idLvl, + this.options = new CompareOptions( + IPropertiesLvl.All, + IChildrenLvl.All, + IEventsLvl.All, + IIdLvl.All, + IKeyLvl.Default, + null + ); } } @@ -555,7 +516,7 @@ export class PolicyComparator { * @public * @static */ - public static async createModelById(policyId: string, options: ICompareOptions): Promise { + public static async createModelById(policyId: string, options: CompareOptions): Promise { //Policy const policy = await DatabaseServer.getPolicyById(policyId); diff --git a/guardian-service/src/analytics/compare/comparators/record-comparator.ts b/guardian-service/src/analytics/compare/comparators/record-comparator.ts new file mode 100644 index 0000000000..69a2626838 --- /dev/null +++ b/guardian-service/src/analytics/compare/comparators/record-comparator.ts @@ -0,0 +1,467 @@ +import { DatabaseServer, IRecordResult } from '@guardian/common'; +import { CSV } from '../../table/csv'; +import { ReportTable } from '../../table/report-table'; +import { CompareOptions } from '../interfaces/compare-options.interface'; +import { ICompareResult } from '../interfaces/compare-result.interface'; +import { IMultiCompareResult } from '../interfaces/multi-compare-result.interface'; +import { IReportTable } from '../interfaces/report-table.interface'; +import { DocumentModel, VcDocumentModel, VpDocumentModel } from '../models/document.model'; +import { SchemaModel } from '../models/schema.model'; +import { DocumentsRate } from '../rates/documents-rate'; +import { ComparePolicyUtils } from '../utils/compare-policy-utils'; +import { MultiCompareUtils } from '../utils/multi-compare-utils'; +import { CompareUtils } from '../utils/utils'; +import { IRate } from '../interfaces/rate.interface'; +import { RecordModel } from '../models/record.model'; +import { RecordRate } from '../rates/record-rate'; + +/** + * Component for comparing two record + */ +export class RecordComparator { + /** + * Compare Options + * @private + */ + private readonly options: CompareOptions; + + constructor(options?: CompareOptions) { + this.options = options || CompareOptions.default; + } + + /** + * Convert tree to table + * @param tree + * @param table + * @param lvl + * @private + */ + private documentToTable(tree: DocumentsRate, table: ReportTable, lvl: number): void { + const leftItem = tree.left; + const rightItem = tree.right; + const row = table.createRow(); + + row.set('lvl', lvl); + row.set('type', tree.type); + row.set('document_type', tree.documentType); + row.set('document_schema', tree.schema); + + row.setArray('documents', tree.getSubRate(DocumentsRate.DOCUMENTS_RATE)); + row.setArray('options', tree.getSubRate(DocumentsRate.OPTIONS_RATE)); + + row.set('left', leftItem?.toObject()); + row.set('right', rightItem?.toObject()); + + if (leftItem) { + row.set('left_id', leftItem.id); + row.set('left_message_id', leftItem.messageId); + row.set('left_type', leftItem.type); + row.set('left_schema', leftItem.title()); + row.set('left_owner', leftItem.owner); + } + if (rightItem) { + row.set('right_id', rightItem.id); + row.set('right_message_id', rightItem.messageId); + row.set('right_type', rightItem.type); + row.set('right_schema', rightItem.title()); + row.set('right_owner', rightItem.owner); + } + if (leftItem && rightItem) { + row.set('document_rate', `${tree.getRateValue(DocumentsRate.DOCUMENTS_RATE)}%`); + row.set('options_rate', `${tree.getRateValue(DocumentsRate.OPTIONS_RATE)}%`); + row.set('total_rate', `${tree.getRateValue(DocumentsRate.TOTAL_RATE)}%`); + } else { + row.set('document_rate', `-`); + row.set('options_rate', `-`); + row.set('total_rate', `-`); + } + + for (const child of tree.getChildren()) { + this.documentToTable(child, table, lvl + 1); + } + } + + /** + * Convert tree to table + * @param tree + * @param table + * @param lvl + * @private + */ + private recordToTable(tree: RecordRate, table: ReportTable, lvl: number): void { + const leftItem = tree.left; + const rightItem = tree.right; + const row = table.createRow(); + + row.set('lvl', lvl); + row.set('left', leftItem?.toObject()); + row.set('right', rightItem?.toObject()); + + if (leftItem && rightItem) { + row.set('total_rate', `${tree.getRateValue(DocumentsRate.TOTAL_RATE)}%`); + } else { + row.set('total_rate', `-`); + } + + for (const child of tree.getChildren()) { + this.documentToTable(child, table, lvl + 1); + } + } + + /** + * Compare two documents + * @param document1 - left document + * @param document2 - right document + * @private + */ + private compareTwoDocuments( + document1: RecordModel, + document2: RecordModel + ): ICompareResult { + const columns = [ + { name: 'type', label: '', type: 'string' }, + { name: 'lvl', label: 'Offset', type: 'number' }, + { name: 'document_type', label: '', type: 'string' }, + { name: 'document_schema', label: '', type: 'string' }, + + { name: 'left_id', label: 'ID', type: 'string' }, + { name: 'left_message_id', label: 'Message', type: 'string' }, + { name: 'left_type', label: 'Type', type: 'string' }, + { name: 'left_schema', label: 'Schema', type: 'string' }, + { name: 'left_owner', label: 'Owner', type: 'string' }, + + { name: 'right_id', label: 'ID', type: 'string' }, + { name: 'right_message_id', label: 'Message', type: 'string' }, + { name: 'right_type', label: 'Type', type: 'string' }, + { name: 'right_schema', label: 'Schema', type: 'string' }, + { name: 'right_owner', label: 'Owner', type: 'string' }, + + { name: 'document_rate', label: 'Document Rate', type: 'number', display: 'Rate' }, + { name: 'options_rate', label: 'Options Rate', type: 'number', display: 'Rate' }, + { name: 'total_rate', label: 'Total Rate', type: 'number', display: 'Rate' }, + + { name: 'left', label: '', type: 'object' }, + { name: 'right', label: '', type: 'object' }, + { name: 'documents', label: '', type: 'object' }, + { name: 'options', label: '', type: 'object' } + ]; + + const tree = ComparePolicyUtils.compareRecord(document1, document2, this.options); + const table = new ReportTable(columns); + + this.recordToTable(tree, table, 1); + + const fields = ComparePolicyUtils.rateToTable(tree); + const fieldsRate = this.total(fields); + + const result: ICompareResult = { + left: document1.info(), + right: document2.info(), + total: fieldsRate, + documents: { + columns, + report: table.object(), + } + } + return result; + } + + /** + * Compare documents + * @param policies + * @public + */ + public compare(documents: RecordModel[]): any[] { + const left = documents[0]; + const rights = documents.slice(1); + const results: any[] = []; + for (const right of rights) { + const result = this.compareTwoDocuments(left, right); + results.push(result); + } + return results; + } + + /** + * Convert result to CSV + * @param results + * @public + */ + public static tableToCsv(results: ICompareResult[]): string { + const csv = new CSV(); + + csv.add('Document 1').addLine(); + csv + .add('Document ID') + .add('Document Type') + .add('Document Owner') + .add('Policy') + .addLine(); + csv + .add(results[0].left.id) + .add(results[0].left.type) + .add(results[0].left.owner) + .add(results[0].left.policy) + .addLine(); + + for (let i = 0; i < results.length; i++) { + const result = results[i]; + csv.addLine(); + csv.add(`Document ${i + 2}`).addLine(); + csv + .add('Document ID') + .add('Document Type') + .add('Document Owner') + .add('Policy') + .addLine(); + csv + .add(result.right.id) + .add(result.right.type) + .add(result.right.owner) + .add(result.right.policy) + .addLine(); + csv.addLine(); + + csv.add('Data').addLine(); + CompareUtils.tableToCsv(csv, result.documents); + csv.addLine(); + + csv.add('Total') + .add(result.total + '%') + .addLine(); + } + + return csv.result(); + } + + /** + * Merge compare results + * @param results + * @public + */ + public mergeCompareResults(results: ICompareResult[]): IMultiCompareResult { + const documentsTable = this.mergeDocumentTables(results.map(r => r.documents)); + const multiResult: IMultiCompareResult = { + size: results.length + 1, + left: results[0].left, + rights: results.map(r => r.right), + totals: results.map(r => r.total), + documents: documentsTable + }; + return multiResult; + } + + /** + * Merge documents tables + * @param rates + * @private + */ + private mergeDocumentTables(tables: IReportTable[]): IReportTable { + const documentColumns: any[] = [ + { name: 'lvl', label: 'Offset', type: 'number' }, + { name: 'document_type', label: '', type: 'string' }, + { name: 'document_schema', label: '', type: 'string' }, + { name: 'left', label: '', type: 'object' }, + { name: 'left_id', label: 'ID', type: 'string' }, + { name: 'left_message_id', label: 'Message', type: 'string' }, + { name: 'left_type', label: 'Type', type: 'string' }, + { name: 'left_schema', label: 'Schema', type: 'string' }, + { name: 'left_owner', label: 'Owner', type: 'string' }, + { name: 'documents', label: '', type: 'object' }, + { name: 'options', label: '', type: 'object' }, + ]; + for (let index = 0; index < tables.length; index++) { + const i = index + 1; + documentColumns.push({ name: `type_${i}`, label: '', type: 'string' }); + documentColumns.push({ name: `right_${i}`, label: '', type: 'object' }); + documentColumns.push({ name: `right_id_${i}`, label: 'ID', type: 'string' }); + documentColumns.push({ name: `right_message_id_${i}`, label: 'Message', type: 'string' }); + documentColumns.push({ name: `right_type_${i}`, label: 'Type', type: 'string' }); + documentColumns.push({ name: `right_schema_${i}`, label: 'Schema', type: 'string' }); + documentColumns.push({ name: `right_owner_${i}`, label: 'Owner', type: 'string' }); + documentColumns.push({ name: `document_rate_${i}`, label: 'Document Rate', type: 'number' }); + documentColumns.push({ name: `options_rate_${i}`, label: 'Options Rate', type: 'number' }); + documentColumns.push({ name: `total_rate_${i}`, label: 'Total Rate', type: 'number' }); + } + const mergeResults = MultiCompareUtils.mergeTables(tables); + const table: any[] = []; + for (const mergeResult of mergeResults) { + const cols = mergeResult.cols; + const size = cols.length - 1; + const row: any = { size }; + for (let index = 0; index < cols.length; index++) { + const colData = cols[index]; + if (colData) { + if (index === 0) { + row[`lvl`] = colData.lvl; + row[`document_type`] = colData.document_type; + row[`document_schema`] = colData.document_schema; + row[`left`] = colData.left; + row[`left_id`] = colData.left_id; + row[`left_message_id`] = colData.left_message_id; + row[`left_type`] = colData.left_type; + row[`left_schema`] = colData.left_schema; + row[`left_owner`] = colData.left_owner; + } else { + row[`lvl`] = colData.lvl; + row[`document_type`] = colData.document_type; + row[`document_schema`] = colData.document_schema; + row[`type_${index}`] = colData.type; + row[`right_${index}`] = colData.right; + row[`right_id_${index}`] = colData.right_id; + row[`right_message_id_${index}`] = colData.right_message_id; + row[`right_type_${index}`] = colData.right_type; + row[`right_schema_${index}`] = colData.right_schema; + row[`right_owner_${index}`] = colData.right_owner; + row[`document_rate_${index}`] = colData.document_rate; + row[`options_rate_${index}`] = colData.options_rate; + row[`total_rate_${index}`] = colData.total_rate; + } + } + } + this.mergeRateTables(row, cols, 'documents'); + this.mergeRateTables(row, cols, 'options'); + table.push(row); + } + return { + columns: documentColumns, + report: table, + } + } + + /** + * Merge Rates + * @param rates + * @private + */ + private mergeRateTables(row: any, cols: any[], propName: string): any { + row[propName] = []; + const data: any[] = []; + for (const colData of cols) { + if (colData) { + data.push(colData[propName]); + } else { + data.push(null); + } + } + const mergeResults = MultiCompareUtils.mergeRates(data); + for (const mergeResult of mergeResults) { + const propRow: any[] = mergeResult.cols.slice(); + row[propName].push(propRow); + } + return row; + } + + /** + * Calculate total rate + * @param rates + * @private + */ + private total(rates: IRate[]): number { + let total = 0; + for (let index = 1; index < rates.length; index++) { + const child = rates[index]; + if (child.totalRate > 0) { + total += child.totalRate; + } + } + if (rates.length - 1 > 0) { + return Math.floor(total / (rates.length - 1)); + } + return 100; + } + + /** + * Create document model + * @param cacheDocuments + * @param cacheSchemas + * @param id + * @param options + * @private + * @static + */ + private static async loadSchemas( + document: DocumentModel, + cacheSchemas: Map, + options: CompareOptions + ): Promise { + const schemaModels: SchemaModel[] = []; + const schemasIds = document.getSchemas(); + for (const schemasId of schemasIds) { + const iri = schemasId?.replace('schema#', '#'); + if (cacheSchemas.has(schemasId)) { + const schemaModel = cacheSchemas.get(schemasId); + if (schemaModel) { + schemaModels.push(schemaModel); + } + } else if (cacheSchemas.has(iri)) { + const schemaModel = cacheSchemas.get(iri); + if (schemaModel) { + schemaModels.push(schemaModel); + } + } else { + const schema = schemasId.startsWith('schema#') ? + await DatabaseServer.getSchema({ iri }) : + await DatabaseServer.getSchema({ contextURL: schemasId }); + if (schema) { + const schemaModel = new SchemaModel(schema, options); + schemaModel.update(options); + schemaModels.push(schemaModel); + cacheSchemas.set(schemasId, schemaModel); + } else { + cacheSchemas.set(schemasId, null); + } + } + } + return schemaModels; + } + + /** + * Create policy model + * @param documents + * @param options + * @public + * @static + */ + public static async createModel(documents: IRecordResult[], options: CompareOptions): Promise { + const model = new RecordModel(options); + model.setDocuments(documents); + + const children: DocumentModel[] = []; + const cacheSchemas = new Map(); + for (const document of documents) { + if (document.type === 'schema') { + const schemaModel = SchemaModel.from(document.document, options); + schemaModel.update(options); + cacheSchemas.set(document.id, schemaModel); + } + } + for (let index = 0; index < documents.length; index++) { + const document = documents[index]; + if (document.type === 'vc') { + const child = VcDocumentModel.from(document.document, options); + const schemaModels = await RecordComparator.loadSchemas(child, cacheSchemas, options); + child.setAttributes(index); + child.setRelationships([]); + child.setSchemas(schemaModels); + child.update(options); + children.push(child); + } + if (document.type === 'vp') { + const child = VpDocumentModel.from(document.document, options); + const schemaModels = await RecordComparator.loadSchemas(child, cacheSchemas, options); + child.setAttributes(index); + child.setRelationships([]); + child.setSchemas(schemaModels); + child.update(options); + children.push(child); + } + } + model.setChildren(children); + + //Compare + model.update(options); + + return model; + } +} \ No newline at end of file diff --git a/guardian-service/src/analytics/compare/comparators/schema-comparator.ts b/guardian-service/src/analytics/compare/comparators/schema-comparator.ts index adf21d2732..376c0deff0 100644 --- a/guardian-service/src/analytics/compare/comparators/schema-comparator.ts +++ b/guardian-service/src/analytics/compare/comparators/schema-comparator.ts @@ -1,4 +1,4 @@ -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions, IChildrenLvl, IEventsLvl, IIdLvl, IKeyLvl, IPropertiesLvl } from '../interfaces/compare-options.interface'; import { ReportTable } from '../../table/report-table'; import { Status } from '../types/status.type'; import { SchemaModel } from '../models/schema.model'; @@ -19,18 +19,20 @@ export class SchemaComparator { * Compare Options * @private */ - private readonly options: ICompareOptions; + private readonly options: CompareOptions; - constructor(options?: ICompareOptions) { + constructor(options?: CompareOptions) { if (options) { this.options = options; } else { - this.options = { - propLvl: 2, - childLvl: 0, - eventLvl: 0, - idLvl: 1, - } + this.options = new CompareOptions( + IPropertiesLvl.All, + IChildrenLvl.None, + IEventsLvl.None, + IIdLvl.All, + IKeyLvl.Default, + null + ); } } @@ -155,7 +157,7 @@ export class SchemaComparator { * @param options * @private */ - private compareSchemas(schema1: SchemaModel, schema2: SchemaModel, options: ICompareOptions): IRate[] { + private compareSchemas(schema1: SchemaModel, schema2: SchemaModel, options: CompareOptions): IRate[] { const fields = this.compareArray(Status.PARTLY, schema1.fields, schema2.fields, options); return fields; } @@ -167,7 +169,7 @@ export class SchemaComparator { * @param options * @private */ - private compareField(field1: FieldModel, field2: FieldModel, options: ICompareOptions): FieldsRate { + private compareField(field1: FieldModel, field2: FieldModel, options: CompareOptions): FieldsRate { const rate = new FieldsRate(field1, field2); rate.calc(options); if (!field1 && !field2) { @@ -205,7 +207,7 @@ export class SchemaComparator { type: Status, fields1: FieldModel[], fields2: FieldModel[], - options: ICompareOptions + options: CompareOptions ): FieldsRate[] { let result: IRateMap[]; if (type === Status.FULL) { diff --git a/guardian-service/src/analytics/compare/comparators/tool-comparator.ts b/guardian-service/src/analytics/compare/comparators/tool-comparator.ts index 7d58a63cfe..8e231e4955 100644 --- a/guardian-service/src/analytics/compare/comparators/tool-comparator.ts +++ b/guardian-service/src/analytics/compare/comparators/tool-comparator.ts @@ -1,5 +1,5 @@ import { DatabaseServer } from '@guardian/common'; -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions, IChildrenLvl, IEventsLvl, IIdLvl, IKeyLvl, IPropertiesLvl } from '../interfaces/compare-options.interface'; import { ToolModel } from '../models/tool.model'; import { SchemaModel } from '../models/schema.model'; import { ICompareResult } from '../interfaces/compare-result.interface'; @@ -18,63 +18,24 @@ import { MultiCompareUtils } from '../utils/multi-compare-utils'; * Component for comparing tools */ export class ToolComparator { - /** - * Properties - * 0 - Don't compare - * 1 - Only simple properties - * 2 - All properties - * @private - */ - private readonly propLvl: number; - - /** - * Children - * 0 - Don't compare - * 1 - Only child blocks of the first level - * 2 - All children - * @private - */ - private readonly childLvl: number; - - /** - * Events - * 0 - Don't compare - * 1 - All events - * @private - */ - private readonly eventLvl: number; - - /** - * UUID - * 0 - Don't compare - * 1 - All UUID - * @private - */ - private readonly idLvl: number; - /** * Compare Options * @private */ - private readonly options: ICompareOptions; + private readonly options: CompareOptions; - constructor(options?: ICompareOptions) { + constructor(options?: CompareOptions) { if (options) { - this.propLvl = options.propLvl; - this.childLvl = options.childLvl; - this.eventLvl = options.eventLvl; - this.idLvl = options.idLvl; + this.options = options; } else { - this.propLvl = 2; - this.childLvl = 2; - this.eventLvl = 1; - this.idLvl = 1; - } - this.options = { - propLvl: this.propLvl, - childLvl: this.childLvl, - eventLvl: this.eventLvl, - idLvl: this.idLvl, + this.options = new CompareOptions( + IPropertiesLvl.All, + IChildrenLvl.All, + IEventsLvl.All, + IIdLvl.All, + IKeyLvl.Default, + null + ); } } @@ -540,7 +501,7 @@ export class ToolComparator { * @public * @static */ - public static async createModelById(toolId: string, options: ICompareOptions): Promise { + public static async createModelById(toolId: string, options: CompareOptions): Promise { //Tool const tool = await DatabaseServer.getToolById(toolId); diff --git a/guardian-service/src/analytics/compare/index.ts b/guardian-service/src/analytics/compare/index.ts index 6173d17292..1abab262b4 100644 --- a/guardian-service/src/analytics/compare/index.ts +++ b/guardian-service/src/analytics/compare/index.ts @@ -1,7 +1,7 @@ export { PolicyComparator } from './comparators/policy-comparator'; export { SchemaComparator } from './comparators/schema-comparator'; export { IArtifacts } from './interfaces/artifacts.interface'; -export { ICompareOptions } from './interfaces/compare-options.interface'; +export { CompareOptions as ICompareOptions } from './interfaces/compare-options.interface'; export { ICompareResult } from './interfaces/compare-result.interface'; export { IKeyMap } from './interfaces/key-map.interface'; export { IModel } from './interfaces/model.interface'; @@ -52,7 +52,16 @@ export { ModuleComparator } from './comparators/module-comparator'; export { HashComparator } from './comparators/hash-comparator'; export { FileModel } from './models/file.model'; export { DocumentComparator } from './comparators/document-comparator'; +export { RecordComparator } from './comparators/record-comparator'; export { ToolComparator } from './comparators/tool-comparator'; export { ToolModel } from './models/tool.model'; export { DocumentModel } from './models/document.model'; -export { RateMap, RateKeyMap } from './utils/rate-map'; \ No newline at end of file +export { RateMap, RateKeyMap } from './utils/rate-map'; +export { + CompareOptions, + IChildrenLvl, + IEventsLvl, + IIdLvl, + IKeyLvl, + IPropertiesLvl +} from './interfaces/compare-options.interface'; diff --git a/guardian-service/src/analytics/compare/interfaces/compare-options.interface.ts b/guardian-service/src/analytics/compare/interfaces/compare-options.interface.ts index 114329f6ce..6a9798f734 100644 --- a/guardian-service/src/analytics/compare/interfaces/compare-options.interface.ts +++ b/guardian-service/src/analytics/compare/interfaces/compare-options.interface.ts @@ -1,37 +1,198 @@ +/** + * Properties + */ +export enum IPropertiesLvl { + None = 'None', //Don't compare + Simple = 'Simple', //Only simple properties + All = 'All' //All properties +} + +/** + * Events + */ +export enum IEventsLvl { + None = 'None', //Don't compare + All = 'All' //All events +} + +/** + * Children + */ +export enum IChildrenLvl { + None = 'None', //Don't compare + First = 'First', //Only child blocks of the first level + All = 'All' //All children +} + +/** + * UUID + * 0 - Don't compare + * 1 - All UUID + */ +export enum IIdLvl { + None = 'None', //Don't compare + All = 'All' // All UUID +} + +/** + * Key + * 0 - Default + * 1 - Description + * 2 - Title + */ +export enum IKeyLvl { + Default = 'Default', //Default + Description = 'Description', //Description + Title = 'Title' //Title +} + /** * Compare Options */ -export interface ICompareOptions { +export class CompareOptions { /** * Properties - * 0 - Don't compare - * 1 - Only simple properties - * 2 - All properties */ - propLvl: number; + public readonly propLvl: IPropertiesLvl; /** - * Events - * 0 - Don't compare - * 1 - All events + * Children */ - childLvl: number; + public readonly childLvl: IChildrenLvl; /** - * Children - * 0 - Don't compare - * 1 - Only child blocks of the first level - * 2 - All children + * Events */ - eventLvl: number; + public readonly eventLvl: IEventsLvl; /** * UUID - * 0 - Don't compare - * 1 - All UUID */ - idLvl: number; + public readonly idLvl: IIdLvl; + /** + * Key + */ + public readonly keyLvl: IKeyLvl; /** * Permissions - * 0 - User - * 1 - Standard Registry */ - owner?: string; -} + public readonly owner: string; + + constructor( + propLvl?: IPropertiesLvl | string | number | null | undefined, + childLvl?: IChildrenLvl | string | number | null | undefined, + eventLvl?: IEventsLvl | string | number | null | undefined, + idLvl?: IIdLvl | string | number | null | undefined, + keyLvl?: IKeyLvl | string | number | null | undefined, + owner?: string | null | undefined, + ) { + switch (propLvl) { + case IPropertiesLvl.None: + case '0': + case 0: { + this.propLvl = IPropertiesLvl.None; + break; + } + case IPropertiesLvl.Simple: + case '1': + case 1: { + this.propLvl = IPropertiesLvl.Simple; + break; + } + case IPropertiesLvl.All: + case '2': + case 2: { + this.propLvl = IPropertiesLvl.All; + break; + } + default: { + this.propLvl = IPropertiesLvl.All; + break; + } + } + switch (childLvl) { + case IChildrenLvl.None: + case '0': + case 0: { + this.childLvl = IChildrenLvl.None; + break; + } + case IChildrenLvl.First: + case '1': + case 1: { + this.childLvl = IChildrenLvl.First; + break; + } + case IChildrenLvl.All: + case '2': + case 2: { + this.childLvl = IChildrenLvl.All; + break; + } + default: { + this.childLvl = IChildrenLvl.All; + break; + } + } + switch (eventLvl) { + case IEventsLvl.None: + case '0': + case 0: { + this.eventLvl = IEventsLvl.None; + break; + } + case IEventsLvl.All: + case '1': + case 1: { + this.eventLvl = IEventsLvl.All; + break; + } + default: { + this.eventLvl = IEventsLvl.All; + break; + } + } + switch (idLvl) { + case IIdLvl.None: + case '0': + case 0: { + this.idLvl = IIdLvl.None; + break; + } + case IIdLvl.All: + case '1': + case 1: { + this.idLvl = IIdLvl.All; + break; + } + default: { + this.idLvl = IIdLvl.All; + break; + } + } + switch (keyLvl) { + case IKeyLvl.Default: + case '0': + case 0: { + this.keyLvl = IKeyLvl.Default; + break; + } + case IKeyLvl.Description: + case '1': + case 1: { + this.keyLvl = IKeyLvl.Description; + break; + } + case IKeyLvl.Title: + case '2': + case 2: { + this.keyLvl = IKeyLvl.Title; + break; + } + default: { + this.keyLvl = IKeyLvl.Default; + break; + } + } + this.owner = owner; + } + + public static readonly default = new CompareOptions(); +} \ No newline at end of file diff --git a/guardian-service/src/analytics/compare/interfaces/rate.interface.ts b/guardian-service/src/analytics/compare/interfaces/rate.interface.ts index 0284c6bd34..c4b929fa87 100644 --- a/guardian-service/src/analytics/compare/interfaces/rate.interface.ts +++ b/guardian-service/src/analytics/compare/interfaces/rate.interface.ts @@ -1,5 +1,5 @@ import { Status } from '../types/status.type'; -import { ICompareOptions } from './compare-options.interface'; +import { CompareOptions } from './compare-options.interface'; /** * Rate Model interface @@ -39,7 +39,7 @@ export interface IRate { * @param options - comparison options * @public */ - calc(options: ICompareOptions): void; + calc(options: CompareOptions): void; /** * Convert class to object diff --git a/guardian-service/src/analytics/compare/interfaces/weight-model.interface.ts b/guardian-service/src/analytics/compare/interfaces/weight-model.interface.ts index 9b98effa53..b651ac47da 100644 --- a/guardian-service/src/analytics/compare/interfaces/weight-model.interface.ts +++ b/guardian-service/src/analytics/compare/interfaces/weight-model.interface.ts @@ -1,4 +1,4 @@ -import { ICompareOptions } from './compare-options.interface'; +import { CompareOptions } from './compare-options.interface'; import { IModel } from './model.interface'; /** @@ -49,7 +49,7 @@ export interface IWeightModel extends IModel { * @param options - comparison options * @public */ - update(options: ICompareOptions): void; + update(options: CompareOptions): void; } /** diff --git a/guardian-service/src/analytics/compare/models/artifact.model.ts b/guardian-service/src/analytics/compare/models/artifact.model.ts index 5af56b7c8c..2a041477c4 100644 --- a/guardian-service/src/analytics/compare/models/artifact.model.ts +++ b/guardian-service/src/analytics/compare/models/artifact.model.ts @@ -1,5 +1,5 @@ import MurmurHash3 from 'imurmurhash'; -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions, IEventsLvl } from '../interfaces/compare-options.interface'; import { IWeightItem } from '../interfaces/weight-item.interface'; /** @@ -58,14 +58,14 @@ export class ArtifactModel { * @param options - comparison options * @public */ - public update(data: string, options: ICompareOptions): void { + public update(data: string, options: CompareOptions): void { const hashState = MurmurHash3(); hashState.hash(this.name); hashState.hash(this.type); hashState.hash(this.extension); hashState.hash(data); const weight = String(hashState.result()); - if (options.eventLvl > 0) { + if (options.eventLvl === IEventsLvl.All) { this._weight = weight; } else { this._weight = ''; @@ -110,7 +110,7 @@ export class ArtifactModel { * Get weight object * @public */ - public toWeight(options: ICompareOptions): IWeightItem { + public toWeight(options: CompareOptions): IWeightItem { return { weight: this._hash } diff --git a/guardian-service/src/analytics/compare/models/block.model.ts b/guardian-service/src/analytics/compare/models/block.model.ts index dc0dbe5564..fc0f8835e3 100644 --- a/guardian-service/src/analytics/compare/models/block.model.ts +++ b/guardian-service/src/analytics/compare/models/block.model.ts @@ -1,5 +1,5 @@ import MurmurHash3 from 'imurmurhash'; -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions, IChildrenLvl, IPropertiesLvl } from '../interfaces/compare-options.interface'; import { EventModel } from './event.model'; import { BlockPropertiesModel } from './block-properties.model'; import { WeightType } from '../types/weight.type'; @@ -137,7 +137,7 @@ export class BlockModel implements IWeightModel { * @param options - comparison options * @public */ - public update(options: ICompareOptions): void { + public update(options: CompareOptions): void { const weights = []; const weightMap = {}; @@ -173,9 +173,9 @@ export class BlockModel implements IWeightModel { } _children2 = String(_hashState.result()); - if (options.childLvl > 1) { + if (options.childLvl === IChildrenLvl.All) { _children = _children2; - } else if (options.childLvl > 0) { + } else if (options.childLvl === IChildrenLvl.First) { _children = _children1; } else { _children = '0'; @@ -206,36 +206,36 @@ export class BlockModel implements IWeightModel { - tag + prop + children */ weightMap[WeightType.CHILD_LVL_2] = _children2; - if (options.childLvl > 0) { + if (options.childLvl !== IChildrenLvl.None) { weightMap[WeightType.CHILD_LVL_1] = _children; weights.push(weightMap[WeightType.CHILD_LVL_1]); } - if (options.propLvl > 0) { + if (options.propLvl !== IPropertiesLvl.None) { weightMap[WeightType.PROP_LVL_1] = _tag; weights.push(weightMap[WeightType.PROP_LVL_1]); } - if (options.propLvl > 0) { + if (options.propLvl !== IPropertiesLvl.None) { weightMap[WeightType.PROP_LVL_2] = _prop; weights.push(weightMap[WeightType.PROP_LVL_2]); } - if (options.propLvl > 0 && options.childLvl > 0) { + if (options.propLvl !== IPropertiesLvl.None && options.childLvl !== IChildrenLvl.None) { weightMap[WeightType.PROP_AND_CHILD_1] = CompareUtils.aggregateHash(_tag, _children); weights.push(weightMap[WeightType.PROP_AND_CHILD_1]); } - if (options.propLvl > 0 && options.childLvl > 0) { + if (options.propLvl !== IPropertiesLvl.None && options.childLvl !== IChildrenLvl.None) { weightMap[WeightType.PROP_AND_CHILD_2] = CompareUtils.aggregateHash(_prop, _children); weights.push(weightMap[WeightType.PROP_AND_CHILD_2]); } - if (options.propLvl > 0) { + if (options.propLvl !== IPropertiesLvl.None) { weightMap[WeightType.PROP_LVL_3] = CompareUtils.aggregateHash(_tag, _prop); weights.push(weightMap[WeightType.PROP_LVL_3]); } - if (options.propLvl > 0 && options.childLvl > 0) { + if (options.propLvl !== IPropertiesLvl.None && options.childLvl !== IChildrenLvl.None) { weightMap[WeightType.PROP_AND_CHILD_3] = CompareUtils.aggregateHash(_tag, _prop, _children); weights.push(weightMap[WeightType.PROP_AND_CHILD_3]); } - if (options.propLvl > 0) { + if (options.propLvl !== IPropertiesLvl.None) { this._hash = CompareUtils.aggregateHash(_hash, _tag, _prop); } else { this._hash = CompareUtils.aggregateHash(_hash, _tag); @@ -251,7 +251,7 @@ export class BlockModel implements IWeightModel { * @param options - comparison options * @public */ - public updateEvents(map: IKeyMap, options: ICompareOptions) { + public updateEvents(map: IKeyMap, options: CompareOptions) { for (const event of this._events) { event.update(map, options); } @@ -263,7 +263,7 @@ export class BlockModel implements IWeightModel { * @param options - comparison options * @public */ - public updateArtifacts(artifacts: IArtifacts[], options: ICompareOptions) { + public updateArtifacts(artifacts: IArtifacts[], options: CompareOptions) { for (const artifact of this._artifacts) { const row = artifacts.find(e => e.uuid === artifact.uuid); if (row && row.data) { @@ -280,7 +280,7 @@ export class BlockModel implements IWeightModel { * @param options - comparison options * @public */ - public updateSchemas(schemaMap: IKeyMap, options: ICompareOptions): void { + public updateSchemas(schemaMap: IKeyMap, options: CompareOptions): void { this._prop.updateSchemas(schemaMap, options); } @@ -290,7 +290,7 @@ export class BlockModel implements IWeightModel { * @param options - comparison options * @public */ - public updateTokens(tokenMap: IKeyMap, options: ICompareOptions): void { + public updateTokens(tokenMap: IKeyMap, options: CompareOptions): void { this._prop.updateTokens(tokenMap, options); } @@ -427,7 +427,7 @@ export class BlockModel implements IWeightModel { * Get weight object * @public */ - public toWeight(options: ICompareOptions): IWeightBlock { + public toWeight(options: CompareOptions): IWeightBlock { const children: IWeightBlock[] = []; let length = 0; for (const child of this._children) { diff --git a/guardian-service/src/analytics/compare/models/document-fields.model.ts b/guardian-service/src/analytics/compare/models/document-fields.model.ts index 1aae5d8c0f..4ea018502d 100644 --- a/guardian-service/src/analytics/compare/models/document-fields.model.ts +++ b/guardian-service/src/analytics/compare/models/document-fields.model.ts @@ -1,5 +1,5 @@ -import { ICompareOptions } from '../interfaces/compare-options.interface'; -import { AnyPropertyModel, ArrayPropertyModel, ObjectPropertyModel, PropertyModel } from './property.model'; +import { CompareOptions } from '../interfaces/compare-options.interface'; +import { ArrayPropertyModel, DocumentPropertyModel, ObjectPropertyModel, PropertyModel } from './property.model'; import { SchemaModel } from './schema.model'; /** @@ -27,8 +27,6 @@ export class DocumentFieldsModel { constructor(document: any) { this.type = document.type; - this.fields = DocumentFieldsModel.createFieldsList(document); - this.schemas = DocumentFieldsModel.createSchemasList(document); if (typeof document.type === 'string') { this.type = document.type; } else if (Array.isArray(document.type)) { @@ -40,6 +38,8 @@ export class DocumentFieldsModel { this.type = document.type[0]; } } + this.fields = DocumentFieldsModel.createFieldsList(document); + this.schemas = DocumentFieldsModel.createSchemasList(document); } /** @@ -71,7 +71,7 @@ export class DocumentFieldsModel { * @param options - comparison options * @public */ - public hash(options: ICompareOptions): string { + public hash(options: CompareOptions): string { const result: string[] = []; for (const item of this.fields) { const hash = item.hash(options); @@ -88,7 +88,7 @@ export class DocumentFieldsModel { * @param options - comparison options * @public */ - public updateSchemas(schemas: SchemaModel[], options: ICompareOptions): void { + public updateSchemas(schemas: SchemaModel[], options: CompareOptions): void { for (const data of this.fields) { const path = this.getRelativePath(data); for (const schema of schemas) { @@ -106,7 +106,7 @@ export class DocumentFieldsModel { * Update all weight * @public */ - public update(options: ICompareOptions): void { + public update(options: CompareOptions): void { for (const data of this.fields) { data.update(options); } @@ -120,6 +120,28 @@ export class DocumentFieldsModel { return this.fields.slice(); } + /** + * Check context + * @param context + * @param result + * @private + * @static + */ + private static checkContext(context: string | string[], result: Set): Set { + if (context) { + if (Array.isArray(context)) { + for (const item of context) { + if (typeof item === 'string') { + result.add(item); + } + } + } else if (typeof context === 'string') { + result.add(context); + } + } + return result; + } + /** * Create schemas by JSON * @param document - json @@ -127,20 +149,23 @@ export class DocumentFieldsModel { * @static */ public static createSchemasList(document: any): string[] { - if (document && document['@context']) { - if (typeof document['@context'] === 'string') { - return [document['@context']]; - } else if (Array.isArray(document['@context'])) { - const schemas = []; - for (const id of document['@context']) { - if (typeof id === 'string') { - schemas.push(id); - } + if (!document) { + return []; + } + const list = new Set(); + DocumentFieldsModel.checkContext(document['@context'], list); + if (document.verifiableCredential) { + if (Array.isArray(document.verifiableCredential)) { + for (const vc of document.verifiableCredential) { + DocumentFieldsModel.checkContext(vc['@context'], list); } - return schemas; + } else { + const vc = document.verifiableCredential; + DocumentFieldsModel.checkContext(vc['@context'], list); } } - return []; + list.delete('https://www.w3.org/2018/credentials/v1'); + return Array.from(list); } /** @@ -152,8 +177,9 @@ export class DocumentFieldsModel { public static createFieldsList(document: any): PropertyModel[] { const list: PropertyModel[] = []; const keys = Object.keys(document); + const type = document?.type; for (const key of keys) { - DocumentFieldsModel.createField(key, document[key], 1, key, list); + DocumentFieldsModel.createField(key, document[key], 1, key, type, list); } return list; } @@ -173,6 +199,7 @@ export class DocumentFieldsModel { value: any, lvl: number, path: string, + type: string, fields: PropertyModel[] ): PropertyModel[] { if (value === undefined) { @@ -182,19 +209,19 @@ export class DocumentFieldsModel { if (Array.isArray(value)) { fields.push(new ArrayPropertyModel(name, value.length, lvl, path)); for (let index = 0; index < value.length; index++) { - DocumentFieldsModel.createField(String(index), value[index], lvl + 1, `${path}.${index}`, fields); + DocumentFieldsModel.createField(String(index), value[index], lvl + 1, `${path}.${index}`, type, fields); } return fields; } else { const keys = Object.keys(value); fields.push(new ObjectPropertyModel(name, !!keys.length, lvl, path)); for (const key of keys) { - DocumentFieldsModel.createField(key, value[key], lvl + 1, `${path}.${key}`, fields); + DocumentFieldsModel.createField(key, value[key], lvl + 1, `${path}.${key}`, value.type, fields); } return fields; } } else { - fields.push(new AnyPropertyModel(name, value, lvl, path)); + fields.push(new DocumentPropertyModel(name, value, lvl, path, type)); return fields; } } diff --git a/guardian-service/src/analytics/compare/models/document.model.ts b/guardian-service/src/analytics/compare/models/document.model.ts index b60431f6df..e445adafa3 100644 --- a/guardian-service/src/analytics/compare/models/document.model.ts +++ b/guardian-service/src/analytics/compare/models/document.model.ts @@ -1,5 +1,5 @@ import { VcDocument, VpDocument } from '@guardian/common'; -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions, IChildrenLvl } from '../interfaces/compare-options.interface'; import { IWeightModel } from '../interfaces/weight-model.interface'; import { IKeyMap } from '../interfaces/key-map.interface'; import { WeightType } from '../types/weight.type'; @@ -38,7 +38,7 @@ export class DocumentModel implements IWeightModel { * Compare Options * @public */ - public readonly options: ICompareOptions; + public readonly options: CompareOptions; /** * Document @@ -124,6 +124,12 @@ export class DocumentModel implements IWeightModel { */ private readonly _options: PropertiesModel; + /** + * Attributes + * @private + */ + private _attributes: any; + /** * Children * @public @@ -148,10 +154,18 @@ export class DocumentModel implements IWeightModel { return this._relationshipIds; } + /** + * Attributes + * @public + */ + public get attributes(): any { + return this._attributes; + } + constructor( type: DocumentType, document: VcDocument | VpDocument, - options: ICompareOptions + options: CompareOptions ) { this.type = type; this.options = options; @@ -169,6 +183,16 @@ export class DocumentModel implements IWeightModel { this._hash = ''; } + /** + * Set attributes + * @param schemas + * @public + */ + public setAttributes(attributes: any): DocumentModel { + this._attributes = attributes; + return this; + } + /** * Set relationship models * @param relationships @@ -197,7 +221,7 @@ export class DocumentModel implements IWeightModel { * Update all weight * @public */ - public update(options: ICompareOptions): DocumentModel { + public update(options: CompareOptions): DocumentModel { const weights = []; const weightMap = {}; const hashUtils: HashUtils = new HashUtils(); @@ -252,9 +276,9 @@ export class DocumentModel implements IWeightModel { _document = hashUtils.result(); } - if (options.childLvl > 1) { + if (options.childLvl === IChildrenLvl.All) { _children = _children2; - } else if (options.childLvl > 0) { + } else if (options.childLvl === IChildrenLvl.First) { _children = _children1; } else { _children = '0'; @@ -307,15 +331,11 @@ export class DocumentModel implements IWeightModel { * @public */ public getSchemas(): string[] { - const list = new Set(); - if (this._document) { - for (const id of this._document.schemas) { - if (id !== 'https://www.w3.org/2018/credentials/v1') { - list.add(id); - } - } + if (this._document && this._document.schemas) { + return this._document.schemas; + } else { + return []; } - return Array.from(list); } /** @@ -420,6 +440,7 @@ export class DocumentModel implements IWeightModel { key: this.key, owner: this.owner, policy: this.policy, + attributes: this.attributes, document, options } @@ -443,19 +464,21 @@ export class DocumentModel implements IWeightModel { * @public */ public title(): string { + const titles: string[] = []; if (this._schemas) { for (const schema of this._schemas) { if (schema.description) { - return schema.description; - } - } - for (const schema of this._schemas) { - if (schema.iri) { - return schema.iri; + titles.push(schema.description); + } else if (schema.iri) { + titles.push(schema.iri); } } } - return this.key; + if (titles.length) { + return titles.join(', '); + } else { + return this.key; + } } } @@ -463,7 +486,7 @@ export class DocumentModel implements IWeightModel { * VC Document Model */ export class VcDocumentModel extends DocumentModel { - constructor(vc: VcDocument, options: ICompareOptions) { + constructor(vc: VcDocument, options: CompareOptions) { super(DocumentType.VC, vc, options); this._relationshipIds = []; @@ -476,13 +499,19 @@ export class VcDocumentModel extends DocumentModel { this._key = vc.schema; } + public static from(data: any, options: CompareOptions): VcDocumentModel { + return new VcDocumentModel({ + schema: null, + document: data + } as any, options); + } } /** * VP Document Model */ export class VpDocumentModel extends DocumentModel { - constructor(vp: VpDocument, options: ICompareOptions) { + constructor(vp: VpDocument, options: CompareOptions) { super(DocumentType.VP, vp, options); this._relationshipIds = []; @@ -494,4 +523,11 @@ export class VpDocumentModel extends DocumentModel { this._key = vp.type; } + + public static from(data: any, options: CompareOptions): VpDocumentModel { + return new VpDocumentModel({ + type: null, + document: data + } as any, options); + } } \ No newline at end of file diff --git a/guardian-service/src/analytics/compare/models/event.model.ts b/guardian-service/src/analytics/compare/models/event.model.ts index 4e73d9438b..b17eb35766 100644 --- a/guardian-service/src/analytics/compare/models/event.model.ts +++ b/guardian-service/src/analytics/compare/models/event.model.ts @@ -1,6 +1,6 @@ import MurmurHash3 from 'imurmurhash'; import { BlockModel } from './block.model'; -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions, IEventsLvl } from '../interfaces/compare-options.interface'; import { IKeyMap } from '../interfaces/key-map.interface'; import { IWeightItem } from '../interfaces/weight-item.interface'; @@ -99,7 +99,7 @@ export class EventModel { * @param options - comparison options * @public */ - public update(map: IKeyMap, options: ICompareOptions): void { + public update(map: IKeyMap, options: CompareOptions): void { const source: BlockModel = map[this.source]; const target: BlockModel = map[this.target]; if (source) { @@ -124,7 +124,7 @@ export class EventModel { hashState.hash(`input:${this.input}`); hashState.hash(`output:${this.output}`); const weight = String(hashState.result()); - if (options.eventLvl > 0) { + if (options.eventLvl === IEventsLvl.All) { this._weight = weight; } else { this._weight = ''; @@ -163,7 +163,7 @@ export class EventModel { * Get weight object * @public */ - public toWeight(options: ICompareOptions): IWeightItem { + public toWeight(options: CompareOptions): IWeightItem { return { weight: this._hash } diff --git a/guardian-service/src/analytics/compare/models/field.model.ts b/guardian-service/src/analytics/compare/models/field.model.ts index d3be13d615..908c9c4953 100644 --- a/guardian-service/src/analytics/compare/models/field.model.ts +++ b/guardian-service/src/analytics/compare/models/field.model.ts @@ -1,5 +1,5 @@ import MurmurHash3 from 'imurmurhash'; -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions, IIdLvl, IPropertiesLvl } from '../interfaces/compare-options.interface'; import { IKeyMap } from '../interfaces/key-map.interface'; import { IWeightModel } from '../interfaces/weight-model.interface'; import { PropertyType } from '../types/property.type'; @@ -247,13 +247,13 @@ export class FieldModel implements IWeightModel { * @param options - comparison options * @public */ - public calcBaseWeight(options: ICompareOptions): void { + public calcBaseWeight(options: CompareOptions): void { const weights = []; const weightMap = {}; let hashState: any; - if (options.propLvl > -1) { + if (options.propLvl) { hashState = MurmurHash3(); hashState.hash(this.name); const weight = String(hashState.result()); @@ -261,7 +261,7 @@ export class FieldModel implements IWeightModel { weightMap[WeightType.SCHEMA_LVL_0] = weight; } - if (options.propLvl > 0) { + if (options.propLvl !== IPropertiesLvl.None) { hashState = MurmurHash3(); hashState.hash(this.description); const weight = String(hashState.result()); @@ -269,10 +269,10 @@ export class FieldModel implements IWeightModel { weightMap[WeightType.SCHEMA_LVL_1] = weight; } - if (options.propLvl > 0) { + if (options.propLvl !== IPropertiesLvl.None) { hashState = MurmurHash3(); hashState.hash(this.description); - if (!this.isRef && options.idLvl > 0) { + if (!this.isRef && options.idLvl === IIdLvl.All) { hashState.hash(this.type); } const weight = String(hashState.result()); @@ -280,7 +280,7 @@ export class FieldModel implements IWeightModel { weightMap[WeightType.SCHEMA_LVL_2] = weight; } - if (options.propLvl > 0 && options.idLvl > 0) { + if (options.propLvl !== IPropertiesLvl.None && options.idLvl === IIdLvl.All) { hashState = MurmurHash3(); hashState.hash(this.name + this.description); if (!this.isRef) { @@ -291,7 +291,7 @@ export class FieldModel implements IWeightModel { weightMap[WeightType.SCHEMA_LVL_3] = weight; } - if (options.propLvl > 0) { + if (options.propLvl !== IPropertiesLvl.None) { hashState = MurmurHash3(); hashState.hash( this.name + @@ -309,7 +309,7 @@ export class FieldModel implements IWeightModel { this.remoteLink + this.condition ); - if (!this.isRef && options.idLvl > 0) { + if (!this.isRef && options.idLvl === IIdLvl.All) { hashState.hash(this.type); } if (Array.isArray(this.enum)) { @@ -506,7 +506,7 @@ export class FieldModel implements IWeightModel { * @param options - comparison options * @public */ - public update(options: ICompareOptions): void { + public update(options: CompareOptions): void { if (this._subSchema) { this._subSchema.update(options); } @@ -518,7 +518,7 @@ export class FieldModel implements IWeightModel { * @param options - comparison options * @public */ - public hash(options: ICompareOptions): string { + public hash(options: CompareOptions): string { return this._weight[0]; } diff --git a/guardian-service/src/analytics/compare/models/file.model.ts b/guardian-service/src/analytics/compare/models/file.model.ts index 149636798f..aa409222f4 100644 --- a/guardian-service/src/analytics/compare/models/file.model.ts +++ b/guardian-service/src/analytics/compare/models/file.model.ts @@ -1,5 +1,5 @@ import { Artifact } from '@guardian/common'; -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions } from '../interfaces/compare-options.interface'; import MurmurHash3 from 'imurmurhash'; import * as crypto from 'crypto'; @@ -29,9 +29,9 @@ export class FileModel { * Compare Options * @private */ - private readonly options: ICompareOptions; + private readonly options: CompareOptions; - constructor(artifact: Artifact, buffer: Buffer, options: ICompareOptions) { + constructor(artifact: Artifact, buffer: Buffer, options: CompareOptions) { this.options = options; this.uuid = artifact.uuid; this.data = crypto @@ -70,7 +70,7 @@ export class FileModel { * @param options - comparison options * @public */ - public hash(options?: ICompareOptions): string { + public hash(options?: CompareOptions): string { return this._weight; } @@ -79,7 +79,7 @@ export class FileModel { * @param options - comparison options * @public */ - public update(options: ICompareOptions): void { + public update(options: CompareOptions): void { const hashState = MurmurHash3(); hashState.hash(String(this.uuid)); hashState.hash(String(this.data)); diff --git a/guardian-service/src/analytics/compare/models/group.model.ts b/guardian-service/src/analytics/compare/models/group.model.ts index 0eb57f3400..9a08ec480e 100644 --- a/guardian-service/src/analytics/compare/models/group.model.ts +++ b/guardian-service/src/analytics/compare/models/group.model.ts @@ -1,5 +1,5 @@ import MurmurHash3 from 'imurmurhash'; -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions } from '../interfaces/compare-options.interface'; import { IKeyMap } from '../interfaces/key-map.interface'; import { IWeightModel } from '../interfaces/weight-model.interface'; import { PropertyType } from '../types/property.type'; @@ -57,7 +57,7 @@ export class GroupModel implements IWeightModel { * @param options - comparison options * @public */ - public update(options: ICompareOptions): void { + public update(options: CompareOptions): void { const weights = []; const weightMap = {}; @@ -174,7 +174,7 @@ export class GroupModel implements IWeightModel { * Get weight object * @public */ - public toWeight(options: ICompareOptions): IWeightItem { + public toWeight(options: CompareOptions): IWeightItem { if (!this._weight.length) { return { weight: this.name diff --git a/guardian-service/src/analytics/compare/models/module.model.ts b/guardian-service/src/analytics/compare/models/module.model.ts index b9a400cddc..f0675886a8 100644 --- a/guardian-service/src/analytics/compare/models/module.model.ts +++ b/guardian-service/src/analytics/compare/models/module.model.ts @@ -1,6 +1,6 @@ import { PolicyModule } from '@guardian/common'; import { BlockModel } from './block.model'; -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions } from '../interfaces/compare-options.interface'; import { IKeyMap } from '../interfaces/key-map.interface'; import { PropertyModel } from './property.model'; import { PropertyType } from '../types/property.type'; @@ -57,7 +57,7 @@ export class ModuleModel { * Compare Options * @private */ - private readonly options: ICompareOptions; + private readonly options: CompareOptions; /** * All Blocks @@ -65,7 +65,7 @@ export class ModuleModel { */ private readonly _list: BlockModel[]; - constructor(policyModule: PolicyModule, options: ICompareOptions) { + constructor(policyModule: PolicyModule, options: CompareOptions) { this.options = options; this.id = policyModule.id; @@ -104,7 +104,7 @@ export class ModuleModel { * @param options - comparison options * @public */ - private updateAllBlocks(root: BlockModel, options: ICompareOptions): void { + private updateAllBlocks(root: BlockModel, options: CompareOptions): void { for (const child of root.children) { this.updateAllBlocks(child, options); } @@ -117,7 +117,7 @@ export class ModuleModel { * @param options - comparison options * @private */ - private createInputEvents(variables: any[], options: ICompareOptions): VariableModel[] { + private createInputEvents(variables: any[], options: CompareOptions): VariableModel[] { const result: VariableModel[] = []; if (Array.isArray(variables)) { for (const json of variables) { @@ -136,7 +136,7 @@ export class ModuleModel { * @param options - comparison options * @private */ - private createOutputEvents(variables: any[], options: ICompareOptions): VariableModel[] { + private createOutputEvents(variables: any[], options: CompareOptions): VariableModel[] { const result: VariableModel[] = []; if (Array.isArray(variables)) { for (const json of variables) { @@ -155,7 +155,7 @@ export class ModuleModel { * @param options - comparison options * @private */ - private createVariables(variables: any[], options: ICompareOptions): VariableModel[] { + private createVariables(variables: any[], options: CompareOptions): VariableModel[] { const result: VariableModel[] = []; if (Array.isArray(variables)) { for (const json of variables) { diff --git a/guardian-service/src/analytics/compare/models/policy.model.ts b/guardian-service/src/analytics/compare/models/policy.model.ts index 7762918798..7e70b1be51 100644 --- a/guardian-service/src/analytics/compare/models/policy.model.ts +++ b/guardian-service/src/analytics/compare/models/policy.model.ts @@ -1,6 +1,6 @@ import { Policy } from '@guardian/common'; import { BlockModel } from './block.model'; -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions } from '../interfaces/compare-options.interface'; import { SchemaModel } from './schema.model'; import { IKeyMap } from '../interfaces/key-map.interface'; import { PropertyModel } from './property.model'; @@ -81,7 +81,7 @@ export class PolicyModel { * Compare Options * @private */ - public readonly options: ICompareOptions; + public readonly options: CompareOptions; /** * All Blocks @@ -107,7 +107,7 @@ export class PolicyModel { */ private _tokens: TokenModel[]; - constructor(policy: Policy, options: ICompareOptions) { + constructor(policy: Policy, options: CompareOptions) { this.options = options; this.id = policy.id; @@ -149,7 +149,7 @@ export class PolicyModel { * @param options - comparison options * @public */ - private updateAllBlocks(root: BlockModel, options: ICompareOptions): void { + private updateAllBlocks(root: BlockModel, options: CompareOptions): void { for (const child of root.children) { this.updateAllBlocks(child, options); } @@ -162,7 +162,7 @@ export class PolicyModel { * @param options - comparison options * @private */ - private createRoles(roles: string[], options: ICompareOptions): RoleModel[] { + private createRoles(roles: string[], options: CompareOptions): RoleModel[] { const result: RoleModel[] = []; if (Array.isArray(roles)) { for (const json of roles) { @@ -180,7 +180,7 @@ export class PolicyModel { * @param options - comparison options * @private */ - private createGroups(groups: any[], options: ICompareOptions): GroupModel[] { + private createGroups(groups: any[], options: CompareOptions): GroupModel[] { const result: GroupModel[] = []; if (Array.isArray(groups)) { for (const json of groups) { @@ -198,7 +198,7 @@ export class PolicyModel { * @param options - comparison options * @private */ - private createTopics(topics: any[], options: ICompareOptions): TopicModel[] { + private createTopics(topics: any[], options: CompareOptions): TopicModel[] { const result: TopicModel[] = []; if (Array.isArray(topics)) { for (const json of topics) { @@ -216,7 +216,7 @@ export class PolicyModel { * @param options - comparison options * @private */ - private createTokens(tokens: any[], options: ICompareOptions): TemplateTokenModel[] { + private createTokens(tokens: any[], options: CompareOptions): TemplateTokenModel[] { const result: TemplateTokenModel[] = []; if (Array.isArray(tokens)) { for (const json of tokens) { diff --git a/guardian-service/src/analytics/compare/models/properties.model.ts b/guardian-service/src/analytics/compare/models/properties.model.ts index 670fefdc57..d0e85b0b5e 100644 --- a/guardian-service/src/analytics/compare/models/properties.model.ts +++ b/guardian-service/src/analytics/compare/models/properties.model.ts @@ -1,4 +1,4 @@ -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions } from '../interfaces/compare-options.interface'; import { IKeyMap } from '../interfaces/key-map.interface'; import { AnyPropertyModel, ArrayPropertyModel, ObjectPropertyModel, PropertyModel, SchemaPropertyModel, TokenPropertyModel } from './property.model'; import { PropertyType } from '../types/property.type'; @@ -28,7 +28,7 @@ export class PropertiesModel { * @param options - comparison options * @public */ - public hash(options: ICompareOptions): string { + public hash(options: CompareOptions): string { const result: string[] = []; for (const item of this.list) { const hash = item.hash(options); @@ -58,7 +58,7 @@ export class PropertiesModel { * @param options - comparison options * @public */ - public updateSchemas(schemaMap: IKeyMap, options: ICompareOptions): void { + public updateSchemas(schemaMap: IKeyMap, options: CompareOptions): void { for (const prop of this.list) { if (prop.type === PropertyType.Schema) { (prop as SchemaPropertyModel).setSchema(schemaMap[prop.value]); @@ -72,7 +72,7 @@ export class PropertiesModel { * @param options - comparison options * @public */ - public updateTokens(tokenMap: IKeyMap, options: ICompareOptions): void { + public updateTokens(tokenMap: IKeyMap, options: CompareOptions): void { for (const prop of this.list) { if (prop.type === PropertyType.Token) { (prop as TokenPropertyModel).setToken(tokenMap[prop.value]); diff --git a/guardian-service/src/analytics/compare/models/property.model.ts b/guardian-service/src/analytics/compare/models/property.model.ts index 51996a587f..b6605be39e 100644 --- a/guardian-service/src/analytics/compare/models/property.model.ts +++ b/guardian-service/src/analytics/compare/models/property.model.ts @@ -1,4 +1,4 @@ -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions, IIdLvl, IKeyLvl, IPropertiesLvl } from '../interfaces/compare-options.interface'; import { IProperties } from '../interfaces/properties.interface'; import { PropertyType } from '../types/property.type'; import { SchemaModel } from './schema.model'; @@ -93,13 +93,22 @@ export class PropertyModel implements IProperties { this._key = this.path; } + /** + * Ignore comparison + * @param options - comparison options + * @public + */ + public ignore(options: CompareOptions): boolean { + return false; + } + /** * Comparison of models using weight * @param item - model * @param options - comparison options * @public */ - public equal(item: PropertyModel, options: ICompareOptions): boolean { + public equal(item: PropertyModel, options: CompareOptions): boolean { return this.type === item.type && this._weight === item._weight; } @@ -129,8 +138,8 @@ export class PropertyModel implements IProperties { * @param options - comparison options * @public */ - public hash(options: ICompareOptions): string { - if (options.propLvl === 1) { + public hash(options: CompareOptions): string { + if (options.propLvl === IPropertiesLvl.Simple) { if (this.lvl === 1) { return `${this.path}:${this.value}`; } else { @@ -169,10 +178,10 @@ export class PropertyModel implements IProperties { * Update all weight * @public */ - public update(options: ICompareOptions): void { - if (options.idLvl === 2) { + public update(options: CompareOptions): void { + if (options.keyLvl === IKeyLvl.Description) { this._key = this._description; - } else if (options.idLvl === 3) { + } else if (options.keyLvl === IKeyLvl.Title) { this._key = this._title; } else { this._key = this.path; @@ -201,8 +210,8 @@ export class UUIDPropertyModel extends PropertyModel { * @param options - comparison options * @public */ - public hash(options: ICompareOptions): string { - if (options.idLvl === 0) { + public hash(options: CompareOptions): string { + if (options.idLvl === IIdLvl.None) { return null; } return super.hash(options); @@ -213,8 +222,8 @@ export class UUIDPropertyModel extends PropertyModel { * @param item - model * @public */ - public override equal(item: PropertyModel, options: ICompareOptions): boolean { - if (options.idLvl === 0) { + public override equal(item: PropertyModel, options: CompareOptions): boolean { + if (options.idLvl === IIdLvl.None) { return true; } else { return this.type === item.type && this.value === item.value; @@ -331,8 +340,8 @@ export class TokenPropertyModel extends PropertyModel { * @param options - comparison options * @public */ - public hash(options: ICompareOptions): string { - if (options.idLvl === 0 && this.token) { + public hash(options: CompareOptions): string { + if (options.idLvl === IIdLvl.None && this.token) { return `${this.path}:${this.token.hash(options)}`; } return super.hash(options); @@ -343,8 +352,8 @@ export class TokenPropertyModel extends PropertyModel { * @param item - model * @public */ - public override equal(item: PropertyModel, options: ICompareOptions): boolean { - if (options.idLvl === 0) { + public override equal(item: PropertyModel, options: CompareOptions): boolean { + if (options.idLvl === IIdLvl.None) { return super.equal(item, options); } else { return this.type === item.type && this.value === item.value; @@ -400,8 +409,8 @@ export class SchemaPropertyModel extends PropertyModel { * @param options - comparison options * @public */ - public hash(options: ICompareOptions): string { - if (options.idLvl === 0 && this.schema) { + public hash(options: CompareOptions): string { + if (options.idLvl === IIdLvl.None && this.schema) { return `${this.path}:${this.schema.hash(options)}`; } return super.hash(options); @@ -412,11 +421,97 @@ export class SchemaPropertyModel extends PropertyModel { * @param item - model * @public */ - public override equal(item: PropertyModel, options: ICompareOptions): boolean { - if (options.idLvl === 0) { + public override equal(item: PropertyModel, options: CompareOptions): boolean { + if (options.idLvl === IIdLvl.None) { return super.equal(item, options); } else { return this.type === item.type && this.value === item.value; } } +} + +/** + * Document Property + */ +export class DocumentPropertyModel extends PropertyModel { + /** + * Is system fields + * @public + */ + private readonly isSystem: boolean; + + constructor( + name: string, + value: any, + lvl?: number, + path?: string, + type?: string + ) { + super(name, PropertyType.Property, value, lvl, path); + this.isSystem = this.checkSystemField(name, path, type, value); + console.debug(this.isSystem, path, type); + } + + private checkSystemField( + name: string, + path: string, + type: string, + value: any + ): boolean { + try { + if ( + name === '@context' || + name === 'type' || + name === 'policyId' || + name === 'id' || + name === 'ref' || + name === 'tokenId' || + name === 'issuanceDate' || + name === 'issuer' + ) { + return true; + } + if (type === 'MintToken') { + if ( + name === 'date' + ) { + return true; + } + } + if (path && typeof path === 'string') { + if ( + path === 'proof' || + path.includes('@context') || + path.startsWith('proof.') || + path.startsWith('type.') || + path.endsWith('proof.created') || + path.endsWith('proof.jws') || + path.endsWith('proof.proofPurpose') || + path.endsWith('proof.type') || + path.endsWith('proof.verificationMethod') + ) { + return true; + } + } + if (value && typeof value === 'string') { + if ( + value.startsWith('did:hedera:') + ) { + return true; + } + } + return false; + } catch (error) { + return false; + } + } + + /** + * Ignore comparison + * @param options - comparison options + * @public + */ + public override ignore(options: CompareOptions): boolean { + return this.isSystem && options.idLvl === IIdLvl.None; + } } \ No newline at end of file diff --git a/guardian-service/src/analytics/compare/models/record.model.ts b/guardian-service/src/analytics/compare/models/record.model.ts new file mode 100644 index 0000000000..600a259a19 --- /dev/null +++ b/guardian-service/src/analytics/compare/models/record.model.ts @@ -0,0 +1,268 @@ +import { IRecordResult } from '@guardian/common'; +import { CompareOptions } from '../interfaces/compare-options.interface'; +import { IWeightModel } from '../interfaces/weight-model.interface'; +import { IKeyMap } from '../interfaces/key-map.interface'; +import { WeightType } from '../types/weight.type'; +import { DocumentModel } from './document.model'; + +/** + * Document Model + */ +export class RecordModel implements IWeightModel { + /** + * Compare Options + * @public + */ + public readonly options: CompareOptions; + + /** + * All children + * @protected + */ + protected _children: DocumentModel[]; + + /** + * Weights + * @protected + */ + protected _weight: string[]; + + /** + * Weights map by name + * @protected + */ + protected _weightMap: IKeyMap; + + /** + * Weights + * @protected + */ + protected _key: string; + + /** + * Hash + * @private + */ + private _hash: string; + + /** + * Count + * @private + */ + private _count: number; + + /** + * Tokens + * @private + */ + private _tokens: number; + + /** + * Children + * @public + */ + public get children(): DocumentModel[] { + return this._children; + } + + /** + * Model key + * @public + */ + public get key(): string { + return this._key; + } + + /** + * Count + * @public + */ + public get count(): number { + return this._count; + } + + /** + * Tokens + * @public + */ + public get tokens(): number { + return this._tokens; + } + + constructor( + options: CompareOptions + ) { + this.options = options; + + this._weight = []; + this._weightMap = {}; + this._hash = ''; + } + + private findToken(document: IRecordResult): number { + try { + const vp = document?.document; + const vcs = vp?.verifiableCredential || []; + const mintIndex = Math.max(1, vcs.length - 1); + const mint = vcs[mintIndex]; + if (mint && mint.credentialSubject) { + if (Array.isArray(mint.credentialSubject)) { + return Number(mint.credentialSubject[0].amount); + } else { + return Number(mint.credentialSubject.amount); + } + } else { + return 0; + } + } catch (error) { + return 0; + } + } + + /** + * Set documents + * @param children + * @public + */ + public setDocuments(documents: IRecordResult[]): RecordModel { + this._count = 0; + this._tokens = 0; + if (Array.isArray(documents)) { + for (const document of documents) { + if (document.type === 'vc') { + this._count++; + } + if (document.type === 'vp') { + this._count++; + this._tokens += this.findToken(document); + } + } + } + return this; + } + + /** + * Set relationship models + * @param children + * @public + */ + public setChildren(children: DocumentModel[]): RecordModel { + if (Array.isArray(children)) { + this._children = children; + } else { + this._children = []; + } + return this; + } + + /** + * Update all weight + * @public + */ + public update(options: CompareOptions): RecordModel { + const weights = []; + const weightMap = {}; + this._hash = '' + this._weightMap = weightMap; + this._weight = weights.reverse(); + return this; + } + + /** + * Compare weight + * @param doc + * @param index + * @param schema + * @private + */ + private compareWeight(doc: RecordModel, index: number): boolean { + return this._weight[index] === doc._weight[index] && this._weight[index] !== '0'; + } + + /** + * Get weight by name + * @param type - weight name + * @public + */ + public getWeight(type?: WeightType): string { + if (type) { + return this._weightMap[type]; + } else { + return this._weight[0]; + } + } + + /** + * Check weight by number + * @param index - weight index + * @public + */ + public checkWeight(index: number): boolean { + return index < this._weight.length; + } + + /** + * Get all weight + * @public + */ + public getWeights(): string[] { + return this._weight; + } + + /** + * Get weight number + * @public + */ + public maxWeight(): number { + return this._weight ? this._weight.length : 0; + } + + /** + * Comparison of models using weight + * @param item - model + * @param index - weight index + * @public + */ + public equal(doc: RecordModel, index?: number): boolean { + if (!this._weight.length) { + return this._hash === doc._hash; + } + + if (!Number.isFinite(index)) { + return this._hash === doc._hash; + } + + return this.compareWeight(doc, index); + } + + /** + * Comparison of models using key + * @param item - model + * @public + */ + public equalKey(doc: RecordModel): boolean { + return true; + } + + /** + * Convert class to object + * @public + */ + public toObject(): any { + return { + documents: this._count, + tokens: this._tokens, + } + } + + /** + * Convert class to object + * @public + */ + public info(): any { + return { + documents: this._count, + tokens: this._tokens, + } + } +} \ No newline at end of file diff --git a/guardian-service/src/analytics/compare/models/role.model.ts b/guardian-service/src/analytics/compare/models/role.model.ts index 59641641f6..2bc271a432 100644 --- a/guardian-service/src/analytics/compare/models/role.model.ts +++ b/guardian-service/src/analytics/compare/models/role.model.ts @@ -1,5 +1,5 @@ import MurmurHash3 from 'imurmurhash'; -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions } from '../interfaces/compare-options.interface'; import { IKeyMap } from '../interfaces/key-map.interface'; import { IWeightModel } from '../interfaces/weight-model.interface'; import { PropertyType } from '../types/property.type'; @@ -56,7 +56,7 @@ export class RoleModel implements IWeightModel { * @param options - comparison options * @public */ - public update(options: ICompareOptions): void { + public update(options: CompareOptions): void { const weights = []; const weightMap = {}; @@ -166,7 +166,7 @@ export class RoleModel implements IWeightModel { * Get weight object * @public */ - public toWeight(options: ICompareOptions): IWeightItem { + public toWeight(options: CompareOptions): IWeightItem { if (!this._weight.length) { return { weight: this.name diff --git a/guardian-service/src/analytics/compare/models/schema-document.model.ts b/guardian-service/src/analytics/compare/models/schema-document.model.ts index f6fb2be8e9..16a83ea9ac 100644 --- a/guardian-service/src/analytics/compare/models/schema-document.model.ts +++ b/guardian-service/src/analytics/compare/models/schema-document.model.ts @@ -1,6 +1,6 @@ import { FieldModel } from './field.model'; import { ConditionModel } from './condition.model'; -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions } from '../interfaces/compare-options.interface'; import MurmurHash3 from 'imurmurhash'; import { ComparePolicyUtils } from '../utils/compare-policy-utils'; @@ -140,7 +140,7 @@ export class SchemaDocumentModel { * @param options - comparison options * @public */ - public update(options: ICompareOptions): void { + public update(options: CompareOptions): void { const hashState = MurmurHash3(); for (const field of this.fields) { field.update(options); @@ -154,7 +154,7 @@ export class SchemaDocumentModel { * @param options - comparison options * @public */ - public hash(options: ICompareOptions): string { + public hash(options: CompareOptions): string { return this._weight; } diff --git a/guardian-service/src/analytics/compare/models/schema.model.ts b/guardian-service/src/analytics/compare/models/schema.model.ts index 509d691d2c..32f11a1b46 100644 --- a/guardian-service/src/analytics/compare/models/schema.model.ts +++ b/guardian-service/src/analytics/compare/models/schema.model.ts @@ -1,4 +1,4 @@ -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions, IIdLvl } from '../interfaces/compare-options.interface'; import { FieldModel } from './field.model'; import { SchemaDocumentModel } from './schema-document.model'; import { Policy, PolicyTool, Schema as SchemaCollection } from '@guardian/common'; @@ -54,7 +54,7 @@ export class SchemaModel { * Compare Options * @private */ - private readonly options: ICompareOptions; + private readonly options: CompareOptions; /** * Schema Model @@ -105,7 +105,7 @@ export class SchemaModel { constructor( schema: SchemaCollection, - options: ICompareOptions + options: CompareOptions ) { this.options = options; this.id = ''; @@ -136,6 +136,16 @@ export class SchemaModel { } } + public static from(data: any, options: CompareOptions): SchemaModel { + return new SchemaModel({ + id: data.$id, + name: data.title, + description: data.description, + iri: data.$id, + document: data + } as any, options); + } + /** * Convert class to object * @public @@ -159,13 +169,13 @@ export class SchemaModel { * @param options - comparison options * @public */ - public update(options: ICompareOptions): void { + public update(options: CompareOptions): void { const hashUtils: HashUtils = new HashUtils(); hashUtils.reset(); hashUtils.add(this.name || ''); hashUtils.add(this.description || ''); - if (options.idLvl > 0) { + if (options.idLvl === IIdLvl.All) { hashUtils.add(this.version || ''); hashUtils.add(this.uuid || ''); hashUtils.add(this.iri || ''); @@ -187,7 +197,7 @@ export class SchemaModel { * @param options - comparison options * @public */ - public hash(options?: ICompareOptions): string { + public hash(options?: CompareOptions): string { return this._weight; } diff --git a/guardian-service/src/analytics/compare/models/template-token.model.ts b/guardian-service/src/analytics/compare/models/template-token.model.ts index 8a706b4a60..b6b04e08fc 100644 --- a/guardian-service/src/analytics/compare/models/template-token.model.ts +++ b/guardian-service/src/analytics/compare/models/template-token.model.ts @@ -1,5 +1,5 @@ import MurmurHash3 from 'imurmurhash'; -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions } from '../interfaces/compare-options.interface'; import { IKeyMap } from '../interfaces/key-map.interface'; import { IWeightModel } from '../interfaces/weight-model.interface'; import { PropertyType } from '../types/property.type'; @@ -57,7 +57,7 @@ export class TemplateTokenModel implements IWeightModel { * @param options - comparison options * @public */ - public update(options: ICompareOptions): void { + public update(options: CompareOptions): void { const weights = []; const weightMap = {}; @@ -174,7 +174,7 @@ export class TemplateTokenModel implements IWeightModel { * Get weight object * @public */ - public toWeight(options: ICompareOptions): IWeightItem { + public toWeight(options: CompareOptions): IWeightItem { if (!this._weight.length) { return { weight: this.name diff --git a/guardian-service/src/analytics/compare/models/token.model.ts b/guardian-service/src/analytics/compare/models/token.model.ts index e9d6099c01..1a1881fed4 100644 --- a/guardian-service/src/analytics/compare/models/token.model.ts +++ b/guardian-service/src/analytics/compare/models/token.model.ts @@ -1,5 +1,5 @@ import { Token } from '@guardian/common'; -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions, IIdLvl } from '../interfaces/compare-options.interface'; import MurmurHash3 from 'imurmurhash'; import { IWeightItem } from '../interfaces/weight-item.interface'; @@ -83,9 +83,9 @@ export class TokenModel { * Compare Options * @private */ - private readonly options: ICompareOptions; + private readonly options: CompareOptions; - constructor(token: Token, options: ICompareOptions) { + constructor(token: Token, options: CompareOptions) { this.options = options; this.id = token.id; this.tokenId = token.tokenId; @@ -145,7 +145,7 @@ export class TokenModel { * @param options - comparison options * @public */ - public hash(options?: ICompareOptions): string { + public hash(options?: CompareOptions): string { return this._weight; } @@ -154,7 +154,7 @@ export class TokenModel { * @param options - comparison options * @public */ - public update(options: ICompareOptions): void { + public update(options: CompareOptions): void { const hashState = MurmurHash3(); hashState.hash(String(this.tokenName)); hashState.hash(String(this.tokenSymbol)); @@ -165,7 +165,7 @@ export class TokenModel { hashState.hash(String(this.enableFreeze)); hashState.hash(String(this.enableKYC)); hashState.hash(String(this.enableWipe)); - if (options.idLvl > 0) { + if (options.idLvl === IIdLvl.All) { hashState.hash(String(this.tokenId)); } this._weight = String(hashState.result()); @@ -175,7 +175,7 @@ export class TokenModel { * Get weight object * @public */ - public toWeight(options: ICompareOptions): IWeightItem { + public toWeight(options: CompareOptions): IWeightItem { if (!this._weight) { return { weight: this.tokenId diff --git a/guardian-service/src/analytics/compare/models/tool.model.ts b/guardian-service/src/analytics/compare/models/tool.model.ts index e691705c73..5b59a6b3d6 100644 --- a/guardian-service/src/analytics/compare/models/tool.model.ts +++ b/guardian-service/src/analytics/compare/models/tool.model.ts @@ -1,5 +1,5 @@ import { PolicyTool } from '@guardian/common'; -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions } from '../interfaces/compare-options.interface'; import { BlockModel } from './block.model'; import { VariableModel } from './variable.model'; import { CompareUtils } from '../utils/utils'; @@ -69,7 +69,7 @@ export class ToolModel { * Compare Options * @private */ - private readonly options: ICompareOptions; + private readonly options: CompareOptions; /** * All Blocks @@ -89,7 +89,7 @@ export class ToolModel { */ private _artifacts: FileModel[]; - constructor(tool: PolicyTool, options: ICompareOptions) { + constructor(tool: PolicyTool, options: CompareOptions) { this.options = options; this.id = tool.id; @@ -130,7 +130,7 @@ export class ToolModel { * @param options - comparison options * @private */ - private createInputEvents(variables: any[], options: ICompareOptions): VariableModel[] { + private createInputEvents(variables: any[], options: CompareOptions): VariableModel[] { const result: VariableModel[] = []; if (Array.isArray(variables)) { for (const json of variables) { @@ -149,7 +149,7 @@ export class ToolModel { * @param options - comparison options * @private */ - private createOutputEvents(variables: any[], options: ICompareOptions): VariableModel[] { + private createOutputEvents(variables: any[], options: CompareOptions): VariableModel[] { const result: VariableModel[] = []; if (Array.isArray(variables)) { for (const json of variables) { @@ -168,7 +168,7 @@ export class ToolModel { * @param options - comparison options * @private */ - private createVariables(variables: any[], options: ICompareOptions): VariableModel[] { + private createVariables(variables: any[], options: CompareOptions): VariableModel[] { const result: VariableModel[] = []; if (Array.isArray(variables)) { for (const json of variables) { @@ -206,7 +206,7 @@ export class ToolModel { * @param options - comparison options * @public */ - private updateAllBlocks(root: BlockModel, options: ICompareOptions): void { + private updateAllBlocks(root: BlockModel, options: CompareOptions): void { for (const child of root.children) { this.updateAllBlocks(child, options); } diff --git a/guardian-service/src/analytics/compare/models/topic.model.ts b/guardian-service/src/analytics/compare/models/topic.model.ts index 79ab67ba95..cf91623301 100644 --- a/guardian-service/src/analytics/compare/models/topic.model.ts +++ b/guardian-service/src/analytics/compare/models/topic.model.ts @@ -1,5 +1,5 @@ import MurmurHash3 from 'imurmurhash'; -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions } from '../interfaces/compare-options.interface'; import { IKeyMap } from '../interfaces/key-map.interface'; import { IWeightModel } from '../interfaces/weight-model.interface'; import { PropertyType } from '../types/property.type'; @@ -57,7 +57,7 @@ export class TopicModel implements IWeightModel { * @param options - comparison options * @public */ - public update(options: ICompareOptions): void { + public update(options: CompareOptions): void { const weights = []; const weightMap = {}; @@ -165,7 +165,7 @@ export class TopicModel implements IWeightModel { * Get weight object * @public */ - public toWeight(options: ICompareOptions): IWeightItem { + public toWeight(options: CompareOptions): IWeightItem { if (!this._weight.length) { return { weight: this.name diff --git a/guardian-service/src/analytics/compare/models/variable.model.ts b/guardian-service/src/analytics/compare/models/variable.model.ts index 29e173cd4f..aa470b3cde 100644 --- a/guardian-service/src/analytics/compare/models/variable.model.ts +++ b/guardian-service/src/analytics/compare/models/variable.model.ts @@ -1,5 +1,5 @@ import MurmurHash3 from 'imurmurhash'; -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions } from '../interfaces/compare-options.interface'; import { IKeyMap } from '../interfaces/key-map.interface'; import { IWeightModel } from '../interfaces/weight-model.interface'; import { PropertyType } from '../types/property.type'; @@ -56,7 +56,7 @@ export class VariableModel implements IWeightModel { * @param options - comparison options * @public */ - public update(options: ICompareOptions): void { + public update(options: CompareOptions): void { const weights = []; const weightMap = {}; diff --git a/guardian-service/src/analytics/compare/rates/blocks-rate.ts b/guardian-service/src/analytics/compare/rates/blocks-rate.ts index f8dda40a4d..bba1a90333 100644 --- a/guardian-service/src/analytics/compare/rates/blocks-rate.ts +++ b/guardian-service/src/analytics/compare/rates/blocks-rate.ts @@ -1,5 +1,5 @@ import { BlockModel } from '../models/block.model'; -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions } from '../interfaces/compare-options.interface'; import { Status } from '../types/status.type'; import { EventModel } from '../models/event.model'; import { PropertiesRate } from './properties-rate'; @@ -94,7 +94,7 @@ export class BlocksRate extends Rate { private compare( block1: BlockModel, block2: BlockModel, - options: ICompareOptions + options: CompareOptions ): IRate[] { const list: string[] = []; const map: { [key: string]: IRateMap> } = {}; @@ -144,7 +144,7 @@ export class BlocksRate extends Rate { private comparePermissions( block1: BlockModel, block2: BlockModel, - options: ICompareOptions + options: CompareOptions ): IRate[] { const list: IRateMap[] = []; if (block1) { @@ -176,7 +176,7 @@ export class BlocksRate extends Rate { private compareEvents( block1: BlockModel, block2: BlockModel, - options: ICompareOptions + options: CompareOptions ): IRate[] { const list: IRateMap[] = []; if (block1) { @@ -208,7 +208,7 @@ export class BlocksRate extends Rate { private compareArtifacts( block1: BlockModel, block2: BlockModel, - options: ICompareOptions + options: CompareOptions ): IRate[] { const list: IRateMap[] = []; if (block1) { @@ -235,7 +235,7 @@ export class BlocksRate extends Rate { * @param options - comparison options * @public */ - public override calc(options: ICompareOptions): void { + public override calc(options: CompareOptions): void { const block1 = this.left; const block2 = this.right; diff --git a/guardian-service/src/analytics/compare/rates/documents-rate.ts b/guardian-service/src/analytics/compare/rates/documents-rate.ts index 2ced60feb3..5598e83fb8 100644 --- a/guardian-service/src/analytics/compare/rates/documents-rate.ts +++ b/guardian-service/src/analytics/compare/rates/documents-rate.ts @@ -1,4 +1,4 @@ -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions } from '../interfaces/compare-options.interface'; import { IRate } from '../interfaces/rate.interface'; import { Rate } from './rate'; import { DocumentModel } from '../models/document.model'; @@ -87,7 +87,7 @@ export class DocumentsRate extends Rate { private compareDocuments( document1: DocumentModel, document2: DocumentModel, - options: ICompareOptions + options: CompareOptions ): IRate[] { const list: string[] = []; const map: { [key: string]: IRateMap> } = {}; @@ -133,7 +133,7 @@ export class DocumentsRate extends Rate { private compareOptions( document1: DocumentModel, document2: DocumentModel, - options: ICompareOptions + options: CompareOptions ): IRate[] { const list: string[] = []; const map: { [key: string]: IRateMap> } = {}; @@ -174,7 +174,7 @@ export class DocumentsRate extends Rate { * @param options - comparison options * @public */ - public override calc(options: ICompareOptions): void { + public override calc(options: CompareOptions): void { const document1 = this.left; const document2 = this.right; diff --git a/guardian-service/src/analytics/compare/rates/fields-rate.ts b/guardian-service/src/analytics/compare/rates/fields-rate.ts index f23f5adcb4..557de1852b 100644 --- a/guardian-service/src/analytics/compare/rates/fields-rate.ts +++ b/guardian-service/src/analytics/compare/rates/fields-rate.ts @@ -1,7 +1,7 @@ import { Status } from '../types/status.type'; import { IRate } from '../interfaces/rate.interface'; import { FieldModel } from '../models/field.model'; -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions } from '../interfaces/compare-options.interface'; import { PropertiesRate } from './properties-rate'; import { Rate } from './rate'; import { IRateMap } from '../interfaces/rate-map.interface'; @@ -56,7 +56,7 @@ export class FieldsRate extends Rate { private compare( field1: FieldModel, field2: FieldModel, - options: ICompareOptions + options: CompareOptions ): void { const list: string[] = []; const map: { [key: string]: IRateMap> } = {}; @@ -119,7 +119,7 @@ export class FieldsRate extends Rate { * @param options - comparison options * @public */ - public override calc(options: ICompareOptions): void { + public override calc(options: CompareOptions): void { this.compare(this.left, this.right, options); if (!this.left || !this.right) { diff --git a/guardian-service/src/analytics/compare/rates/object-rate.ts b/guardian-service/src/analytics/compare/rates/object-rate.ts index 6b4097e8ca..310cbdf133 100644 --- a/guardian-service/src/analytics/compare/rates/object-rate.ts +++ b/guardian-service/src/analytics/compare/rates/object-rate.ts @@ -1,6 +1,6 @@ import { Status } from '../types/status.type'; import { IRate } from '../interfaces/rate.interface'; -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions } from '../interfaces/compare-options.interface'; import { PropertiesRate } from './properties-rate'; import { Rate } from './rate'; import { IRateMap } from '../interfaces/rate-map.interface'; @@ -44,7 +44,7 @@ export class ObjectRate extends Rate { * @param options - comparison options * @private */ - private compare(item1: any, item2: any, options: ICompareOptions): void { + private compare(item1: any, item2: any, options: CompareOptions): void { const list: string[] = []; const map: { [key: string]: IRateMap> } = {}; @@ -82,7 +82,7 @@ export class ObjectRate extends Rate { * @param options - comparison options * @public */ - public override calc(options: ICompareOptions): void { + public override calc(options: CompareOptions): void { this.compare(this.left, this.right, options); if (!this.left || !this.right) { diff --git a/guardian-service/src/analytics/compare/rates/permissions-rate.ts b/guardian-service/src/analytics/compare/rates/permissions-rate.ts index 38c3db8041..0fb06d9a54 100644 --- a/guardian-service/src/analytics/compare/rates/permissions-rate.ts +++ b/guardian-service/src/analytics/compare/rates/permissions-rate.ts @@ -1,4 +1,4 @@ -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions } from '../interfaces/compare-options.interface'; import { IRateTable } from '../interfaces/rate-table.interface'; import { IRate } from '../interfaces/rate.interface'; import { Status } from '../types/status.type'; @@ -71,7 +71,7 @@ export class PermissionsRate implements IRate { * @param options - comparison options * @public */ - public calc(options: ICompareOptions): void { + public calc(options: CompareOptions): void { return; } diff --git a/guardian-service/src/analytics/compare/rates/properties-rate.ts b/guardian-service/src/analytics/compare/rates/properties-rate.ts index 3ca9053cf7..69daacdc86 100644 --- a/guardian-service/src/analytics/compare/rates/properties-rate.ts +++ b/guardian-service/src/analytics/compare/rates/properties-rate.ts @@ -1,6 +1,6 @@ import { Status } from '../types/status.type'; import { IRate } from '../interfaces/rate.interface'; -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions } from '../interfaces/compare-options.interface'; import { PropertyModel } from '../models/property.model'; import { CompareUtils } from '../utils/utils'; import { IRateMap } from '../interfaces/rate-map.interface'; @@ -106,7 +106,7 @@ export class PropertiesRate implements IRate> { private compareProp( prop1: PropertyModel, prop2: PropertyModel, - options: ICompareOptions + options: CompareOptions ): PropertiesRate[] { const list: string[] = []; const map: { [key: string]: IRateMap> } = {}; @@ -146,7 +146,7 @@ export class PropertiesRate implements IRate> { * @param options - comparison options * @public */ - public calc(options: ICompareOptions): void { + public calc(options: CompareOptions): void { this.properties = this.compareProp(this.left, this.right, options); if (!this.left || !this.right) { @@ -161,6 +161,12 @@ export class PropertiesRate implements IRate> { this.type = Status.PARTLY; } + if (this.left.ignore(options)) { + this.totalRate = 100; + this.propertiesRate = 100; + return; + } + if (this.properties && this.properties.length) { this.propertiesRate = CompareUtils.calcRate(this.properties); this.totalRate = CompareUtils.calcTotalRate(this.totalRate, this.propertiesRate); diff --git a/guardian-service/src/analytics/compare/rates/rate.ts b/guardian-service/src/analytics/compare/rates/rate.ts index a7e2eeca0c..5059291a36 100644 --- a/guardian-service/src/analytics/compare/rates/rate.ts +++ b/guardian-service/src/analytics/compare/rates/rate.ts @@ -1,6 +1,6 @@ import { Status } from '../types/status.type'; import { IRate } from '../interfaces/rate.interface'; -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions } from '../interfaces/compare-options.interface'; import { IModel } from '../interfaces/model.interface'; import { IRateTable } from '../interfaces/rate-table.interface'; @@ -67,7 +67,7 @@ export class Rate implements IRate { * @param options - comparison options * @public */ - public calc(options: ICompareOptions): void { + public calc(options: CompareOptions): void { return; } diff --git a/guardian-service/src/analytics/compare/rates/record-rate.ts b/guardian-service/src/analytics/compare/rates/record-rate.ts new file mode 100644 index 0000000000..ce2b2950c4 --- /dev/null +++ b/guardian-service/src/analytics/compare/rates/record-rate.ts @@ -0,0 +1,62 @@ +import { CompareOptions } from '../interfaces/compare-options.interface'; +import { IRate } from '../interfaces/rate.interface'; +import { Rate } from './rate'; +import { DocumentsRate } from './documents-rate'; +import { RecordModel } from '../models/record.model'; + +/** + * Calculates the difference between two Documents + */ +export class RecordRate extends Rate { + /** + * Sub Blocks + */ + public children: DocumentsRate[]; + + constructor(document1: RecordModel, document2: RecordModel) { + super(document1, document2); + } + + /** + * Calculations all rates + * @param options - comparison options + * @public + */ + public override calc(options: CompareOptions): void { + this.totalRate = 100; + } + + /** + * Set children rates + * @public + */ + public override setChildren>(children: U[]): void { + this.children = children as any; + } + + /** + * Get Children Rates + * @public + */ + public override getChildren>(): T[] { + return this.children as any; + } + + /** + * Get sub rates by name + * @param name - rate name + * @public + */ + public getSubRate(name: string): IRate[] { + return null; + } + + /** + * Get rate by name + * @param name - rate name + * @public + */ + public override getRateValue(name: string): number { + return this.totalRate; + } +} \ No newline at end of file diff --git a/guardian-service/src/analytics/compare/utils/compare-policy-utils.ts b/guardian-service/src/analytics/compare/utils/compare-policy-utils.ts index 1bd5f6a25a..7b827ec8c6 100644 --- a/guardian-service/src/analytics/compare/utils/compare-policy-utils.ts +++ b/guardian-service/src/analytics/compare/utils/compare-policy-utils.ts @@ -1,4 +1,4 @@ -import { ICompareOptions } from '../interfaces/compare-options.interface'; +import { CompareOptions } from '../interfaces/compare-options.interface'; import { IModel } from '../interfaces/model.interface'; import { IRateMap } from '../interfaces/rate-map.interface'; import { IRate } from '../interfaces/rate.interface'; @@ -6,8 +6,10 @@ import { IWeightModel, IWeightTreeModel } from '../interfaces/weight-model.inter import { BlockModel } from '../models/block.model'; import { DocumentModel } from '../models/document.model'; import { FieldModel } from '../models/field.model'; +import { RecordModel } from '../models/record.model'; import { BlocksRate } from '../rates/blocks-rate'; import { DocumentsRate } from '../rates/documents-rate'; +import { RecordRate } from '../rates/record-rate'; import { FieldsRate } from '../rates/fields-rate'; import { ObjectRate } from '../rates/object-rate'; import { Status } from '../types/status.type'; @@ -82,7 +84,7 @@ export class ComparePolicyUtils { public static compareFields( fields1: FieldModel[], fields2: FieldModel[], - options: ICompareOptions + options: CompareOptions ): FieldsRate[] { const createRate = (field1: FieldModel, field2: FieldModel) => { const rate = new FieldsRate(field1, field2); @@ -105,7 +107,7 @@ export class ComparePolicyUtils { public static compareBlocks( tree1: BlockModel, tree2: BlockModel, - options: ICompareOptions + options: CompareOptions ): BlocksRate { const createRate = (block1: BlockModel, block2: BlockModel) => { const rate = new BlocksRate(block1, block2); @@ -126,7 +128,7 @@ export class ComparePolicyUtils { public static compareDocuments( tree1: DocumentModel, tree2: DocumentModel, - options: ICompareOptions + options: CompareOptions ): DocumentsRate { const createRate = (document1: DocumentModel, document2: DocumentModel) => { const rate = new DocumentsRate(document1, document2); @@ -136,6 +138,51 @@ export class ComparePolicyUtils { return ComparePolicyUtils.compareTree(tree1, tree2, createRate); } + /** + * Compare two trees + * @param tree1 + * @param tree2 + * @param options + * @public + * @static + */ + public static compareRecord( + tree1: RecordModel, + tree2: RecordModel, + options: CompareOptions + ): RecordRate { + const rate = new RecordRate(tree1, tree2); + rate.calc(options); + + const createRate = (document1: DocumentModel, document2: DocumentModel) => { + const _rate = new DocumentsRate(document1, document2); + _rate.calc(options); + return _rate; + } + if (tree1.equal(tree2)) { + rate.type = Status.FULL; + rate.setChildren( + ComparePolicyUtils.compareChildren( + Status.FULL, + tree1.children, + tree2.children, + createRate + ) + ); + } else { + rate.type = Status.PARTLY; + rate.setChildren( + ComparePolicyUtils.compareChildren( + Status.PARTLY, + tree1.children, + tree2.children, + createRate + ) + ); + } + return rate; + } + /** * Compare two trees * @param tree1 @@ -257,7 +304,7 @@ export class ComparePolicyUtils { public static compareArray( children1: IWeightModel[], children2: IWeightModel[], - options: ICompareOptions + options: CompareOptions ): IRate[] { const result = MergeUtils.partlyMerge(children1, children2); const rates: IRate[] = []; diff --git a/guardian-service/src/analytics/search/models/pair.model.ts b/guardian-service/src/analytics/search/models/pair.model.ts index 7e46cb713c..34e84b815b 100644 --- a/guardian-service/src/analytics/search/models/pair.model.ts +++ b/guardian-service/src/analytics/search/models/pair.model.ts @@ -1,7 +1,13 @@ import { ArtifactsRate, + CompareOptions, CompareUtils, EventsRate, + IChildrenLvl, + IEventsLvl, + IIdLvl, + IKeyLvl, + IPropertiesLvl, IRate, PermissionsRate, PropertiesRate, @@ -60,12 +66,14 @@ export class PairSearchModel { * Compare options * @private */ - private readonly _options = { - idLvl: 1, - eventLvl: 2, - childLvl: 1, - propLvl: 2 - } + private readonly _options = new CompareOptions( + IPropertiesLvl.All, + IChildrenLvl.All, + IEventsLvl.All, + IIdLvl.All, + IKeyLvl.Default, + null + ); constructor(source: BlockSearchModel, filter: BlockSearchModel) { this._hash = 0; diff --git a/guardian-service/src/api/analytics.service.ts b/guardian-service/src/api/analytics.service.ts index b7029d5ae3..b63f603e34 100644 --- a/guardian-service/src/api/analytics.service.ts +++ b/guardian-service/src/api/analytics.service.ts @@ -1,8 +1,11 @@ import { + CompareOptions, DocumentComparator, DocumentModel, HashComparator, - ICompareOptions, + IChildrenLvl, + IEventsLvl, + IPropertiesLvl, ModuleComparator, ModuleModel, PolicyComparator, @@ -40,12 +43,7 @@ export async function analyticsAPI(): Promise { childrenLvl, idLvl } = msg; - const options = { - propLvl: parseInt(propLvl, 10), - childLvl: parseInt(childrenLvl, 10), - eventLvl: parseInt(eventsLvl, 10), - idLvl: parseInt(idLvl, 10), - }; + const options = new CompareOptions(propLvl, childrenLvl, eventsLvl, idLvl, null, null); const compareModels: PolicyModel[] = []; for (const policyId of ids) { @@ -91,12 +89,7 @@ export async function analyticsAPI(): Promise { childrenLvl, idLvl } = msg; - const options = { - propLvl: parseInt(propLvl, 10), - childLvl: parseInt(childrenLvl, 10), - eventLvl: parseInt(eventsLvl, 10), - idLvl: parseInt(idLvl, 10), - }; + const options = new CompareOptions(propLvl, childrenLvl, eventsLvl, idLvl, null, null); //Policy const module1 = await DatabaseServer.getModuleById(moduleId1); @@ -135,15 +128,17 @@ export async function analyticsAPI(): Promise { schemaId2, idLvl } = msg; + const options = new CompareOptions( + IPropertiesLvl.All, + IChildrenLvl.None, + IEventsLvl.None, + idLvl, + null, + null + ); const schema1 = await DatabaseServer.getSchemaById(schemaId1); const schema2 = await DatabaseServer.getSchemaById(schemaId2); - const options = { - propLvl: 2, - childLvl: 0, - eventLvl: 0, - idLvl: parseInt(idLvl, 10) - } const policy1 = await DatabaseServer.getPolicy({ topicId: schema1?.topicId }); const policy2 = await DatabaseServer.getPolicy({ topicId: schema2?.topicId }); @@ -255,18 +250,17 @@ export async function analyticsAPI(): Promise { eventsLvl, propLvl, childrenLvl, - idLvl + idLvl, + keyLvl } = msg; - const options: ICompareOptions = { - owner: null, - propLvl: parseInt(propLvl, 10), - childLvl: parseInt(childrenLvl, 10), - eventLvl: parseInt(eventsLvl, 10), - idLvl: parseInt(idLvl, 10), - }; - if (user?.role === UserRole.STANDARD_REGISTRY) { - options.owner = user.did; - } + const options = new CompareOptions( + propLvl, + childrenLvl, + eventsLvl, + idLvl, + keyLvl, + user?.role === UserRole.STANDARD_REGISTRY ? user.did : null + ); const compareModels: DocumentModel[] = []; for (const documentsId of ids) { @@ -315,16 +309,14 @@ export async function analyticsAPI(): Promise { childrenLvl, idLvl } = msg; - const options: ICompareOptions = { - owner: null, - propLvl: parseInt(propLvl, 10), - childLvl: parseInt(childrenLvl, 10), - eventLvl: parseInt(eventsLvl, 10), - idLvl: parseInt(idLvl, 10), - }; - if (user?.role === UserRole.STANDARD_REGISTRY) { - options.owner = user.did; - } + const options = new CompareOptions( + propLvl, + childrenLvl, + eventsLvl, + idLvl, + null, + user?.role === UserRole.STANDARD_REGISTRY ? user.did : null + ); const compareModels: ToolModel[] = []; for (const toolId of ids) { diff --git a/guardian-service/src/api/contract.service.ts b/guardian-service/src/api/contract.service.ts index 0e11e47497..aec594666a 100644 --- a/guardian-service/src/api/contract.service.ts +++ b/guardian-service/src/api/contract.service.ts @@ -122,7 +122,7 @@ async function setPool( contractId ); // tslint:disable-next-line:no-empty - } catch {} + } catch { } await setContractWiperPermissions( contractRepository, retirePoolRepository, @@ -795,8 +795,7 @@ export async function syncRetireContract( contractOwnerIds.map((user) => NotificationHelper.info( `Pools cleared in contract: ${contractId}`, - `All ${ - count === 1 ? 'single' : 'double' + `All ${count === 1 ? 'single' : 'double' } pools cleared`, user ) @@ -816,8 +815,7 @@ export async function syncRetireContract( contractOwnerIds.map((user) => NotificationHelper.info( `Requests cleared in contract: ${contractId}`, - `All ${ - count === 1 ? 'single' : 'double' + `All ${count === 1 ? 'single' : 'double' } requests cleared`, user ) @@ -1006,11 +1004,7 @@ async function saveRetireVC( ); } - const vcObject = await vcHelper.createVC( - did, - hederaAccountKey, - credentialSubject - ); + const vcObject = await vcHelper.createVcDocument(credentialSubject, { did, key: hederaAccountKey }); const vcMessage = new VCMessage(MessageAction.CreateVC); vcMessage.setDocument(vcObject); await messageServer.sendMessage(vcMessage); diff --git a/guardian-service/src/api/profile.service.ts b/guardian-service/src/api/profile.service.ts index c0d390bdb8..e76d60d2c0 100644 --- a/guardian-service/src/api/profile.service.ts +++ b/guardian-service/src/api/profile.service.ts @@ -148,7 +148,7 @@ async function createUserProfile(profile: any, notifier: INotifier, user?: IAuth const didObject = await DIDDocument.create(hederaAccountKey, topicConfig.topicId); const userDID = didObject.getDid(); - const existingUser = await new DataBaseHelper(DidDocumentCollection).findOne({did: userDID}); + const existingUser = await new DataBaseHelper(DidDocumentCollection).findOne({ did: userDID }); if (existingUser) { notifier.completedAndStart('User restored'); notifier.completed(); @@ -281,7 +281,7 @@ async function createUserProfile(profile: any, notifier: INotifier, user?: IAuth credentialSubject = SchemaHelper.updateObjectContext(schemaObject, credentialSubject); } - const vcObject = await vcHelper.createVC(userDID, hederaAccountKey, credentialSubject); + const vcObject = await vcHelper.createVcDocument(credentialSubject, { did: userDID, key: hederaAccountKey }); const vcMessage = new VCMessage(MessageAction.CreateVC); vcMessage.setDocument(vcObject); const vcDoc = await new DataBaseHelper(VcDocumentCollection).save({ @@ -596,4 +596,4 @@ export function profileAPI() { ProfileController ] }) -export class ProfileModule {} +export class ProfileModule { } diff --git a/guardian-service/src/api/record.service.ts b/guardian-service/src/api/record.service.ts new file mode 100644 index 0000000000..5cdfc5ada2 --- /dev/null +++ b/guardian-service/src/api/record.service.ts @@ -0,0 +1,396 @@ +import { CompareOptions, IChildrenLvl, IEventsLvl, IIdLvl, IKeyLvl, IPropertiesLvl, RecordComparator } from '@analytics'; +import { ApiResponse } from '@api/helpers/api-response'; +import { + BinaryMessageResponse, + DatabaseServer, + IRecordResult, + Logger, + MessageError, + MessageResponse, + Policy, + RecordImportExport, +} from '@guardian/common'; +import { MessageAPI, PolicyEvents, PolicyType } from '@guardian/interfaces'; +import { GuardiansService } from '@helpers/guardians'; + +/** + * Compare results + * @param policyId + * @param owner + */ +export async function compareResults(details: any): Promise { + if (details) { + const options = new CompareOptions( + IPropertiesLvl.All, + IChildrenLvl.None, + IEventsLvl.None, + IIdLvl.None, + IKeyLvl.Default, + null + ); + const documents: IRecordResult[] = details.documents; + const recorded: IRecordResult[] = details.recorded; + const comparator = new RecordComparator(options); + const recordedModel = await RecordComparator.createModel(recorded, options); + const documentsModel = await RecordComparator.createModel(documents, options); + const results = comparator.compare([recordedModel, documentsModel]); + const result = results[0]; + return result; + } else { + return null; + } +} + +/** + * Check policy + * @param policyId + * @param owner + */ +export async function checkPolicy(policyId: string, owner: string): Promise { + const model = await DatabaseServer.getPolicyById(policyId); + if (!model) { + throw new Error('Unknown policy'); + } + if (model.owner !== owner) { + throw new Error('Invalid owner.'); + } + if (model.status !== PolicyType.DRY_RUN) { + throw new Error(`Policy is not in Dry Run`); + } + return model; +} + +/** + * Connect to the message broker methods of working with records. + */ +export async function recordAPI(): Promise { + /** + * Get recording or running status + * + * @param payload - options + * + * @returns {any} result + */ + ApiResponse(MessageAPI.GET_RECORD_STATUS, async (msg) => { + try { + if (!msg) { + throw new Error('Invalid parameters'); + } + const { policyId, owner } = msg; + await checkPolicy(policyId, owner); + const guardiansService = new GuardiansService(); + const result = await guardiansService + .sendPolicyMessage(PolicyEvents.GET_RECORD_STATUS, policyId, null); + return new MessageResponse(result); + } catch (error) { + new Logger().error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Start recording + * + * @param payload - options + * + * @returns {any} result + */ + ApiResponse(MessageAPI.START_RECORDING, async (msg) => { + try { + if (!msg) { + throw new Error('Invalid parameters'); + } + const { policyId, owner, options } = msg; + await checkPolicy(policyId, owner); + const guardiansService = new GuardiansService(); + const result = await guardiansService + .sendPolicyMessage(PolicyEvents.START_RECORDING, policyId, options); + return new MessageResponse(result); + } catch (error) { + new Logger().error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Stop recording + * + * @param payload - options + * + * @returns {any} result + */ + ApiResponse(MessageAPI.STOP_RECORDING, async (msg) => { + try { + if (!msg) { + throw new Error('Invalid parameters'); + } + const { policyId, owner, options } = msg; + await checkPolicy(policyId, owner); + const guardiansService = new GuardiansService(); + const result = await guardiansService + .sendPolicyMessage(PolicyEvents.STOP_RECORDING, policyId, options); + + if (!result) { + throw new Error('Invalid record'); + } + + const items = await DatabaseServer.getRecord({ policyId, method: 'STOP' }); + const uuid = items[items.length - 1]?.uuid; + + const zip = await RecordImportExport.generate(uuid); + const file = await zip.generateAsync({ + type: 'arraybuffer', + compression: 'DEFLATE', + compressionOptions: { + level: 3, + }, + }); + return new BinaryMessageResponse(file); + } catch (error) { + new Logger().error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Get recorded actions + * + * @param payload - options + * + * @returns {any} result + */ + ApiResponse(MessageAPI.GET_RECORDED_ACTIONS, async (msg) => { + try { + if (!msg) { + throw new Error('Invalid parameters'); + } + const { policyId, owner } = msg; + await checkPolicy(policyId, owner); + const guardiansService = new GuardiansService(); + const result = await guardiansService + .sendPolicyMessage(PolicyEvents.GET_RECORDED_ACTIONS, policyId, null); + return new MessageResponse(result); + } catch (error) { + new Logger().error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Run record + * + * @param payload - options + * + * @returns {any} result + */ + ApiResponse(MessageAPI.RUN_RECORD, async (msg) => { + try { + if (!msg) { + throw new Error('Invalid parameters'); + } + + const { policyId, owner, options } = msg; + await checkPolicy(policyId, owner); + + const zip = options.file; + delete options.file; + const recordToImport = await RecordImportExport.parseZipFile(Buffer.from(zip.data)); + const guardiansService = new GuardiansService(); + + const result = await guardiansService + .sendPolicyMessage(PolicyEvents.RUN_RECORD, policyId, { + records: recordToImport.records, + results: recordToImport.results, + options + }); + return new MessageResponse(result); + } catch (error) { + new Logger().error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Stop running + * + * @param payload - options + * + * @returns {any} result + */ + ApiResponse(MessageAPI.STOP_RUNNING, async (msg) => { + try { + if (!msg) { + throw new Error('Invalid parameters'); + } + + const { policyId, owner, options } = msg; + await checkPolicy(policyId, owner); + + const guardiansService = new GuardiansService(); + const result = await guardiansService + .sendPolicyMessage(PolicyEvents.STOP_RUNNING, policyId, options); + return new MessageResponse(result); + } catch (error) { + new Logger().error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Get record results + * + * @param payload - options + * + * @returns {any} result + */ + ApiResponse(MessageAPI.GET_RECORD_RESULTS, async (msg) => { + try { + if (!msg) { + throw new Error('Invalid parameters'); + } + + const { policyId, owner } = msg; + await checkPolicy(policyId, owner); + + const guardiansService = new GuardiansService(); + const details: any = await guardiansService + .sendPolicyMessage(PolicyEvents.GET_RECORD_RESULTS, policyId, null); + + const report = await compareResults(details); + const total = report?.total; + const info = report?.right; + const table = report?.documents?.report || []; + + const documents = []; + for (let i = 1; i < table.length; i++) { + const row = table[i]; + if (row.right) { + const index = row.right.attributes; + const document = details?.documents?.[index]; + documents.push({ + type: row.document_type, + rate: row.total_rate, + schema: row.right_schema, + document + }) + } + } + + return new MessageResponse({ + info, + total, + details, + documents + }); + } catch (error) { + new Logger().error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Get record results + * + * @param payload - options + * + * @returns {any} result + */ + ApiResponse(MessageAPI.GET_RECORD_DETAILS, async (msg) => { + try { + if (!msg) { + throw new Error('Invalid parameters'); + } + + const { policyId, owner } = msg; + await checkPolicy(policyId, owner); + + const guardiansService = new GuardiansService(); + const details: any = await guardiansService + .sendPolicyMessage(PolicyEvents.GET_RECORD_RESULTS, policyId, null); + + const result = await compareResults(details); + return new MessageResponse(result); + } catch (error) { + new Logger().error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Fast Forward + * + * @param payload - options + * + * @returns {any} result + */ + ApiResponse(MessageAPI.FAST_FORWARD, async (msg) => { + try { + if (!msg) { + throw new Error('Invalid parameters'); + } + + const { policyId, owner, options } = msg; + await checkPolicy(policyId, owner); + + const guardiansService = new GuardiansService(); + const result = await guardiansService + .sendPolicyMessage(PolicyEvents.FAST_FORWARD, policyId, options); + return new MessageResponse(result); + } catch (error) { + new Logger().error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Retry Step + * + * @param payload - options + * + * @returns {any} result + */ + ApiResponse(MessageAPI.RECORD_RETRY_STEP, async (msg) => { + try { + if (!msg) { + throw new Error('Invalid parameters'); + } + + const { policyId, owner, options } = msg; + await checkPolicy(policyId, owner); + + const guardiansService = new GuardiansService(); + const result = await guardiansService + .sendPolicyMessage(PolicyEvents.RECORD_RETRY_STEP, policyId, options); + return new MessageResponse(result); + } catch (error) { + new Logger().error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Skip Step + * + * @param payload - options + * + * @returns {any} result + */ + ApiResponse(MessageAPI.RECORD_SKIP_STEP, async (msg) => { + try { + if (!msg) { + throw new Error('Invalid parameters'); + } + + const { policyId, owner, options } = msg; + await checkPolicy(policyId, owner); + + const guardiansService = new GuardiansService(); + const result = await guardiansService + .sendPolicyMessage(PolicyEvents.RECORD_SKIP_STEP, policyId, options); + return new MessageResponse(result); + } catch (error) { + new Logger().error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); +} \ No newline at end of file diff --git a/guardian-service/src/api/tag.service.ts b/guardian-service/src/api/tag.service.ts index df4d4442db..6a7b88dcfd 100644 --- a/guardian-service/src/api/tag.service.ts +++ b/guardian-service/src/api/tag.service.ts @@ -375,7 +375,7 @@ export async function tagsAPI(): Promise { const schemaObject = new Schema(tagSchema); credentialSubject = SchemaHelper.updateObjectContext(schemaObject, credentialSubject); } - const vcObject = await vcHelper.createVC(owner, root.hederaAccountKey, credentialSubject); + const vcObject = await vcHelper.createVcDocument(credentialSubject, { did: owner, key: root.hederaAccountKey }); tag.document = vcObject.getDocument(); } else { tag.document = null; diff --git a/guardian-service/src/app.ts b/guardian-service/src/app.ts index 8669c4ca53..3a7903fe8c 100644 --- a/guardian-service/src/app.ts +++ b/guardian-service/src/app.ts @@ -68,6 +68,7 @@ import { analyticsAPI } from '@api/analytics.service'; import { GridFSBucket } from 'mongodb'; import { suggestionsAPI } from '@api/suggestions.service'; import { SynchronizationTask } from '@helpers/synchronization-task'; +import { recordAPI } from '@api/record.service'; export const obj = {}; @@ -173,6 +174,7 @@ Promise.all([ await mapAPI(); await themeAPI(); await wizardAPI(); + await recordAPI(); await brandingAPI(brandingRepository); await suggestionsAPI() } catch (error) { diff --git a/guardian-service/src/helpers/guardians.ts b/guardian-service/src/helpers/guardians.ts index 4a4d331e13..2d80546359 100644 --- a/guardian-service/src/helpers/guardians.ts +++ b/guardian-service/src/helpers/guardians.ts @@ -32,7 +32,7 @@ export class GuardiansService extends NatsService { * @param policyId */ public async checkIfPolicyAlive(policyId: string): Promise { - const exist = await this.sendPolicyMessage(PolicyEvents.CHECK_IF_ALIVE, policyId, {}) + const exist = await this.sendPolicyMessage(PolicyEvents.CHECK_IF_ALIVE, policyId, {}, 1000) return !!exist } @@ -41,8 +41,9 @@ export class GuardiansService extends NatsService { * @param subject * @param policyId * @param data + * @param awaitInterval */ - public sendPolicyMessage(subject: string, policyId: string, data?: unknown): Promise{ + public sendPolicyMessage(subject: string, policyId: string, data: unknown, awaitInterval: number = 100000): Promise { const messageId = GenerateUUIDv4(); const head = headers(); head.append('messageId', messageId); @@ -66,7 +67,7 @@ export class GuardiansService extends NatsService { new Promise((resolve, reject) => { setTimeout(() => { resolve(null); - }, 1 * 1000) + }, awaitInterval) }), ]) } diff --git a/guardian-service/src/policy-engine/block-validators/interfaces/serialized-errors.interface.ts b/guardian-service/src/policy-engine/block-validators/interfaces/serialized-errors.interface.ts index 2955052503..7885f2b17b 100644 --- a/guardian-service/src/policy-engine/block-validators/interfaces/serialized-errors.interface.ts +++ b/guardian-service/src/policy-engine/block-validators/interfaces/serialized-errors.interface.ts @@ -24,4 +24,9 @@ export interface ISerializedErrors { * Tools */ tools?: IModulesErrors[]; + + /** + * Is valid + */ + isValid: boolean; } \ No newline at end of file diff --git a/guardian-service/src/policy-engine/block-validators/module-validator.ts b/guardian-service/src/policy-engine/block-validators/module-validator.ts index 919a9e03c0..fbd6f1a23a 100644 --- a/guardian-service/src/policy-engine/block-validators/module-validator.ts +++ b/guardian-service/src/policy-engine/block-validators/module-validator.ts @@ -6,6 +6,7 @@ import { IModulesErrors } from './interfaces/modules-errors.interface'; import { ISchema, ModuleStatus } from '@guardian/interfaces'; import { DatabaseServer } from '@guardian/common'; import { ToolValidator } from './tool-validator'; +import { SchemaValidator } from './schema-validator'; /** * Module Validator @@ -45,12 +46,7 @@ export class ModuleValidator { * Schemas * @private */ - private readonly schemas: Map; - /** - * Unsupported Schemas - * @private - */ - private readonly unsupportedSchemas: Set; + private readonly schemas: Map; /** * Tokens * @private @@ -85,7 +81,6 @@ export class ModuleValidator { this.errors = []; this.permissions = ['NO_ROLE', 'ANY_ROLE', 'OWNER']; this.schemas = new Map(); - this.unsupportedSchemas = new Set(); this.tokens = []; this.topics = []; this.tokenTemplates = []; @@ -109,7 +104,6 @@ export class ModuleValidator { } } await this.registerSchemas(); - this.checkSchemas(); return true; } } @@ -118,31 +112,9 @@ export class ModuleValidator { * Register schemas */ private async registerSchemas(): Promise { - const db = new DatabaseServer(null); - for (const [key, value] of this.schemas) { - if (typeof value === 'string') { - const baseSchema = await db.getSchemaByIRI(value); - this.schemas.set(key, baseSchema); - } - } - } - - /** - * Check schemas - */ - private checkSchemas(): void { - for (const schema of this.schemas.values()) { - const defs = schema?.document?.$defs; - if (defs && Object.prototype.toString.call(defs) === '[object Object]') { - for (const iri of Object.keys(defs)) { - if (!this.schemaExist(iri)) { - this.schemas.delete(schema.iri); - this.unsupportedSchemas.add(schema.iri); - this.checkSchemas(); - return; - } - } - } + this.schemas.set('#GeoJSON', SchemaValidator.fromSystem('#GeoJSON')); + for (const validator of this.schemas.values()) { + await validator.load(); } } @@ -203,7 +175,7 @@ export class ModuleValidator { this.variables.push(variable); switch (variable.type) { case 'Schema': { - this.schemas.set(variable.name, variable.baseSchema); + this.schemas.set(variable.name, SchemaValidator.fromTemplate(variable)); break; } case 'Token': @@ -271,6 +243,10 @@ export class ModuleValidator { * Validate */ public async validate() { + const allSchemas = this.getAllSchemas(new Map()); + for (const item of this.schemas.values()) { + await item.validate(allSchemas); + } for (const item of this.tools.values()) { await item.validate(); } @@ -318,18 +294,38 @@ export class ModuleValidator { */ public getSerializedErrors(): IModulesErrors { let valid = !this.errors.length; + const blocksErrors = []; const toolsErrors = []; + const commonErrors = this.errors.slice(); + /** + * Schema errors + */ + for (const item of this.schemas.values()) { + const result = item.getSerializedErrors(); + for (const error of result.errors) { + commonErrors.push(error); + } + valid = valid && result.isValid; + } + /** + * Tools errors + */ for (const item of this.tools.values()) { const result = item.getSerializedErrors(); toolsErrors.push(result); valid = valid && result.isValid; } - const blocksErrors = []; + /** + * Blocks errors + */ for (const item of this.blocks.values()) { const result = item.getSerializedErrors(); blocksErrors.push(result); valid = valid && result.isValid; } + /** + * Common module errors + */ for (const item of this.errors) { blocksErrors.push({ id: this.uuid, @@ -338,6 +334,9 @@ export class ModuleValidator { isValid: false }); } + /** + * Result error + */ if (!valid) { blocksErrors.push({ id: this.uuid, @@ -346,7 +345,6 @@ export class ModuleValidator { isValid: false }); } - const commonErrors = this.errors.slice(); return { errors: commonErrors, blocks: blocksErrors, @@ -393,15 +391,35 @@ export class ModuleValidator { */ public getSchema(iri: string): ISchema { if (this.schemas.has(iri)) { - return this.schemas.get(iri); - } - for (const item of this.tools.values()) { - const schema = item.getSchema(iri); - if (schema) { - return schema; + const validator = this.schemas.get(iri); + if (validator.isValid) { + return validator.getSchema(); + } else { + return null; } + } else { + for (const item of this.tools.values()) { + const schema = item.getSchema(iri); + if (schema) { + return schema; + } + } + return null; } - return null; + } + + /** + * Get all schemas + * @param iri + */ + public getAllSchemas(map: Map): Map { + for (const [key, value] of this.schemas) { + map.set(key, value); + } + for (const tool of this.tools.values()) { + tool.getAllSchemas(map); + } + return map; } /** @@ -409,19 +427,17 @@ export class ModuleValidator { * @param iri */ public schemaExist(iri: string): boolean { - if (iri === '#GeoJSON') { - return true; - } if (this.schemas.has(iri)) { - return !!this.schemas.get(iri); - } - for (const item of this.tools.values()) { - const exist = item.schemaExist(iri); - if (exist) { - return exist; + const validator = this.schemas.get(iri); + return validator.isValid; + } else { + for (const item of this.tools.values()) { + if (item.schemaExist(iri)) { + return true; + } } + return false; } - return false; } /** @@ -429,15 +445,17 @@ export class ModuleValidator { * @param iri */ public unsupportedSchema(iri: string): boolean { - if (this.unsupportedSchemas.has(iri)) { - return true; - } - for (const item of this.tools.values()) { - if (item.unsupportedSchema(iri)) { - return true; + if (this.schemas.has(iri)) { + const validator = this.schemas.get(iri); + return !validator.isValid; + } else { + for (const item of this.tools.values()) { + if (item.unsupportedSchema(iri)) { + return true; + } } + return false; } - return false; } /** diff --git a/guardian-service/src/policy-engine/block-validators/policy-validator.ts b/guardian-service/src/policy-engine/block-validators/policy-validator.ts index 7dd46535ba..b6d7c49631 100644 --- a/guardian-service/src/policy-engine/block-validators/policy-validator.ts +++ b/guardian-service/src/policy-engine/block-validators/policy-validator.ts @@ -4,6 +4,7 @@ import { BlockValidator } from './block-validator'; import { ModuleValidator } from './module-validator'; import { ISerializedErrors } from './interfaces/serialized-errors.interface'; import { ToolValidator } from './tool-validator'; +import { SchemaValidator } from './schema-validator'; /** * Policy Validator @@ -63,12 +64,7 @@ export class PolicyValidator { * Schemas * @private */ - private readonly schemas: Map; - /** - * Unsupported Schemas - * @private - */ - private readonly unsupportedSchemas: Set; + private readonly schemas: Map; constructor(policy: Policy) { this.blocks = new Map(); @@ -82,7 +78,6 @@ export class PolicyValidator { this.policyTopics = policy.policyTopics || []; this.policyGroups = policy.policyGroups; this.schemas = new Map(); - this.unsupportedSchemas = new Set(); } /** @@ -97,7 +92,6 @@ export class PolicyValidator { this.addPermissions(policy.policyRoles); await this.registerBlock(policy.config); await this.registerSchemas(); - this.checkSchemas(); return true; } } @@ -106,29 +100,13 @@ export class PolicyValidator { * Register schemas */ private async registerSchemas(): Promise { + this.schemas.set('#GeoJSON', SchemaValidator.fromSystem('#GeoJSON')); const schemas = await DatabaseServer.getSchemas({ topicId: this.topicId }); - this.schemas.clear(); for (const schema of schemas) { - this.schemas.set(schema.iri, schema); + this.schemas.set(schema.iri, SchemaValidator.fromSchema(schema)); } - } - - /** - * Check schemas - */ - private checkSchemas(): void { - for (const schema of this.schemas.values()) { - const defs = schema?.document?.$defs; - if (defs && Object.prototype.toString.call(defs) === '[object Object]') { - for (const iri of Object.keys(defs)) { - if (!this.schemaExist(iri)) { - this.schemas.delete(schema.iri); - this.unsupportedSchemas.add(schema.iri); - this.checkSchemas(); - return; - } - } - } + for (const validator of this.schemas.values()) { + await validator.load(); } } @@ -208,6 +186,10 @@ export class PolicyValidator { * Validate */ public async validate(): Promise { + const allSchemas = this.getAllSchemas(new Map()); + for (const item of this.schemas.values()) { + await item.validate(allSchemas); + } for (const item of this.modules.values()) { await item.validate(); } @@ -269,18 +251,48 @@ export class PolicyValidator { * Get serialized errors */ public getSerializedErrors(): ISerializedErrors { + let valid = !this.errors.length; const modulesErrors = []; + const toolsErrors = []; + const blocksErrors = []; + const commonErrors = this.errors.slice(); + /** + * Schema errors + */ + for (const item of this.schemas.values()) { + const result = item.getSerializedErrors(); + for (const error of result.errors) { + commonErrors.push(error); + } + valid = valid && result.isValid; + } + /** + * Modules errors + */ for (const item of this.modules.values()) { - modulesErrors.push(item.getSerializedErrors()); + const result = item.getSerializedErrors(); + modulesErrors.push(result); + valid = valid && result.isValid; } - const toolsErrors = []; + /** + * Tools errors + */ for (const item of this.tools.values()) { - toolsErrors.push(item.getSerializedErrors()); + const result = item.getSerializedErrors(); + toolsErrors.push(result); + valid = valid && result.isValid; } - const blocksErrors = []; + /** + * Blocks errors + */ for (const item of this.blocks.values()) { - blocksErrors.push(item.getSerializedErrors()); + const result = item.getSerializedErrors(); + blocksErrors.push(result); + valid = valid && result.isValid; } + /** + * Common policy errors + */ for (const item of this.errors) { blocksErrors.push({ id: null, @@ -289,12 +301,12 @@ export class PolicyValidator { isValid: false }); } - const commonErrors = this.errors.slice(); return { errors: commonErrors, blocks: blocksErrors, modules: modulesErrors, tools: toolsErrors, + isValid: valid } } @@ -331,15 +343,35 @@ export class PolicyValidator { */ public getSchema(iri: string): ISchema { if (this.schemas.has(iri)) { - return this.schemas.get(iri); - } - for (const item of this.tools.values()) { - const schema = item.getSchema(iri); - if (schema) { - return schema; + const validator = this.schemas.get(iri); + if (validator.isValid) { + return validator.getSchema(); + } else { + return null; + } + } else { + for (const item of this.tools.values()) { + const schema = item.getSchema(iri); + if (schema) { + return schema; + } } + return null; } - return null; + } + + /** + * Get all schemas + * @param iri + */ + public getAllSchemas(map: Map): Map { + for (const [key, value] of this.schemas) { + map.set(key, value); + } + for (const tool of this.tools.values()) { + tool.getAllSchemas(map); + } + return map; } /** @@ -347,18 +379,17 @@ export class PolicyValidator { * @param iri */ public schemaExist(iri: string): boolean { - if (iri === '#GeoJSON') { - return true; - } if (this.schemas.has(iri)) { - return !!this.schemas.get(iri); - } - for (const item of this.tools.values()) { - if (item.schemaExist(iri)) { - return true; + const validator = this.schemas.get(iri); + return validator.isValid; + } else { + for (const item of this.tools.values()) { + if (item.schemaExist(iri)) { + return true; + } } + return false; } - return false; } /** @@ -366,15 +397,17 @@ export class PolicyValidator { * @param iri */ public unsupportedSchema(iri: string): boolean { - if (this.unsupportedSchemas.has(iri)) { - return true; - } - for (const item of this.tools.values()) { - if (item.unsupportedSchema(iri)) { - return true; + if (this.schemas.has(iri)) { + const validator = this.schemas.get(iri); + return !validator.isValid; + } else { + for (const item of this.tools.values()) { + if (item.unsupportedSchema(iri)) { + return true; + } } + return false; } - return false; } /** diff --git a/guardian-service/src/policy-engine/block-validators/schema-validator.ts b/guardian-service/src/policy-engine/block-validators/schema-validator.ts new file mode 100644 index 0000000000..4840082bac --- /dev/null +++ b/guardian-service/src/policy-engine/block-validators/schema-validator.ts @@ -0,0 +1,199 @@ +/** + * Schema Validator + */ +import { ISchema, SchemaCategory } from '@guardian/interfaces'; +import { DatabaseServer, Schema } from '@guardian/common'; +import { IBlockErrors } from './interfaces/block-errors.interface'; + +/** + * Schema Validator + */ +export class SchemaValidator { + /** + * IRI + * @private + */ + public readonly iri: string; + /** + * Common errors + * @private + */ + private readonly errors: string[]; + /** + * Template schema + * @private + */ + private readonly isTemplate: boolean; + /** + * Base schema iri + * @private + */ + private readonly baseSchema: string; + /** + * Document + * @private + */ + private _document: ISchema; + /** + * Sub schemas + * @private + */ + private _subSchemas: string[]; + /** + * Status + * @private + */ + private _validating: boolean; + /** + * Status + * @private + */ + private _validated: boolean; + /** + * Is schema valid + * @private + */ + public get isValid(): boolean { + return this.errors.length === 0; + } + + constructor( + iri: string, + schema: Schema | string, + template: boolean, + ) { + this.iri = iri; + this.errors = []; + + this._subSchemas = []; + this.isTemplate = template; + if (typeof schema === 'string') { + this.baseSchema = schema; + this._document = null; + } else if (typeof schema === 'object') { + this._document = schema; + this.baseSchema = null; + } + this._validating = false; + this._validated = false; + } + + public async load(): Promise { + if (this.baseSchema) { + const db = new DatabaseServer(null); + this._document = await db.getSchemaByIRI(this.baseSchema); + } + if (this._document) { + const defs = this._document?.document?.$defs; + if (defs && Object.prototype.toString.call(defs) === '[object Object]') { + this._subSchemas = Object.keys(defs); + } + } + } + + /** + * Add Error + * @param error + */ + public addError(error: string): void { + this.errors.push(error); + } + + /** + * Validate + * @param schemas + */ + private _validate(schemas: Map): void { + if (this._validating) { + this.addError(`Circular dependency schemas '${this.iri}'`); + this._validating = false; + this._validated = true; + return; + } + + if (this.isTemplate && !this._document) { + this._validating = false; + this._validated = true; + return; + } + + if (!this._document) { + this.addError(`Schema '${this.iri}' does not exist`); + this._validating = false; + this._validated = true; + return; + } + + this._validating = true; + + if (Array.isArray(this._subSchemas)) { + for (const subSchemaIRI of this._subSchemas) { + if (schemas.has(subSchemaIRI)) { + const subSchema = schemas.get(subSchemaIRI); + if (!subSchema._validated) { + subSchema._validate(schemas); + } + if (!subSchema.isValid) { + this.addError(`Schema with id '${this.iri}' refers to invalid schema '${subSchemaIRI}'`); + this._validating = false; + this._validated = true; + return; + } + } else { + this.addError(`Schema with id '${this.iri}' refers to non-existing schema '${subSchemaIRI}'`); + this._validating = false; + this._validated = true; + return; + } + } + } + + this._validating = false; + this._validated = true; + } + + /** + * Validate + * @param validator + */ + public async validate(schemas: Map): Promise { + if (!this._validated) { + this._validate(schemas); + } + } + + /** + * Get schema document + */ + public getSchema(): ISchema { + return this._document; + } + + /** + * Get serialized errors + */ + public getSerializedErrors(): IBlockErrors { + return { + id: this.iri, + name: 'schema', + errors: this.errors.slice(), + isValid: !this.errors.length + }; + } + + public static fromSchema(schema: Schema): SchemaValidator { + if (schema.system || schema.category === SchemaCategory.SYSTEM || schema.readonly) { + return SchemaValidator.fromSystem(schema.iri); + } else { + return new SchemaValidator(schema.iri, schema, false); + } + } + + public static fromTemplate(variable: any): SchemaValidator { + return new SchemaValidator(variable.name, variable.baseSchema, true); + } + + public static fromSystem(name: string): SchemaValidator { + return new SchemaValidator(name, null, true); + } +} \ No newline at end of file diff --git a/guardian-service/src/policy-engine/block-validators/tool-validator.ts b/guardian-service/src/policy-engine/block-validators/tool-validator.ts index 99bed81899..9f95fcaf6e 100644 --- a/guardian-service/src/policy-engine/block-validators/tool-validator.ts +++ b/guardian-service/src/policy-engine/block-validators/tool-validator.ts @@ -2,6 +2,7 @@ import { DatabaseServer, PolicyTool } from '@guardian/common'; import { BlockValidator } from './block-validator'; import { IModulesErrors } from './interfaces/modules-errors.interface'; import { ISchema, ModuleStatus } from '@guardian/interfaces'; +import { SchemaValidator } from './schema-validator'; /** * Policy Validator @@ -27,21 +28,16 @@ export class ToolValidator { * @private */ private readonly tools: Map; - /** - * Common errors - * @private - */ - private readonly errors: string[]; /** * Schemas * @private */ - private readonly schemas: Map; + private readonly schemas: Map; /** - * Unsupported Schemas + * Common errors * @private */ - private readonly unsupportedSchemas: Set; + private readonly errors: string[]; /** * Tokens * @private @@ -83,10 +79,9 @@ export class ToolValidator { this.blocks = new Map(); this.tools = new Map(); this.tags = new Map(); + this.schemas = new Map(); this.errors = []; this.permissions = ['NO_ROLE', 'ANY_ROLE', 'OWNER']; - this.schemas = new Map(); - this.unsupportedSchemas = new Set(); this.tokens = []; this.topics = []; this.tokenTemplates = []; @@ -112,7 +107,6 @@ export class ToolValidator { } } await this.registerSchemas(); - this.checkSchemas(); return true; } } @@ -121,35 +115,13 @@ export class ToolValidator { * Register schemas */ private async registerSchemas(): Promise { - const db = new DatabaseServer(null); - for (const [key, value] of this.schemas) { - if (typeof value === 'string') { - const baseSchema = await db.getSchemaByIRI(value); - this.schemas.set(key, baseSchema); - } - } + this.schemas.set('#GeoJSON', SchemaValidator.fromSystem('#GeoJSON')); const schemas = await DatabaseServer.getSchemas({ topicId: this.topicId }); for (const schema of schemas) { - this.schemas.set(schema.iri, schema); + this.schemas.set(schema.iri, SchemaValidator.fromSchema(schema)); } - } - - /** - * Check schemas - */ - private checkSchemas(): void { - for (const schema of this.schemas.values()) { - const defs = schema?.document?.$defs; - if (defs && Object.prototype.toString.call(defs) === '[object Object]') { - for (const iri of Object.keys(defs)) { - if (!this.schemaExist(iri)) { - this.schemas.delete(schema.iri); - this.unsupportedSchemas.add(schema.iri); - this.checkSchemas(); - return; - } - } - } + for (const validator of this.schemas.values()) { + await validator.load(); } } @@ -210,7 +182,7 @@ export class ToolValidator { this.variables.push(variable); switch (variable.type) { case 'Schema': { - this.schemas.set(variable.name, variable.baseSchema); + this.schemas.set(variable.name, SchemaValidator.fromTemplate(variable)); break; } case 'Token': @@ -275,6 +247,10 @@ export class ToolValidator { * Validate */ public async validate(): Promise { + const allSchemas = this.getAllSchemas(new Map()); + for (const item of this.schemas.values()) { + await item.validate(allSchemas); + } for (const item of this.blocks.values()) { await item.validate(); } @@ -320,11 +296,37 @@ export class ToolValidator { public getSerializedErrors(): IModulesErrors { let valid = !this.errors.length; const blocksErrors = []; + const toolsErrors = []; + const commonErrors = this.errors.slice(); + /** + * Schema errors + */ + for (const item of this.schemas.values()) { + const result = item.getSerializedErrors(); + for (const error of result.errors) { + commonErrors.push(error); + } + valid = valid && result.isValid; + } + /** + * Tools errors + */ + for (const item of this.tools.values()) { + const result = item.getSerializedErrors(); + toolsErrors.push(result); + valid = valid && result.isValid; + } + /** + * Blocks errors + */ for (const item of this.blocks.values()) { - const result = item.getSerializedErrors() + const result = item.getSerializedErrors(); blocksErrors.push(result); valid = valid && result.isValid; } + /** + * Common tool errors + */ for (const item of this.errors) { blocksErrors.push({ id: this.uuid, @@ -333,6 +335,9 @@ export class ToolValidator { isValid: false }); } + /** + * Result error + */ if (!valid) { blocksErrors.push({ id: this.uuid, @@ -341,10 +346,11 @@ export class ToolValidator { isValid: false }); } - const commonErrors = this.errors.slice(); + return { errors: commonErrors, blocks: blocksErrors, + tools: toolsErrors, id: this.uuid, isValid: valid } @@ -387,15 +393,35 @@ export class ToolValidator { */ public getSchema(iri: string): ISchema { if (this.schemas.has(iri)) { - return this.schemas.get(iri); - } - for (const item of this.tools.values()) { - const schema = item.getSchema(iri); - if (schema) { - return schema; + const validator = this.schemas.get(iri); + if (validator.isValid) { + return validator.getSchema(); + } else { + return null; + } + } else { + for (const item of this.tools.values()) { + const schema = item.getSchema(iri); + if (schema) { + return schema; + } } + return null; } - return null; + } + + /** + * Get all schemas + * @param iri + */ + public getAllSchemas(map: Map): Map { + for (const [key, value] of this.schemas) { + map.set(key, value); + } + for (const tool of this.tools.values()) { + tool.getAllSchemas(map); + } + return map; } /** @@ -403,19 +429,17 @@ export class ToolValidator { * @param iri */ public schemaExist(iri: string): boolean { - if (iri === '#GeoJSON') { - return true; - } if (this.schemas.has(iri)) { - return !!this.schemas.get(iri); - } - for (const item of this.tools.values()) { - const exist = item.schemaExist(iri); - if (exist) { - return exist; + const validator = this.schemas.get(iri); + return validator.isValid; + } else { + for (const item of this.tools.values()) { + if (item.schemaExist(iri)) { + return true; + } } + return false; } - return false; } /** @@ -423,15 +447,17 @@ export class ToolValidator { * @param iri */ public unsupportedSchema(iri: string): boolean { - if (this.unsupportedSchemas.has(iri)) { - return true; - } - for (const item of this.tools.values()) { - if (item.unsupportedSchema(iri)) { - return true; + if (this.schemas.has(iri)) { + const validator = this.schemas.get(iri); + return !validator.isValid; + } else { + for (const item of this.tools.values()) { + if (item.unsupportedSchema(iri)) { + return true; + } } + return false; } - return false; } /** diff --git a/guardian-service/src/policy-engine/policy-engine.service.ts b/guardian-service/src/policy-engine/policy-engine.service.ts index de9630497f..99194f302b 100644 --- a/guardian-service/src/policy-engine/policy-engine.service.ts +++ b/guardian-service/src/policy-engine/policy-engine.service.ts @@ -20,7 +20,6 @@ import { PolicyImportExport, RunFunctionAsync, Singleton, - TopicConfig, Users } from '@guardian/common'; import { PolicyImportExportHelper } from './helpers/policy-import-export-helper'; @@ -222,6 +221,14 @@ export class PolicyEngineService { } }) + this.channel.getMessages(PolicyEvents.RECORD_UPDATE_BROADCAST, async (msg: any) => { + const policy = await DatabaseServer.getPolicyById(msg?.policyId); + if (policy) { + msg.user = { did: policy.owner }; + this.channel.publish('update-record', msg); + } + }) + this.channel.getMessages('mrv-data', async (msg) => { // await PolicyComponentsUtils.ReceiveExternalData(msg); @@ -957,7 +964,7 @@ export class PolicyEngineService { this.channel.getMessages(PolicyEngineEvents.CREATE_VIRTUAL_USER, async (msg) => { try { - const { policyId, did } = msg; + const { policyId, owner } = msg; const model = await DatabaseServer.getPolicyById(policyId); if (!model) { @@ -967,34 +974,34 @@ export class PolicyEngineService { throw new Error(`Policy is not in Dry Run`); } - const topic = await TopicConfig.fromObject( - await DatabaseServer.getTopicByType(did, TopicType.UserTopic), false - ); - + const topic = await DatabaseServer.getTopicByType(owner, TopicType.UserTopic); const newPrivateKey = PrivateKey.generate(); const newAccountId = new AccountId(Date.now()); - const treasury = { - id: newAccountId, - key: newPrivateKey - }; - - const didObject = await DIDDocument.create(treasury.key, topic.topicId); - const userDID = didObject.getDid(); + const didObject = await DIDDocument.create(newPrivateKey, topic.topicId); + const did = didObject.getDid(); + const document = didObject.getDocument(); - const u = await DatabaseServer.getVirtualUsers(policyId); + const count = await DatabaseServer.getVirtualUsers(policyId); + const username = `Virtual User ${count.length}`; await DatabaseServer.createVirtualUser( policyId, - `Virtual User ${u.length}`, - userDID, - treasury.id.toString(), - treasury.key.toString() + username, + did, + newAccountId.toString(), + newPrivateKey.toString() ); const db = new DatabaseServer(policyId); - await db.saveDid({ - did: didObject.getDid(), - document: didObject.getDocument() - }) + await db.saveDid({ did, document }); + + await (new GuardiansService()) + .sendPolicyMessage(PolicyEvents.CREATE_VIRTUAL_USER, policyId, { + did, + data: { + accountId: newAccountId.toString(), + document + } + }); const users = await DatabaseServer.getVirtualUsers(policyId); return new MessageResponse(users); @@ -1017,6 +1024,10 @@ export class PolicyEngineService { await DatabaseServer.setVirtualUser(policyId, did) const users = await DatabaseServer.getVirtualUsers(policyId); + + await (new GuardiansService()) + .sendPolicyMessage(PolicyEvents.SET_VIRTUAL_USER, policyId, { did }); + return new MessageResponse(users); } catch (error) { return new MessageError(error); @@ -1025,10 +1036,6 @@ export class PolicyEngineService { this.channel.getMessages(PolicyEngineEvents.RESTART_DRY_RUN, async (msg) => { try { - if (!msg.model) { - throw new Error('Policy is empty'); - } - const policyId = msg.policyId; const user = msg.user; const owner = await this.getUserDid(user.username); diff --git a/guardian-service/src/policy-engine/policy-engine.ts b/guardian-service/src/policy-engine/policy-engine.ts index a442557fc4..37342106ea 100644 --- a/guardian-service/src/policy-engine/policy-engine.ts +++ b/guardian-service/src/policy-engine/policy-engine.ts @@ -675,7 +675,7 @@ export class PolicyEngine extends NatsService { const schemaObject = new Schema(policySchema); credentialSubject = SchemaHelper.updateObjectContext(schemaObject, credentialSubject); } - const vc = await vcHelper.createVC(owner, root.hederaAccountKey, credentialSubject); + const vc = await vcHelper.createVcDocument(credentialSubject, { did: owner, key: root.hederaAccountKey }); await DatabaseServer.saveVC({ hash: vc.toCredentialHash(), owner, @@ -786,7 +786,7 @@ export class PolicyEngine extends NatsService { credentialSubject = SchemaHelper.updateObjectContext(schemaObject, credentialSubject); } - const vc = await vcHelper.createVC(owner, root.hederaAccountKey, credentialSubject); + const vc = await vcHelper.createVcDocument(credentialSubject, { did: owner, key: root.hederaAccountKey }); await databaseServer.saveVC({ hash: vc.toCredentialHash(), diff --git a/interfaces/package.json b/interfaces/package.json index e809fceacf..d8a4d6c814 100644 --- a/interfaces/package.json +++ b/interfaces/package.json @@ -32,5 +32,6 @@ "prepack": "npm run build", "test": "echo \"Error: no test specified\" && exit 1" }, - "version": "2.19.1" + "version": "2.20.0-prerelease", + "stableVersion": "2.19.1" } diff --git a/interfaces/src/helpers/generate-document.ts b/interfaces/src/helpers/generate-document.ts new file mode 100644 index 0000000000..b78d99ae2a --- /dev/null +++ b/interfaces/src/helpers/generate-document.ts @@ -0,0 +1,208 @@ +import { GenerateID, GenerateUUIDv4, SchemaField, SchemaHelper } from '..'; +import { Schema } from '../models/schema'; + +interface GenerateOption { + enableHiddenFields: boolean +} + +export class DocumentGenerator { + /** + * Default option + */ + public static readonly DefaultOption: GenerateOption = { + enableHiddenFields: false + } + + /** + * Generate new field + * @param pattern pattern + * @returns document + */ + private static _generateString(pattern: string): string { + if (pattern === '^ipfs:\/\/.+') { + return `ipfs://${GenerateID()}`; + } { + return GenerateID(); + } + } + + /** + * Generate new field + * @param field field + * @param context context + * @param option option + * @returns document + */ + private static _generateGeoJSON( + subSchema: SchemaField, + context: string[], + option: GenerateOption + ): any { + const json: any = {}; + json.type = 'Point'; + json['@context'] = context; + json.coordinates = [0.0, 0.0]; + return json; + } + + /** + * Generate new field + * @param field field + * @param context context + * @param option option + * @returns document + */ + private static _generateSubDocument( + subSchema: SchemaField, + context: string[], + option: GenerateOption + ): any { + const { type } = SchemaHelper.parseRef(subSchema.type); + const json: any = {}; + for (const field of subSchema.fields) { + const value = DocumentGenerator.generateField(field, context, option); + if (value !== undefined) { + json[field.name] = value; + } + } + json.type = type; + json['@context'] = context; + return json; + } + + /** + * Generate new field + * @param field field + * @param context context + * @param option option + * @returns document + */ + private static _generateSimpleField( + field: SchemaField, + context: string[], + option: GenerateOption + ): any { + if (Array.isArray(field.examples) && field.examples[0]) { + return field.examples[0]; + } + switch (field.type) { + case 'number': + return 1; + case 'integer': + return 1; + case 'boolean': + return true; + case 'string': { + switch (field.customType) { + case 'enum': + return field.enum[0]; + case 'hederaAccount': + return '0.0.1'; + default: + break; + } + switch (field.format) { + case 'date': + return '2000-01-01'; + case 'time': + return '00:00:00'; + case 'date-time': + return '2000-01-01T01:00:00.000Z'; + case 'duration': + return 'P1D'; + case 'url': + return 'https://example.com'; + case 'uri': + return 'example:uri'; + case 'email': + return 'example@email.com'; + default: + break; + } + if (field.pattern) { + return DocumentGenerator._generateString(field.pattern); + } + return 'example'; + } + case 'null': + return undefined; + default: + break; + } + return undefined; + } + + /** + * Generate new field + * @param field field + * @param context context + * @param option option + * @returns document + */ + private static _generateField( + field: SchemaField, + context: string[], + option: GenerateOption + ): any { + if (!option.enableHiddenFields && field.hidden) { + return undefined; + } + if (field.isRef) { + if (field.type === '#GeoJSON') { + return DocumentGenerator._generateGeoJSON(field, context, option); + } else { + return DocumentGenerator._generateSubDocument(field, context, option); + } + } else { + return DocumentGenerator._generateSimpleField(field, context, option); + } + } + + /** + * Generate new field + * @param field field + * @param context context + * @param option option + * @returns document + */ + private static generateField( + field: SchemaField, + context: string[], + option: GenerateOption + ): any { + const value = DocumentGenerator._generateField(field, context, option); + if (field.isArray && value !== undefined) { + if (Array.isArray(value)) { + return value; + } else { + return [value]; + } + } else { + return value; + } + } + + /** + * Generate new document + * @param schema schema + * @param option option + * @returns document + */ + public static generateDocument(schema: Schema, option?: GenerateOption): any { + if (!option) { + option = DocumentGenerator.DefaultOption; + } + const context: string[] = [schema.iri]; + const json: any = {}; + json.id = GenerateUUIDv4(); + for (const field of schema.fields) { + const value = DocumentGenerator.generateField(field, context, option); + if (value !== undefined) { + json[field.name] = value; + } + } + json.type = schema.type; + json['@context'] = context; + return json; + } +} diff --git a/interfaces/src/helpers/generate-uuid-v4.ts b/interfaces/src/helpers/generate-uuid-v4.ts index eb23ed68e3..b5939fe60d 100644 --- a/interfaces/src/helpers/generate-uuid-v4.ts +++ b/interfaces/src/helpers/generate-uuid-v4.ts @@ -10,3 +10,14 @@ export function GenerateUUIDv4() { return v.toString(16); }); } + +/** + * Generate random UUID + */ +export function GenerateID() { + return 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'.replace(/[x]/g, (c) => { + // tslint:disable-next-line:no-bitwise + const v = Math.random() * 16 | 0; + return v.toString(16); + }); +} \ No newline at end of file diff --git a/interfaces/src/helpers/index.ts b/interfaces/src/helpers/index.ts index a81605c9db..0e8f821df0 100644 --- a/interfaces/src/helpers/index.ts +++ b/interfaces/src/helpers/index.ts @@ -5,3 +5,4 @@ export * from './remove-object-properties'; export * from './schema-helper'; export * from './sort-objects-array'; export * from './geojson-schema'; +export * from './generate-document'; \ No newline at end of file diff --git a/interfaces/src/helpers/schema-helper.ts b/interfaces/src/helpers/schema-helper.ts index 98d9b186dc..4fefaaad77 100644 --- a/interfaces/src/helpers/schema-helper.ts +++ b/interfaces/src/helpers/schema-helper.ts @@ -33,6 +33,7 @@ export class SchemaHelper { customType: null, comment: null, isPrivate: null, + examples: null }; let _property = property; const readonly = _property.readOnly; @@ -56,6 +57,7 @@ export class SchemaHelper { field.pattern = _property.pattern ? String(_property.pattern) : null; field.enum = _property.enum; field.remoteLink = _property.$ref; + field.examples = Array.isArray(_property.examples) ? _property.examples : null; } field.readOnly = !!(_property.readOnly || readonly); return field; @@ -141,6 +143,9 @@ export class SchemaHelper { if (field.pattern) { item.pattern = field.pattern; } + if (field.examples) { + item.examples = field.examples; + } } property.$comment = SchemaHelper.buildFieldComment(field, name, contextURL, orderPosition); @@ -297,6 +302,23 @@ export class SchemaHelper { ); } + /** + * Update schema fields + * @param document + * @param fn + */ + public static updateFields(document: ISchemaDocument, fn: (name: string, property: any) => any): ISchemaDocument { + if (!document || !document.properties) { + return document; + } + const properties = Object.keys(document.properties); + for (const name of properties) { + const property = document.properties[name]; + document.properties[name] = fn(name, property); + } + return document; + } + /** * Build document from schema * @param schema diff --git a/interfaces/src/interface/chain-item.interface.ts b/interfaces/src/interface/chain-item.interface.ts index c26a59d84c..90d921b516 100644 --- a/interfaces/src/interface/chain-item.interface.ts +++ b/interfaces/src/interface/chain-item.interface.ts @@ -172,10 +172,14 @@ export interface ITokenReport { * Report date */ date: string; + /** + * Token expected + */ + expected?: string; /** * Token amount */ - amount: string; + amount?: string; /** * Report tag */ diff --git a/interfaces/src/interface/schema-field.interface.ts b/interfaces/src/interface/schema-field.interface.ts index 397c386b0f..25fe6ec969 100644 --- a/interfaces/src/interface/schema-field.interface.ts +++ b/interfaces/src/interface/schema-field.interface.ts @@ -121,4 +121,9 @@ export interface SchemaField { * Is hidden field */ hidden?: boolean; + + /** + * Examples data + */ + examples?: any[]; } diff --git a/interfaces/src/models/schema.ts b/interfaces/src/models/schema.ts index 6450873df7..298545509c 100644 --- a/interfaces/src/models/schema.ts +++ b/interfaces/src/models/schema.ts @@ -360,4 +360,19 @@ export class Schema implements ISchema { } } } + + /** + * Set example data + * @param data + */ + public setExample(data: any): void { + if (data) { + this.document = SchemaHelper.updateFields(this.document, (name: string, property: any) => { + if (!(property.$ref && !property.type) && data.hasOwnProperty(name)) { + property.examples = [data[name]]; + } + return property; + }); + } + } } diff --git a/interfaces/src/type/messages/message-api.type.ts b/interfaces/src/type/messages/message-api.type.ts index 8a38f23fa7..2cd444431b 100644 --- a/interfaces/src/type/messages/message-api.type.ts +++ b/interfaces/src/type/messages/message-api.type.ts @@ -142,6 +142,7 @@ export enum MessageAPI { UPDATE_EVENT = 'update-event', ERROR_EVENT = 'error-event', UPDATE_USER_INFO_EVENT = 'update-user-info-event', + UPDATE_RECORD = 'update-record-event', MEECO_AUTH_REQUEST = 'MEECO_AUTH_REQUEST', MEECO_AUTH_PRESENT_VP = 'MEECO_AUTH_PRESENT_VP', MEECO_VERIFY_VP = 'MEECO_VERIFY_VP', @@ -169,6 +170,17 @@ export enum MessageAPI { VALIDATE_TOOL = 'VALIDATE_TOOL', SEARCH_BLOCKS = 'SEARCH_BLOCKS', GET_SERIALS = 'GET_SERIALS', + START_RECORDING = 'START_RECORDING', + STOP_RECORDING = 'STOP_RECORDING', + GET_RECORDED_ACTIONS = 'GET_RECORDED_ACTIONS', + GET_RECORD_STATUS = 'GET_RECORD_STATUS', + RUN_RECORD = 'RUN_RECORD', + STOP_RUNNING = 'STOP_RUNNING', + GET_RECORD_RESULTS = 'GET_RECORD_RESULTS', + GET_RECORD_DETAILS = 'GET_RECORD_DETAILS', + FAST_FORWARD = 'FAST_FORWARD', + RECORD_RETRY_STEP = 'RECORD_RETRY_STEP', + RECORD_SKIP_STEP = 'RECORD_SKIP_STEP', } /** diff --git a/interfaces/src/type/messages/policy-engine-events.ts b/interfaces/src/type/messages/policy-engine-events.ts index c685b5ec6d..dd174cfcdb 100644 --- a/interfaces/src/type/messages/policy-engine-events.ts +++ b/interfaces/src/type/messages/policy-engine-events.ts @@ -34,7 +34,7 @@ export enum PolicyEngineEvents { GET_VIRTUAL_USERS = 'policy-engine-event-get-virtual-users', CREATE_VIRTUAL_USER = 'policy-engine-event-create-virtual-user', SET_VIRTUAL_USER = 'policy-engine-event-login-virtual-user', - RESTART_DRY_RUN= 'policy-engine-event-restart-dry-run', + RESTART_DRY_RUN = 'policy-engine-event-restart-dry-run', GET_VIRTUAL_DOCUMENTS = 'policy-engine-event-get-virtual-documents', DELETE_POLICY_ASYNC = 'policy-engine-event-delete-policy-async', GET_INVITE = 'policy-engine-event-get-invite', @@ -43,5 +43,5 @@ export enum PolicyEngineEvents { CLONE_POLICY_ASYNC = 'policy-engine-event-clone-policy-async', GET_TOKENS_MAP = 'policy-engine-event-get-tokens-map', SET_MULTI_POLICY = 'policy-engine-event-set-multi-policy', - GET_MULTI_POLICY = 'policy-engine-event-get-multi-policy' + GET_MULTI_POLICY = 'policy-engine-event-get-multi-policy', } diff --git a/interfaces/src/type/messages/policy-events.ts b/interfaces/src/type/messages/policy-events.ts index 126645528d..c6bf16635e 100644 --- a/interfaces/src/type/messages/policy-events.ts +++ b/interfaces/src/type/messages/policy-events.ts @@ -23,5 +23,19 @@ export enum PolicyEvents { BLOCK_UPDATE_BROADCAST = 'policy-event-block-update-broadcast', MRV_DATA = 'policy-event-mrv-data', GET_BLOCK_ABOUT = 'policy-event-get-block-about', - CHECK_IF_ALIVE = 'check-if-alive' + CHECK_IF_ALIVE = 'check-if-alive', + CREATE_VIRTUAL_USER = 'policy-event-create-virtual-user', + SET_VIRTUAL_USER = 'policy-event-login-virtual-user', + + RECORD_UPDATE_BROADCAST = 'policy-event-record-update-broadcast', + GET_RECORD_STATUS = 'policy-event-get-record-status', + START_RECORDING = 'policy-event-start-recording', + STOP_RECORDING = 'policy-event-stop-recording', + GET_RECORDED_ACTIONS = 'policy-event-get-recorded-actions', + RUN_RECORD = 'policy-event-run-record', + STOP_RUNNING = 'policy-event-stop-running', + GET_RECORD_RESULTS = 'policy-event-get-record-results', + FAST_FORWARD = 'policy-event-fast-forward', + RECORD_RETRY_STEP = 'policy-event-retry-step', + RECORD_SKIP_STEP = 'policy-event-skip-step', } diff --git a/logger-service/package.json b/logger-service/package.json index 1eaf4e3c53..514c6e309f 100644 --- a/logger-service/package.json +++ b/logger-service/package.json @@ -4,8 +4,8 @@ "@azure/core-rest-pipeline": "1.12.1" }, "dependencies": { - "@guardian/common": "^2.19.1", - "@guardian/interfaces": "^2.19.1", + "@guardian/common": "^2.20.0-prerelease", + "@guardian/interfaces": "^2.20.0-prerelease", "@mikro-orm/core": "5.7.12", "@mikro-orm/mongodb": "5.7.12", "@nestjs/common": "^9.4.1", @@ -51,5 +51,6 @@ "start": "node dist/index.js", "watch": "nodemon src/index.ts" }, - "version": "2.19.1" + "version": "2.20.0-prerelease", + "stableVersion": "2.19.1" } diff --git a/mrv-sender/package.json b/mrv-sender/package.json index ee2a2c4a73..3578cfc85b 100644 --- a/mrv-sender/package.json +++ b/mrv-sender/package.json @@ -4,7 +4,7 @@ "@azure/core-rest-pipeline": "1.12.1" }, "dependencies": { - "@guardian/common": "^2.19.1", + "@guardian/common": "^2.20.0-prerelease", "@transmute/credentials-context": "0.7.0-unstable.80", "@transmute/did-context": "0.7.0-unstable.80", "@transmute/ed25519-signature-2018": "0.7.0-unstable.80", @@ -42,5 +42,6 @@ "dev:docker": "nodemon .", "start": "node dist/index.js" }, - "version": "2.19.1" + "version": "2.20.0-prerelease", + "stableVersion": "2.19.1" } diff --git a/notification-service/package.json b/notification-service/package.json index 311cb2c7bd..3a8550c7ea 100644 --- a/notification-service/package.json +++ b/notification-service/package.json @@ -4,8 +4,8 @@ "@azure/core-rest-pipeline": "1.12.1" }, "dependencies": { - "@guardian/common": "^2.19.1", - "@guardian/interfaces": "^2.19.1", + "@guardian/common": "^2.20.0-prerelease", + "@guardian/interfaces": "^2.20.0-prerelease", "@mikro-orm/core": "5.7.12", "@mikro-orm/mongodb": "5.7.12", "@nestjs/common": "^9.4.1", @@ -50,5 +50,6 @@ "start": "node dist/index.js", "watch": "nodemon src/index.ts" }, - "version": "2.19.1" + "version": "2.20.0-prerelease", + "stableVersion": "2.19.1" } diff --git a/package.json b/package.json index 4649e197c6..fbfb003b33 100644 --- a/package.json +++ b/package.json @@ -21,11 +21,12 @@ "api-tests", "notification-service" ], - "version": "2.19.1", + "version": "2.20.0-prerelease", "devDependencies": { "detect-secrets": "^1.0.6" }, "scripts": { "detect-secrets": "detect-secrets-launcher --word-list exclude-secrets.txt k8s-manifests/**/* */src/**.ts **/.env*" - } + }, + "stableVersion": "2.19.1" } diff --git a/policy-service/package.json b/policy-service/package.json index e0749c7f72..592b4218c1 100644 --- a/policy-service/package.json +++ b/policy-service/package.json @@ -14,8 +14,8 @@ "@azure/core-rest-pipeline": "1.12.1" }, "dependencies": { - "@guardian/common": "2.19.1", - "@guardian/interfaces": "2.19.1", + "@guardian/common": "2.20.0-prerelease", + "@guardian/interfaces": "2.20.0-prerelease", "@hashgraph/sdk": "2.34.1", "@mattrglobal/jsonld-signatures-bbs": "^1.1.2", "@meeco/cryppo": "2.0.2", @@ -94,5 +94,6 @@ "test:local": "mocha tests/**/*.test.js", "test:stability": "mocha tests/stability.test.js" }, - "version": "2.19.1" + "version": "2.20.0-prerelease", + "stableVersion": "2.19.1" } diff --git a/policy-service/src/api/policy-process.ts b/policy-service/src/api/policy-process.ts index 48a941d9be..43ecc59b0c 100644 --- a/policy-service/src/api/policy-process.ts +++ b/policy-service/src/api/policy-process.ts @@ -109,11 +109,11 @@ Promise.all([ const policyModel = await generator.generate(policyConfig, skipRegistration, policyValidator); if ((policyModel as { type: 'error', message: string }).type === 'error') { - generator.publish(PolicyEvents.POLICY_READY, { + await generator.publish(PolicyEvents.POLICY_READY, { policyId: policyId.toString(), error: (policyModel as { type: 'error', message: string }).message }); - return; + process.exit(0); // throw new Error((policyModel as {type: 'error', message: string}).message); } diff --git a/policy-service/src/policy-engine/block-tree-generator.ts b/policy-service/src/policy-engine/block-tree-generator.ts index 0a261b432f..6089d4aea9 100644 --- a/policy-service/src/policy-engine/block-tree-generator.ts +++ b/policy-service/src/policy-engine/block-tree-generator.ts @@ -7,6 +7,7 @@ import { PolicyValidator } from '@policy-engine/block-validators'; import { headers } from 'nats'; import { Inject } from '@helpers/decorators/inject'; import { ComponentsService } from './helpers/components-service'; +import { RecordUtils } from './record-utils'; /** * Block tree generator @@ -58,7 +59,7 @@ export class BlockTreeGenerator extends NatsService { } else { userFull.setUsername(regUser.username); } - const groups = await policy.databaseServer.getGroupsByUser(policy.policyId, userFull.did); + const groups = await policy.components.databaseServer.getGroupsByUser(policy.policyId, userFull.did); for (const group of groups) { if (group.active !== false) { return userFull.setGroup(group); @@ -104,7 +105,6 @@ export class BlockTreeGenerator extends NatsService { }); this.getPolicyMessages(PolicyEvents.GET_ROOT_BLOCK_DATA, policyId, async (msg: any) => { - const { user } = msg; const userFull = await this.getUser(policyInstance, user); @@ -118,7 +118,6 @@ export class BlockTreeGenerator extends NatsService { }); this.getPolicyMessages(PolicyEvents.GET_POLICY_GROUPS, policyId, async (msg: any) => { - const { user } = msg; const userFull = await this.getUser(policyInstance, user); @@ -133,18 +132,15 @@ export class BlockTreeGenerator extends NatsService { }); this.getPolicyMessages(PolicyEvents.SELECT_POLICY_GROUP, policyId, async (msg: any) => { - const { user, uuid } = msg; - const userFull = await this.getUser(policyInstance, user); - const templates = policyInstance.components.getGroupTemplates(); - if (templates.length === 0) { - return new MessageResponse([] as any); - } + // <-- Record + await RecordUtils.RecordSelectGroup(policyId, userFull, uuid); + // Record --> - await PolicyComponentsUtils.SelectGroup(policyInstance, userFull, uuid); - return new MessageResponse(true as any); + const result = policyInstance.components.selectGroup(userFull, uuid) as any; + return new MessageResponse(result); }); this.getPolicyMessages(PolicyEvents.GET_BLOCK_DATA, policyId, async (msg: any) => { @@ -182,12 +178,14 @@ export class BlockTreeGenerator extends NatsService { }); this.getPolicyMessages(PolicyEvents.SET_BLOCK_DATA, policyId, async (msg: any) => { - const { user, blockId, data } = msg; - const userFull = await this.getUser(policyInstance, user); const block = PolicyComponentsUtils.GetBlockByUUID(blockId); + // <-- Record + await RecordUtils.RecordSetBlockData(policyId, userFull, block, data); + // Record --> + if (block && (await block.isAvailable(userFull))) { if (typeof block.setData !== 'function') { throw new Error( @@ -203,10 +201,13 @@ export class BlockTreeGenerator extends NatsService { this.getPolicyMessages(PolicyEvents.SET_BLOCK_DATA_BY_TAG, policyId, async (msg: any) => { const { user, tag, data } = msg; - const userFull = await this.getUser(policyInstance, user); const block = PolicyComponentsUtils.GetBlockByTag(policyId, tag); + // <-- Record + await RecordUtils.RecordSetBlockData(policyId, userFull, block, data); + // Record --> + if (block && (await block.isAvailable(userFull))) { const result = await block.setData(userFull, data); return new MessageResponse(result); @@ -236,14 +237,88 @@ export class BlockTreeGenerator extends NatsService { this.getPolicyMessages(PolicyEvents.MRV_DATA, policyId, async (msg: any) => { const { data } = msg; + // <-- Record + await RecordUtils.RecordExternalData(policyId, data); + // Record --> + for (const block of PolicyComponentsUtils.ExternalDataBlocks.values()) { if (block.policyId === policyId) { await (block as any).receiveData(data); } } + return new MessageResponse({}); + }); + this.getPolicyMessages(PolicyEvents.CREATE_VIRTUAL_USER, policyId, async (msg: any) => { + const { did, data } = msg; + await RecordUtils.RecordCreateUser(policyId, did, data); return new MessageResponse({}); - }) + }); + + this.getPolicyMessages(PolicyEvents.SET_VIRTUAL_USER, policyId, async (msg: any) => { + const { did } = msg; + await RecordUtils.RecordSetUser(policyId, did); + return new MessageResponse({}); + }); + } + + /** + * Init record events + */ + async initRecordEvents(policyId: string): Promise { + this.getPolicyMessages(PolicyEvents.START_RECORDING, policyId, async (msg: any) => { + const result = await RecordUtils.StartRecording(policyId); + return new MessageResponse(result); + }); + + this.getPolicyMessages(PolicyEvents.STOP_RECORDING, policyId, async (msg: any) => { + const result = await RecordUtils.StopRecording(policyId); + return new MessageResponse(result); + }); + + this.getPolicyMessages(PolicyEvents.GET_RECORD_STATUS, policyId, async (msg: any) => { + const result = RecordUtils.GetRecordStatus(policyId); + return new MessageResponse(result); + }); + + this.getPolicyMessages(PolicyEvents.GET_RECORDED_ACTIONS, policyId, async (msg: any) => { + const result = await RecordUtils.GetRecordedActions(policyId); + return new MessageResponse(result); + }); + + this.getPolicyMessages(PolicyEvents.RUN_RECORD, policyId, async (msg: any) => { + const { records, results, options } = msg; + const result = await RecordUtils.RunRecord(policyId, records, results, options); + return new MessageResponse(result); + }); + + this.getPolicyMessages(PolicyEvents.STOP_RUNNING, policyId, async (msg: any) => { + const result = await RecordUtils.StopRunning(policyId); + return new MessageResponse(result); + }); + + this.getPolicyMessages(PolicyEvents.GET_RECORD_RESULTS, policyId, async (msg: any) => { + const result = await RecordUtils.GetRecordResults(policyId); + return new MessageResponse(result); + }); + + this.getPolicyMessages(PolicyEvents.FAST_FORWARD, policyId, async (msg: any) => { + const options = msg; + const result = await RecordUtils.FastForward(policyId, options); + return new MessageResponse(result); + }); + + this.getPolicyMessages(PolicyEvents.RECORD_RETRY_STEP, policyId, async (msg: any) => { + const options = msg; + const result = await RecordUtils.RetryStep(policyId, options); + return new MessageResponse(result); + }); + + this.getPolicyMessages(PolicyEvents.RECORD_SKIP_STEP, policyId, async (msg: any) => { + const options = msg; + const result = await RecordUtils.SkipStep(policyId, options); + return new MessageResponse(result); + }); } /** @@ -276,16 +351,19 @@ export class BlockTreeGenerator extends NatsService { await components.registerTool(tool); } - const { rootInstance, allInstances } = - await PolicyComponentsUtils.BuildBlockTree(policy, policyId, components); + const { + rootInstance, + allInstances + } = await PolicyComponentsUtils.BuildBlockTree(policy, policyId, components); + await components.registerRoot(rootInstance); if (!skipRegistration) { - await PolicyComponentsUtils.RegisterPolicyInstance(policyId, policy); + await PolicyComponentsUtils.RegisterPolicyInstance(policyId, policy, components); await PolicyComponentsUtils.RegisterBlockTree(allInstances); this.models.set(policyId, rootInstance); } await this.initPolicyEvents(policyId, rootInstance); - + await this.initRecordEvents(policyId); return rootInstance; } catch (error) { new Logger().error(`Error build policy ${error}`, ['POLICY', policy.name, policyId.toString()]); diff --git a/policy-service/src/policy-engine/block-validators/interfaces/serialized-errors.interface.ts b/policy-service/src/policy-engine/block-validators/interfaces/serialized-errors.interface.ts index 2955052503..7885f2b17b 100644 --- a/policy-service/src/policy-engine/block-validators/interfaces/serialized-errors.interface.ts +++ b/policy-service/src/policy-engine/block-validators/interfaces/serialized-errors.interface.ts @@ -24,4 +24,9 @@ export interface ISerializedErrors { * Tools */ tools?: IModulesErrors[]; + + /** + * Is valid + */ + isValid: boolean; } \ No newline at end of file diff --git a/policy-service/src/policy-engine/block-validators/module-validator.ts b/policy-service/src/policy-engine/block-validators/module-validator.ts index 919a9e03c0..fbd6f1a23a 100644 --- a/policy-service/src/policy-engine/block-validators/module-validator.ts +++ b/policy-service/src/policy-engine/block-validators/module-validator.ts @@ -6,6 +6,7 @@ import { IModulesErrors } from './interfaces/modules-errors.interface'; import { ISchema, ModuleStatus } from '@guardian/interfaces'; import { DatabaseServer } from '@guardian/common'; import { ToolValidator } from './tool-validator'; +import { SchemaValidator } from './schema-validator'; /** * Module Validator @@ -45,12 +46,7 @@ export class ModuleValidator { * Schemas * @private */ - private readonly schemas: Map; - /** - * Unsupported Schemas - * @private - */ - private readonly unsupportedSchemas: Set; + private readonly schemas: Map; /** * Tokens * @private @@ -85,7 +81,6 @@ export class ModuleValidator { this.errors = []; this.permissions = ['NO_ROLE', 'ANY_ROLE', 'OWNER']; this.schemas = new Map(); - this.unsupportedSchemas = new Set(); this.tokens = []; this.topics = []; this.tokenTemplates = []; @@ -109,7 +104,6 @@ export class ModuleValidator { } } await this.registerSchemas(); - this.checkSchemas(); return true; } } @@ -118,31 +112,9 @@ export class ModuleValidator { * Register schemas */ private async registerSchemas(): Promise { - const db = new DatabaseServer(null); - for (const [key, value] of this.schemas) { - if (typeof value === 'string') { - const baseSchema = await db.getSchemaByIRI(value); - this.schemas.set(key, baseSchema); - } - } - } - - /** - * Check schemas - */ - private checkSchemas(): void { - for (const schema of this.schemas.values()) { - const defs = schema?.document?.$defs; - if (defs && Object.prototype.toString.call(defs) === '[object Object]') { - for (const iri of Object.keys(defs)) { - if (!this.schemaExist(iri)) { - this.schemas.delete(schema.iri); - this.unsupportedSchemas.add(schema.iri); - this.checkSchemas(); - return; - } - } - } + this.schemas.set('#GeoJSON', SchemaValidator.fromSystem('#GeoJSON')); + for (const validator of this.schemas.values()) { + await validator.load(); } } @@ -203,7 +175,7 @@ export class ModuleValidator { this.variables.push(variable); switch (variable.type) { case 'Schema': { - this.schemas.set(variable.name, variable.baseSchema); + this.schemas.set(variable.name, SchemaValidator.fromTemplate(variable)); break; } case 'Token': @@ -271,6 +243,10 @@ export class ModuleValidator { * Validate */ public async validate() { + const allSchemas = this.getAllSchemas(new Map()); + for (const item of this.schemas.values()) { + await item.validate(allSchemas); + } for (const item of this.tools.values()) { await item.validate(); } @@ -318,18 +294,38 @@ export class ModuleValidator { */ public getSerializedErrors(): IModulesErrors { let valid = !this.errors.length; + const blocksErrors = []; const toolsErrors = []; + const commonErrors = this.errors.slice(); + /** + * Schema errors + */ + for (const item of this.schemas.values()) { + const result = item.getSerializedErrors(); + for (const error of result.errors) { + commonErrors.push(error); + } + valid = valid && result.isValid; + } + /** + * Tools errors + */ for (const item of this.tools.values()) { const result = item.getSerializedErrors(); toolsErrors.push(result); valid = valid && result.isValid; } - const blocksErrors = []; + /** + * Blocks errors + */ for (const item of this.blocks.values()) { const result = item.getSerializedErrors(); blocksErrors.push(result); valid = valid && result.isValid; } + /** + * Common module errors + */ for (const item of this.errors) { blocksErrors.push({ id: this.uuid, @@ -338,6 +334,9 @@ export class ModuleValidator { isValid: false }); } + /** + * Result error + */ if (!valid) { blocksErrors.push({ id: this.uuid, @@ -346,7 +345,6 @@ export class ModuleValidator { isValid: false }); } - const commonErrors = this.errors.slice(); return { errors: commonErrors, blocks: blocksErrors, @@ -393,15 +391,35 @@ export class ModuleValidator { */ public getSchema(iri: string): ISchema { if (this.schemas.has(iri)) { - return this.schemas.get(iri); - } - for (const item of this.tools.values()) { - const schema = item.getSchema(iri); - if (schema) { - return schema; + const validator = this.schemas.get(iri); + if (validator.isValid) { + return validator.getSchema(); + } else { + return null; } + } else { + for (const item of this.tools.values()) { + const schema = item.getSchema(iri); + if (schema) { + return schema; + } + } + return null; } - return null; + } + + /** + * Get all schemas + * @param iri + */ + public getAllSchemas(map: Map): Map { + for (const [key, value] of this.schemas) { + map.set(key, value); + } + for (const tool of this.tools.values()) { + tool.getAllSchemas(map); + } + return map; } /** @@ -409,19 +427,17 @@ export class ModuleValidator { * @param iri */ public schemaExist(iri: string): boolean { - if (iri === '#GeoJSON') { - return true; - } if (this.schemas.has(iri)) { - return !!this.schemas.get(iri); - } - for (const item of this.tools.values()) { - const exist = item.schemaExist(iri); - if (exist) { - return exist; + const validator = this.schemas.get(iri); + return validator.isValid; + } else { + for (const item of this.tools.values()) { + if (item.schemaExist(iri)) { + return true; + } } + return false; } - return false; } /** @@ -429,15 +445,17 @@ export class ModuleValidator { * @param iri */ public unsupportedSchema(iri: string): boolean { - if (this.unsupportedSchemas.has(iri)) { - return true; - } - for (const item of this.tools.values()) { - if (item.unsupportedSchema(iri)) { - return true; + if (this.schemas.has(iri)) { + const validator = this.schemas.get(iri); + return !validator.isValid; + } else { + for (const item of this.tools.values()) { + if (item.unsupportedSchema(iri)) { + return true; + } } + return false; } - return false; } /** diff --git a/policy-service/src/policy-engine/block-validators/policy-validator.ts b/policy-service/src/policy-engine/block-validators/policy-validator.ts index 7dd46535ba..b6d7c49631 100644 --- a/policy-service/src/policy-engine/block-validators/policy-validator.ts +++ b/policy-service/src/policy-engine/block-validators/policy-validator.ts @@ -4,6 +4,7 @@ import { BlockValidator } from './block-validator'; import { ModuleValidator } from './module-validator'; import { ISerializedErrors } from './interfaces/serialized-errors.interface'; import { ToolValidator } from './tool-validator'; +import { SchemaValidator } from './schema-validator'; /** * Policy Validator @@ -63,12 +64,7 @@ export class PolicyValidator { * Schemas * @private */ - private readonly schemas: Map; - /** - * Unsupported Schemas - * @private - */ - private readonly unsupportedSchemas: Set; + private readonly schemas: Map; constructor(policy: Policy) { this.blocks = new Map(); @@ -82,7 +78,6 @@ export class PolicyValidator { this.policyTopics = policy.policyTopics || []; this.policyGroups = policy.policyGroups; this.schemas = new Map(); - this.unsupportedSchemas = new Set(); } /** @@ -97,7 +92,6 @@ export class PolicyValidator { this.addPermissions(policy.policyRoles); await this.registerBlock(policy.config); await this.registerSchemas(); - this.checkSchemas(); return true; } } @@ -106,29 +100,13 @@ export class PolicyValidator { * Register schemas */ private async registerSchemas(): Promise { + this.schemas.set('#GeoJSON', SchemaValidator.fromSystem('#GeoJSON')); const schemas = await DatabaseServer.getSchemas({ topicId: this.topicId }); - this.schemas.clear(); for (const schema of schemas) { - this.schemas.set(schema.iri, schema); + this.schemas.set(schema.iri, SchemaValidator.fromSchema(schema)); } - } - - /** - * Check schemas - */ - private checkSchemas(): void { - for (const schema of this.schemas.values()) { - const defs = schema?.document?.$defs; - if (defs && Object.prototype.toString.call(defs) === '[object Object]') { - for (const iri of Object.keys(defs)) { - if (!this.schemaExist(iri)) { - this.schemas.delete(schema.iri); - this.unsupportedSchemas.add(schema.iri); - this.checkSchemas(); - return; - } - } - } + for (const validator of this.schemas.values()) { + await validator.load(); } } @@ -208,6 +186,10 @@ export class PolicyValidator { * Validate */ public async validate(): Promise { + const allSchemas = this.getAllSchemas(new Map()); + for (const item of this.schemas.values()) { + await item.validate(allSchemas); + } for (const item of this.modules.values()) { await item.validate(); } @@ -269,18 +251,48 @@ export class PolicyValidator { * Get serialized errors */ public getSerializedErrors(): ISerializedErrors { + let valid = !this.errors.length; const modulesErrors = []; + const toolsErrors = []; + const blocksErrors = []; + const commonErrors = this.errors.slice(); + /** + * Schema errors + */ + for (const item of this.schemas.values()) { + const result = item.getSerializedErrors(); + for (const error of result.errors) { + commonErrors.push(error); + } + valid = valid && result.isValid; + } + /** + * Modules errors + */ for (const item of this.modules.values()) { - modulesErrors.push(item.getSerializedErrors()); + const result = item.getSerializedErrors(); + modulesErrors.push(result); + valid = valid && result.isValid; } - const toolsErrors = []; + /** + * Tools errors + */ for (const item of this.tools.values()) { - toolsErrors.push(item.getSerializedErrors()); + const result = item.getSerializedErrors(); + toolsErrors.push(result); + valid = valid && result.isValid; } - const blocksErrors = []; + /** + * Blocks errors + */ for (const item of this.blocks.values()) { - blocksErrors.push(item.getSerializedErrors()); + const result = item.getSerializedErrors(); + blocksErrors.push(result); + valid = valid && result.isValid; } + /** + * Common policy errors + */ for (const item of this.errors) { blocksErrors.push({ id: null, @@ -289,12 +301,12 @@ export class PolicyValidator { isValid: false }); } - const commonErrors = this.errors.slice(); return { errors: commonErrors, blocks: blocksErrors, modules: modulesErrors, tools: toolsErrors, + isValid: valid } } @@ -331,15 +343,35 @@ export class PolicyValidator { */ public getSchema(iri: string): ISchema { if (this.schemas.has(iri)) { - return this.schemas.get(iri); - } - for (const item of this.tools.values()) { - const schema = item.getSchema(iri); - if (schema) { - return schema; + const validator = this.schemas.get(iri); + if (validator.isValid) { + return validator.getSchema(); + } else { + return null; + } + } else { + for (const item of this.tools.values()) { + const schema = item.getSchema(iri); + if (schema) { + return schema; + } } + return null; } - return null; + } + + /** + * Get all schemas + * @param iri + */ + public getAllSchemas(map: Map): Map { + for (const [key, value] of this.schemas) { + map.set(key, value); + } + for (const tool of this.tools.values()) { + tool.getAllSchemas(map); + } + return map; } /** @@ -347,18 +379,17 @@ export class PolicyValidator { * @param iri */ public schemaExist(iri: string): boolean { - if (iri === '#GeoJSON') { - return true; - } if (this.schemas.has(iri)) { - return !!this.schemas.get(iri); - } - for (const item of this.tools.values()) { - if (item.schemaExist(iri)) { - return true; + const validator = this.schemas.get(iri); + return validator.isValid; + } else { + for (const item of this.tools.values()) { + if (item.schemaExist(iri)) { + return true; + } } + return false; } - return false; } /** @@ -366,15 +397,17 @@ export class PolicyValidator { * @param iri */ public unsupportedSchema(iri: string): boolean { - if (this.unsupportedSchemas.has(iri)) { - return true; - } - for (const item of this.tools.values()) { - if (item.unsupportedSchema(iri)) { - return true; + if (this.schemas.has(iri)) { + const validator = this.schemas.get(iri); + return !validator.isValid; + } else { + for (const item of this.tools.values()) { + if (item.unsupportedSchema(iri)) { + return true; + } } + return false; } - return false; } /** diff --git a/policy-service/src/policy-engine/block-validators/schema-validator.ts b/policy-service/src/policy-engine/block-validators/schema-validator.ts new file mode 100644 index 0000000000..4840082bac --- /dev/null +++ b/policy-service/src/policy-engine/block-validators/schema-validator.ts @@ -0,0 +1,199 @@ +/** + * Schema Validator + */ +import { ISchema, SchemaCategory } from '@guardian/interfaces'; +import { DatabaseServer, Schema } from '@guardian/common'; +import { IBlockErrors } from './interfaces/block-errors.interface'; + +/** + * Schema Validator + */ +export class SchemaValidator { + /** + * IRI + * @private + */ + public readonly iri: string; + /** + * Common errors + * @private + */ + private readonly errors: string[]; + /** + * Template schema + * @private + */ + private readonly isTemplate: boolean; + /** + * Base schema iri + * @private + */ + private readonly baseSchema: string; + /** + * Document + * @private + */ + private _document: ISchema; + /** + * Sub schemas + * @private + */ + private _subSchemas: string[]; + /** + * Status + * @private + */ + private _validating: boolean; + /** + * Status + * @private + */ + private _validated: boolean; + /** + * Is schema valid + * @private + */ + public get isValid(): boolean { + return this.errors.length === 0; + } + + constructor( + iri: string, + schema: Schema | string, + template: boolean, + ) { + this.iri = iri; + this.errors = []; + + this._subSchemas = []; + this.isTemplate = template; + if (typeof schema === 'string') { + this.baseSchema = schema; + this._document = null; + } else if (typeof schema === 'object') { + this._document = schema; + this.baseSchema = null; + } + this._validating = false; + this._validated = false; + } + + public async load(): Promise { + if (this.baseSchema) { + const db = new DatabaseServer(null); + this._document = await db.getSchemaByIRI(this.baseSchema); + } + if (this._document) { + const defs = this._document?.document?.$defs; + if (defs && Object.prototype.toString.call(defs) === '[object Object]') { + this._subSchemas = Object.keys(defs); + } + } + } + + /** + * Add Error + * @param error + */ + public addError(error: string): void { + this.errors.push(error); + } + + /** + * Validate + * @param schemas + */ + private _validate(schemas: Map): void { + if (this._validating) { + this.addError(`Circular dependency schemas '${this.iri}'`); + this._validating = false; + this._validated = true; + return; + } + + if (this.isTemplate && !this._document) { + this._validating = false; + this._validated = true; + return; + } + + if (!this._document) { + this.addError(`Schema '${this.iri}' does not exist`); + this._validating = false; + this._validated = true; + return; + } + + this._validating = true; + + if (Array.isArray(this._subSchemas)) { + for (const subSchemaIRI of this._subSchemas) { + if (schemas.has(subSchemaIRI)) { + const subSchema = schemas.get(subSchemaIRI); + if (!subSchema._validated) { + subSchema._validate(schemas); + } + if (!subSchema.isValid) { + this.addError(`Schema with id '${this.iri}' refers to invalid schema '${subSchemaIRI}'`); + this._validating = false; + this._validated = true; + return; + } + } else { + this.addError(`Schema with id '${this.iri}' refers to non-existing schema '${subSchemaIRI}'`); + this._validating = false; + this._validated = true; + return; + } + } + } + + this._validating = false; + this._validated = true; + } + + /** + * Validate + * @param validator + */ + public async validate(schemas: Map): Promise { + if (!this._validated) { + this._validate(schemas); + } + } + + /** + * Get schema document + */ + public getSchema(): ISchema { + return this._document; + } + + /** + * Get serialized errors + */ + public getSerializedErrors(): IBlockErrors { + return { + id: this.iri, + name: 'schema', + errors: this.errors.slice(), + isValid: !this.errors.length + }; + } + + public static fromSchema(schema: Schema): SchemaValidator { + if (schema.system || schema.category === SchemaCategory.SYSTEM || schema.readonly) { + return SchemaValidator.fromSystem(schema.iri); + } else { + return new SchemaValidator(schema.iri, schema, false); + } + } + + public static fromTemplate(variable: any): SchemaValidator { + return new SchemaValidator(variable.name, variable.baseSchema, true); + } + + public static fromSystem(name: string): SchemaValidator { + return new SchemaValidator(name, null, true); + } +} \ No newline at end of file diff --git a/policy-service/src/policy-engine/block-validators/tool-validator.ts b/policy-service/src/policy-engine/block-validators/tool-validator.ts index 99bed81899..9f95fcaf6e 100644 --- a/policy-service/src/policy-engine/block-validators/tool-validator.ts +++ b/policy-service/src/policy-engine/block-validators/tool-validator.ts @@ -2,6 +2,7 @@ import { DatabaseServer, PolicyTool } from '@guardian/common'; import { BlockValidator } from './block-validator'; import { IModulesErrors } from './interfaces/modules-errors.interface'; import { ISchema, ModuleStatus } from '@guardian/interfaces'; +import { SchemaValidator } from './schema-validator'; /** * Policy Validator @@ -27,21 +28,16 @@ export class ToolValidator { * @private */ private readonly tools: Map; - /** - * Common errors - * @private - */ - private readonly errors: string[]; /** * Schemas * @private */ - private readonly schemas: Map; + private readonly schemas: Map; /** - * Unsupported Schemas + * Common errors * @private */ - private readonly unsupportedSchemas: Set; + private readonly errors: string[]; /** * Tokens * @private @@ -83,10 +79,9 @@ export class ToolValidator { this.blocks = new Map(); this.tools = new Map(); this.tags = new Map(); + this.schemas = new Map(); this.errors = []; this.permissions = ['NO_ROLE', 'ANY_ROLE', 'OWNER']; - this.schemas = new Map(); - this.unsupportedSchemas = new Set(); this.tokens = []; this.topics = []; this.tokenTemplates = []; @@ -112,7 +107,6 @@ export class ToolValidator { } } await this.registerSchemas(); - this.checkSchemas(); return true; } } @@ -121,35 +115,13 @@ export class ToolValidator { * Register schemas */ private async registerSchemas(): Promise { - const db = new DatabaseServer(null); - for (const [key, value] of this.schemas) { - if (typeof value === 'string') { - const baseSchema = await db.getSchemaByIRI(value); - this.schemas.set(key, baseSchema); - } - } + this.schemas.set('#GeoJSON', SchemaValidator.fromSystem('#GeoJSON')); const schemas = await DatabaseServer.getSchemas({ topicId: this.topicId }); for (const schema of schemas) { - this.schemas.set(schema.iri, schema); + this.schemas.set(schema.iri, SchemaValidator.fromSchema(schema)); } - } - - /** - * Check schemas - */ - private checkSchemas(): void { - for (const schema of this.schemas.values()) { - const defs = schema?.document?.$defs; - if (defs && Object.prototype.toString.call(defs) === '[object Object]') { - for (const iri of Object.keys(defs)) { - if (!this.schemaExist(iri)) { - this.schemas.delete(schema.iri); - this.unsupportedSchemas.add(schema.iri); - this.checkSchemas(); - return; - } - } - } + for (const validator of this.schemas.values()) { + await validator.load(); } } @@ -210,7 +182,7 @@ export class ToolValidator { this.variables.push(variable); switch (variable.type) { case 'Schema': { - this.schemas.set(variable.name, variable.baseSchema); + this.schemas.set(variable.name, SchemaValidator.fromTemplate(variable)); break; } case 'Token': @@ -275,6 +247,10 @@ export class ToolValidator { * Validate */ public async validate(): Promise { + const allSchemas = this.getAllSchemas(new Map()); + for (const item of this.schemas.values()) { + await item.validate(allSchemas); + } for (const item of this.blocks.values()) { await item.validate(); } @@ -320,11 +296,37 @@ export class ToolValidator { public getSerializedErrors(): IModulesErrors { let valid = !this.errors.length; const blocksErrors = []; + const toolsErrors = []; + const commonErrors = this.errors.slice(); + /** + * Schema errors + */ + for (const item of this.schemas.values()) { + const result = item.getSerializedErrors(); + for (const error of result.errors) { + commonErrors.push(error); + } + valid = valid && result.isValid; + } + /** + * Tools errors + */ + for (const item of this.tools.values()) { + const result = item.getSerializedErrors(); + toolsErrors.push(result); + valid = valid && result.isValid; + } + /** + * Blocks errors + */ for (const item of this.blocks.values()) { - const result = item.getSerializedErrors() + const result = item.getSerializedErrors(); blocksErrors.push(result); valid = valid && result.isValid; } + /** + * Common tool errors + */ for (const item of this.errors) { blocksErrors.push({ id: this.uuid, @@ -333,6 +335,9 @@ export class ToolValidator { isValid: false }); } + /** + * Result error + */ if (!valid) { blocksErrors.push({ id: this.uuid, @@ -341,10 +346,11 @@ export class ToolValidator { isValid: false }); } - const commonErrors = this.errors.slice(); + return { errors: commonErrors, blocks: blocksErrors, + tools: toolsErrors, id: this.uuid, isValid: valid } @@ -387,15 +393,35 @@ export class ToolValidator { */ public getSchema(iri: string): ISchema { if (this.schemas.has(iri)) { - return this.schemas.get(iri); - } - for (const item of this.tools.values()) { - const schema = item.getSchema(iri); - if (schema) { - return schema; + const validator = this.schemas.get(iri); + if (validator.isValid) { + return validator.getSchema(); + } else { + return null; + } + } else { + for (const item of this.tools.values()) { + const schema = item.getSchema(iri); + if (schema) { + return schema; + } } + return null; } - return null; + } + + /** + * Get all schemas + * @param iri + */ + public getAllSchemas(map: Map): Map { + for (const [key, value] of this.schemas) { + map.set(key, value); + } + for (const tool of this.tools.values()) { + tool.getAllSchemas(map); + } + return map; } /** @@ -403,19 +429,17 @@ export class ToolValidator { * @param iri */ public schemaExist(iri: string): boolean { - if (iri === '#GeoJSON') { - return true; - } if (this.schemas.has(iri)) { - return !!this.schemas.get(iri); - } - for (const item of this.tools.values()) { - const exist = item.schemaExist(iri); - if (exist) { - return exist; + const validator = this.schemas.get(iri); + return validator.isValid; + } else { + for (const item of this.tools.values()) { + if (item.schemaExist(iri)) { + return true; + } } + return false; } - return false; } /** @@ -423,15 +447,17 @@ export class ToolValidator { * @param iri */ public unsupportedSchema(iri: string): boolean { - if (this.unsupportedSchemas.has(iri)) { - return true; - } - for (const item of this.tools.values()) { - if (item.unsupportedSchema(iri)) { - return true; + if (this.schemas.has(iri)) { + const validator = this.schemas.get(iri); + return !validator.isValid; + } else { + for (const item of this.tools.values()) { + if (item.unsupportedSchema(iri)) { + return true; + } } + return false; } - return false; } /** diff --git a/policy-service/src/policy-engine/blocks/action-block.ts b/policy-service/src/policy-engine/blocks/action-block.ts index c04a6b5e84..f833536b6d 100644 --- a/policy-service/src/policy-engine/blocks/action-block.ts +++ b/policy-service/src/policy-engine/blocks/action-block.ts @@ -100,8 +100,6 @@ export class InterfaceDocumentActionBlock { if (ref.options.type === 'download') { const sensorDid = document.document.credentialSubject[0].id; - const policy = await ref.databaseServer.getPolicy(ref.policyId); - const userDID = document.owner; const hederaAccount = await PolicyUtils.getHederaAccount(ref, userDID); const sensorKey = await PolicyUtils.getAccountKey(ref, userDID, KeyType.KEY, sensorDid); @@ -114,7 +112,7 @@ export class InterfaceDocumentActionBlock { fileName: ref.options.filename || `${sensorDid}.config.json`, body: { 'url': ref.options.targetUrl || process.env.MRV_ADDRESS, - 'topic': policy.topicId, + 'topic':ref.policyInstance?.topicId, 'hederaAccountId': hederaAccountId, 'hederaAccountKey': hederaAccountKey, 'installer': userDID, @@ -126,9 +124,9 @@ export class InterfaceDocumentActionBlock { 'type': schema.type, '@context': [schema.contextURL] }, - 'didDocument': await didDocument.getPrivateDidDocument(), + 'didDocument': didDocument.getPrivateDidDocument(), 'policyId': ref.policyId, - 'policyTag': policy.policyTag, + 'policyTag': ref.policyInstance?.policyTag, 'ref': sensorDid } } diff --git a/policy-service/src/policy-engine/blocks/calculate-block.ts b/policy-service/src/policy-engine/blocks/calculate-block.ts index 50d25cfa97..e762830495 100644 --- a/policy-service/src/policy-engine/blocks/calculate-block.ts +++ b/policy-service/src/policy-engine/blocks/calculate-block.ts @@ -220,7 +220,12 @@ export class CalculateContainerBlock { } const root = await PolicyUtils.getHederaAccount(ref, ref.policyOwner); - const newVC = await VCHelper.createVC(ref.policyOwner, root.hederaAccountKey, vcSubject); + const uuid = await ref.components.generateUUID(); + const newVC = await VCHelper.createVcDocument( + vcSubject, + { did: ref.policyOwner, key: root.hederaAccountKey }, + { uuid } + ); const item = PolicyUtils.createVC(ref, owner, newVC); item.type = outputSchema.iri; diff --git a/policy-service/src/policy-engine/blocks/custom-logic-block.ts b/policy-service/src/policy-engine/blocks/custom-logic-block.ts index 42aa940997..228a9baef5 100644 --- a/policy-service/src/policy-engine/blocks/custom-logic-block.ts +++ b/policy-service/src/policy-engine/blocks/custom-logic-block.ts @@ -12,7 +12,7 @@ import { MessageServer, KeyType } from '@guardian/common'; -import { ArtifactType, GenerateUUIDv4, SchemaHelper } from '@guardian/interfaces'; +import { ArtifactType, SchemaHelper } from '@guardian/interfaces'; import { IPolicyEvent, PolicyInputEventType, PolicyOutputEventType } from '@policy-engine/interfaces'; import { ChildrenType, ControlType, PropertyType } from '@policy-engine/interfaces/block-about'; import { IPolicyUser } from '@policy-engine/policy-user'; @@ -291,7 +291,12 @@ export class CustomLogicBlock { throw new Error(JSON.stringify(res.error)); } - const newVC = await VCHelper.createVC(root.did, root.hederaAccountKey, vcSubject); + const uuid = await ref.components.generateUUID(); + const newVC = await VCHelper.createVcDocument( + vcSubject, + { did: root.did, key: root.hederaAccountKey }, + { uuid } + ); const item = PolicyUtils.createVC(ref, owner, newVC); item.type = outputSchema.iri; @@ -331,7 +336,7 @@ export class CustomLogicBlock { const ref = PolicyComponentsUtils.GetBlockRef(this); try { if (idType === 'UUID') { - return GenerateUUIDv4(); + return await ref.components.generateUUID(); } if (idType === 'DID') { const topic = await PolicyUtils.getOrCreateTopic(ref, 'root', null, null); diff --git a/policy-service/src/policy-engine/blocks/impact-addon.ts b/policy-service/src/policy-engine/blocks/impact-addon.ts index 4253c6b673..9fd66ddee0 100644 --- a/policy-service/src/policy-engine/blocks/impact-addon.ts +++ b/policy-service/src/policy-engine/blocks/impact-addon.ts @@ -109,7 +109,12 @@ export class TokenOperationAddon { if (ref.options.description) { vcSubject.description = ref.options.description; } - const vc = await vcHelper.createVC(root.did, root.hederaAccountKey, vcSubject); + const uuid = await ref.components.generateUUID(); + const vc = await vcHelper.createVcDocument( + vcSubject, + { did: root.did, key: root.hederaAccountKey }, + { uuid } + ); return vc; } } diff --git a/policy-service/src/policy-engine/blocks/mint-block.ts b/policy-service/src/policy-engine/blocks/mint-block.ts index c3073b23d4..0d54c1ca82 100644 --- a/policy-service/src/policy-engine/blocks/mint-block.ts +++ b/policy-service/src/policy-engine/blocks/mint-block.ts @@ -1,6 +1,6 @@ import { ActionCallback, TokenBlock } from '@policy-engine/helpers/decorators'; import { BlockActionError } from '@policy-engine/errors'; -import { DocumentSignature, GenerateUUIDv4, SchemaEntity, SchemaHelper } from '@guardian/interfaces'; +import { DocumentSignature, SchemaEntity, SchemaHelper } from '@guardian/interfaces'; import { PolicyComponentsUtils } from '@policy-engine/policy-components-utils'; import { CatchErrors } from '@policy-engine/helpers/decorators/catch-errors'; import { @@ -170,7 +170,12 @@ export class MintBlock { tokenId: token.tokenId, amount: amount.toString() } - const mintVC = await vcHelper.createVC(root.did, root.hederaAccountKey, vcSubject); + const uuid = await ref.components.generateUUID(); + const mintVC = await vcHelper.createVcDocument( + vcSubject, + { did: root.did, key: root.hederaAccountKey }, + { uuid } + ); return mintVC; } @@ -207,7 +212,12 @@ export class MintBlock { if (additionalMessages) { vcSubject.relationships = additionalMessages.slice(); } - const vc = await vcHelper.createVC(root.did, root.hederaAccountKey, vcSubject); + const uuid = await ref.components.generateUUID(); + const vc = await vcHelper.createVcDocument( + vcSubject, + { did: root.did, key: root.hederaAccountKey }, + { uuid } + ); result.push(vc); } if (addons && addons.length) { @@ -228,11 +238,10 @@ export class MintBlock { */ private async createVP(root: IHederaAccount, uuid: string, vcs: VcDocument[]) { const vcHelper = new VcHelper(); - const vp = await vcHelper.createVP( - root.did, - root.hederaAccountKey, + const vp = await vcHelper.createVpDocument( vcs, - uuid + { did: root.did, key: root.hederaAccountKey }, + { uuid } ); return vp; } @@ -259,7 +268,7 @@ export class MintBlock { ): Promise<[IPolicyDocument, number]> { const ref = PolicyComponentsUtils.GetBlockRef(this); - const uuid = GenerateUUIDv4(); + const uuid: string = await ref.components.generateUUID(); const amount = PolicyUtils.aggregate(ref.options.rule, documents); if (Number.isNaN(amount) || !Number.isFinite(amount)) { throw new BlockActionError(`Invalid token value: ${amount}`, ref.blockType, ref.uuid); diff --git a/policy-service/src/policy-engine/blocks/multi-sign-block.ts b/policy-service/src/policy-engine/blocks/multi-sign-block.ts index 729b7d9e51..ee0a3964dc 100644 --- a/policy-service/src/policy-engine/blocks/multi-sign-block.ts +++ b/policy-service/src/policy-engine/blocks/multi-sign-block.ts @@ -6,7 +6,6 @@ import { ChildrenType, ControlType, PropertyType } from '@policy-engine/interfac import { AnyBlockType, IPolicyDocument, IPolicyEventState } from '@policy-engine/policy-engine.interface'; import { IPolicyUser } from '@policy-engine/policy-user'; import { BlockActionError } from '@policy-engine/errors'; -import { GenerateUUIDv4 } from '@guardian/interfaces'; import { PolicyRoles, VcDocument as VcDocumentCollection, @@ -155,11 +154,11 @@ export class MultiSignBlock { const groupContext = await PolicyUtils.getGroupContext(ref, user); const vcDocument = sourceDoc.document; const credentialSubject = vcDocument.credentialSubject[0]; - const newVC = await this.vcHelper.createVC( - root.did, - root.hederaAccountKey, + const uuid = await ref.components.generateUUID(); + const newVC = await this.vcHelper.createVcDocument( credentialSubject, - groupContext + { did: root.did, key: root.hederaAccountKey }, + { uuid, group: groupContext } ); await ref.databaseServer.setMultiSigDocument( @@ -216,11 +215,11 @@ export class MultiSignBlock { const documentOwnerAccount = await PolicyUtils.getHederaAccount(ref, docOwner.did); const vcs = data.map(e => VcDocument.fromJsonTree(e.document)); - const vp = await this.vcHelper.createVP( - policyOwnerAccount.did, - policyOwnerAccount.hederaAccountKey, + const uuid: string = await ref.components.generateUUID(); + const vp = await this.vcHelper.createVpDocument( vcs, - GenerateUUIDv4() + { did: policyOwnerAccount.did, key: policyOwnerAccount.hederaAccountKey }, + { uuid } ); const vpMessage = new VPMessage(MessageAction.CreateVP); diff --git a/policy-service/src/policy-engine/blocks/policy-roles.ts b/policy-service/src/policy-engine/blocks/policy-roles.ts index 8f0f2a5e46..fcc2ee090e 100644 --- a/policy-service/src/policy-engine/blocks/policy-roles.ts +++ b/policy-service/src/policy-engine/blocks/policy-roles.ts @@ -3,7 +3,7 @@ import { PolicyComponentsUtils } from '@policy-engine/policy-components-utils'; import { ChildrenType, ControlType } from '@policy-engine/interfaces/block-about'; import { PolicyInputEventType, PolicyOutputEventType } from '@policy-engine/interfaces'; import { IPolicyUser, PolicyUser } from '@policy-engine/policy-user'; -import { GenerateUUIDv4, GroupAccessType, GroupRelationshipType, SchemaEntity, SchemaHelper } from '@guardian/interfaces'; +import { GroupAccessType, GroupRelationshipType, SchemaEntity, SchemaHelper } from '@guardian/interfaces'; import { BlockActionError } from '@policy-engine/errors'; import { AnyBlockType } from '@policy-engine/policy-engine.interface'; import { DataTypes, PolicyUtils } from '@policy-engine/helpers/utils'; @@ -193,6 +193,7 @@ export class PolicyRolesBlock { user: IAuthUser, groupConfig: IGroupConfig ): Promise { + const uuid: string = await ref.components.generateUUID(); if (groupConfig.groupRelationshipType === GroupRelationshipType.Multiple) { if (groupConfig.groupAccessType === GroupAccessType.Global) { const result = await ref.databaseServer.getGlobalGroup(ref.policyId, groupConfig.name); @@ -218,7 +219,7 @@ export class PolicyRolesBlock { did: user.did, username: user.username, owner: ref.policyOwner, - uuid: GenerateUUIDv4(), + uuid, role: groupConfig.creator, groupName: groupConfig.name, groupLabel: groupConfig.label, @@ -234,7 +235,7 @@ export class PolicyRolesBlock { did: user.did, username: user.username, owner: user.did, - uuid: GenerateUUIDv4(), + uuid, role: groupConfig.creator, groupName: groupConfig.name, groupLabel: groupConfig.label, @@ -250,7 +251,7 @@ export class PolicyRolesBlock { did: user.did, username: user.username, owner: user.did, - uuid: GenerateUUIDv4(), + uuid, role: groupConfig.creator, groupName: groupConfig.name, groupLabel: groupConfig.label, @@ -315,11 +316,12 @@ export class PolicyRolesBlock { return null; } + const uuid: string = await ref.components.generateUUID(); const groupOwner = await PolicyUtils.getHederaAccount(ref, group.owner); const vcHelper = new VcHelper(); const vcSubject: any = { ...SchemaHelper.getContext(policySchema), - id: GenerateUUIDv4(), + id: uuid, role: group.role, userId: group.did, policyId: ref.policyId @@ -337,20 +339,24 @@ export class PolicyRolesBlock { vcSubject.groupLabel = group.groupLabel; } - const mintVC = await vcHelper.createVC(groupOwner.did, groupOwner.hederaAccountKey, vcSubject); + const userVC = await vcHelper.createVcDocument( + vcSubject, + { did: groupOwner.did, key: groupOwner.hederaAccountKey }, + { uuid } + ); const rootTopic = await PolicyUtils.getInstancePolicyTopic(ref); const messageServer = new MessageServer(groupOwner.hederaAccountId, groupOwner.hederaAccountKey, ref.dryRun); const vcMessage = new RoleMessage(MessageAction.CreateVC); - vcMessage.setDocument(mintVC); + vcMessage.setDocument(userVC); vcMessage.setRole(group); const vcMessageResult = await messageServer .setTopicObject(rootTopic) .sendMessage(vcMessage); - const vcDocument = PolicyUtils.createVC(ref, user, mintVC); + const vcDocument = PolicyUtils.createVC(ref, user, userVC); vcDocument.type = DataTypes.USER_ROLE; - vcDocument.schema = `#${mintVC.getSubjectType()}`; + vcDocument.schema = `#${userVC.getSubjectType()}`; vcDocument.messageId = vcMessageResult.getId(); vcDocument.topicId = vcMessageResult.getTopicId(); vcDocument.relationships = null; diff --git a/policy-service/src/policy-engine/blocks/reassigning.block.ts b/policy-service/src/policy-engine/blocks/reassigning.block.ts index 88e6fe6c8b..30ddb1a24f 100644 --- a/policy-service/src/policy-engine/blocks/reassigning.block.ts +++ b/policy-service/src/policy-engine/blocks/reassigning.block.ts @@ -85,12 +85,12 @@ export class ReassigningBlock { actor = user; } + const uuid = await ref.components.generateUUID(); const credentialSubject = vcDocument.credentialSubject[0]; - const vc: any = await this.vcHelper.createVC( - root.did, - root.hederaAccountKey, + const vc: any = await this.vcHelper.createVcDocument( credentialSubject, - groupContext + { did: root.did, key: root.hederaAccountKey }, + { uuid, group: groupContext } ); let item = PolicyUtils.createVC(ref, owner, vc); diff --git a/policy-service/src/policy-engine/blocks/report-block.ts b/policy-service/src/policy-engine/blocks/report-block.ts index e88d62a0f5..c895f46f95 100644 --- a/policy-service/src/policy-engine/blocks/report-block.ts +++ b/policy-service/src/policy-engine/blocks/report-block.ts @@ -123,11 +123,19 @@ export class ReportBlock { username: vp.owner, document: vp } + let amount = -1; + if (vp.amount) { + amount = vp.amount; + } else if (Array.isArray(vp.serials)) { + amount = vp.serials.length; + } + console.log(vp); report.mintDocument = { type: 'VC', tokenId: getVCField(mint, 'tokenId'), date: getVCField(mint, 'date'), - amount: getVCField(mint, 'amount'), + expected: getVCField(mint, 'amount'), + amount: String(amount), tag: vp.tag, issuer: vp.owner, username: vp.owner, diff --git a/policy-service/src/policy-engine/blocks/request-vc-document-block.ts b/policy-service/src/policy-engine/blocks/request-vc-document-block.ts index f0b9500ed6..e8b4078ad1 100644 --- a/policy-service/src/policy-engine/blocks/request-vc-document-block.ts +++ b/policy-service/src/policy-engine/blocks/request-vc-document-block.ts @@ -1,4 +1,4 @@ -import { CheckResult, GenerateUUIDv4, removeObjectProperties, Schema, SchemaHelper } from '@guardian/interfaces'; +import { CheckResult, removeObjectProperties, Schema, SchemaHelper } from '@guardian/interfaces'; import { PolicyUtils } from '@policy-engine/helpers/utils'; import { BlockActionError } from '@policy-engine/errors'; import { ActionCallback, StateField } from '@policy-engine/helpers/decorators'; @@ -6,7 +6,7 @@ import { AnyBlockType, IPolicyDocument, IPolicyEventState, IPolicyRequestBlock, import { IPolicyEvent, PolicyInputEventType, PolicyOutputEventType } from '@policy-engine/interfaces'; import { ChildrenType, ControlType } from '@policy-engine/interfaces/block-about'; import { EventBlock } from '@policy-engine/helpers/decorators/event-block'; -import { DIDDocument, DIDMessage, KeyType, MessageAction, MessageServer, VcDocument as VcDocumentCollection, VcHelper, } from '@guardian/common'; +import { DIDMessage, KeyType, MessageAction, MessageServer, VcDocument as VcDocumentCollection, VcHelper, } from '@guardian/common'; import { PolicyComponentsUtils } from '@policy-engine/policy-components-utils'; import { IPolicyUser } from '@policy-engine/policy-user'; import { ExternalDocuments, ExternalEvent, ExternalEventType } from '@policy-engine/interfaces/external-event'; @@ -221,11 +221,11 @@ export class RequestVcDocumentBlock { } const groupContext = await PolicyUtils.getGroupContext(ref, user); - const vc = await _vcHelper.createVC( - user.did, - hederaAccount.hederaAccountKey, + const uuid = await ref.components.generateUUID(); + const vc = await _vcHelper.createVcDocument( credentialSubject, - groupContext + { did: user.did, key: hederaAccount.hederaAccountKey }, + { uuid, group: groupContext } ); let item = PolicyUtils.createVC(ref, user, vc); const accounts = PolicyUtils.getHederaAccounts( @@ -291,16 +291,21 @@ export class RequestVcDocumentBlock { * @param userHederaAccount * @param userHederaKey */ - async generateId(idType: string, user: IPolicyUser, userHederaAccount: string, userHederaKey: string): Promise { + async generateId( + idType: string, + user: IPolicyUser, + userHederaAccount: string, + userHederaKey: string + ): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); try { if (idType === 'UUID') { - return GenerateUUIDv4(); + return await ref.components.generateUUID(); } if (idType === 'DID') { const topic = await PolicyUtils.getOrCreateTopic(ref, 'root', null, null); - const didObject = await DIDDocument.create(null, topic.topicId); + const didObject = await ref.components.generateDID(topic.topicId); const did = didObject.getDid(); const key = didObject.getPrivateKeyString(); diff --git a/policy-service/src/policy-engine/blocks/retirement-block.ts b/policy-service/src/policy-engine/blocks/retirement-block.ts index 44c2ac0a7b..49f6e6b6cf 100644 --- a/policy-service/src/policy-engine/blocks/retirement-block.ts +++ b/policy-service/src/policy-engine/blocks/retirement-block.ts @@ -1,6 +1,6 @@ import { ActionCallback, BasicBlock } from '@policy-engine/helpers/decorators'; import { BlockActionError } from '@policy-engine/errors'; -import { DocumentSignature, GenerateUUIDv4, IRootConfig, SchemaEntity, SchemaHelper } from '@guardian/interfaces'; +import { DocumentSignature, IRootConfig, SchemaEntity, SchemaHelper } from '@guardian/interfaces'; import { PolicyComponentsUtils } from '@policy-engine/policy-components-utils'; import { CatchErrors } from '@policy-engine/helpers/decorators/catch-errors'; import { Token as TokenCollection, VcHelper, VcDocumentDefinition as VcDocument, MessageServer, VCMessage, MessageAction, VPMessage } from '@guardian/common'; @@ -58,7 +58,11 @@ export class RetirementBlock { tokenId: token.tokenId, amount: amount.toString() } - const wipeVC = await vcHelper.createVC(root.did, root.hederaAccountKey, vcSubject); + const uuid = await ref.components.generateUUID(); + const wipeVC = await vcHelper.createVcDocument( + vcSubject, + { did: root.did, key: root.hederaAccountKey }, + { uuid }); return wipeVC; } @@ -71,11 +75,10 @@ export class RetirementBlock { */ private async createVP(root: IRootConfig, uuid: string, vcs: VcDocument[]) { const vcHelper = new VcHelper(); - const vp = await vcHelper.createVP( - root.did, - root.hederaAccountKey, + const vp = await vcHelper.createVpDocument( vcs, - uuid + { did: root.did, key: root.hederaAccountKey }, + { uuid } ); return vp; } @@ -101,7 +104,7 @@ export class RetirementBlock { ): Promise<[IPolicyDocument, number]> { const ref = PolicyComponentsUtils.GetBlockRef(this); - const uuid = GenerateUUIDv4(); + const uuid: string = await ref.components.generateUUID(); const amount = PolicyUtils.aggregate(ref.options.rule, documents); const [tokenValue, tokenAmount] = PolicyUtils.tokenAmount(token, amount); const wipeVC = await this.createWipeVC(root, token, tokenAmount, ref); diff --git a/policy-service/src/policy-engine/blocks/split-block.ts b/policy-service/src/policy-engine/blocks/split-block.ts index a84f695375..e2e92e99e9 100644 --- a/policy-service/src/policy-engine/blocks/split-block.ts +++ b/policy-service/src/policy-engine/blocks/split-block.ts @@ -130,7 +130,12 @@ export class SplitBlock { maxChunks }); } - vc = await this.vcHelper.issueVC(root.did, root.hederaAccountKey, vc); + const uuid = await ref.components.generateUUID(); + vc = await this.vcHelper.issueVcDocument( + vc, + { did: root.did, key: root.hederaAccountKey }, + { uuid } + ); clone.document = vc.toJsonTree(); clone.hash = vc.toCredentialHash(); clone = PolicyUtils.setDocumentRef(clone, document) as any; diff --git a/policy-service/src/policy-engine/blocks/tag-manager.ts b/policy-service/src/policy-engine/blocks/tag-manager.ts index c2e0b515c4..ddda268680 100644 --- a/policy-service/src/policy-engine/blocks/tag-manager.ts +++ b/policy-service/src/policy-engine/blocks/tag-manager.ts @@ -4,7 +4,7 @@ import { ChildrenType, ControlType } from '@policy-engine/interfaces/block-about import { AnyBlockType, IPolicyDocument } from '@policy-engine/policy-engine.interface'; import { IPolicyUser } from '@policy-engine/policy-user'; import { BlockActionError } from '@policy-engine/errors'; -import { GenerateUUIDv4, SchemaCategory, SchemaHelper, SchemaStatus, TagType } from '@guardian/interfaces'; +import { SchemaCategory, SchemaHelper, SchemaStatus, TagType } from '@guardian/interfaces'; import { Tag, MessageAction, @@ -158,10 +158,11 @@ export class TagsManagerBlock { if (ref.dryRun) { vcHelper.addDryRunContext(credentialSubject); } - const vcObject = await vcHelper.createVC( - user.did, - hederaAccount.hederaAccountKey, - credentialSubject + const uuid = await ref.components.generateUUID(); + const vcObject = await vcHelper.createVcDocument( + credentialSubject, + { did: user.did, key: hederaAccount.hederaAccountKey }, + { uuid } ); tag.document = vcObject.getDocument(); } else { @@ -170,7 +171,8 @@ export class TagsManagerBlock { const target = await this.getTarget(TagType.PolicyDocument, tag.localTarget || tag.target); if (target) { - tag.uuid = tag.uuid || GenerateUUIDv4(); + const uuid: string = await ref.components.generateUUID(); + tag.uuid = tag.uuid || uuid; tag.operation = 'Create'; tag.entity = TagType.PolicyDocument; tag.target = null; diff --git a/policy-service/src/policy-engine/helpers/components-service.ts b/policy-service/src/policy-engine/helpers/components-service.ts index 1acdf01a1e..637d059161 100644 --- a/policy-service/src/policy-engine/helpers/components-service.ts +++ b/policy-service/src/policy-engine/helpers/components-service.ts @@ -1,23 +1,56 @@ import { + DIDDocument, DatabaseServer, Policy as PolicyCollection, PolicyTool as PolicyToolCollection, Schema as SchemaCollection } from '@guardian/common'; -import { PolicyType, SchemaEntity } from '@guardian/interfaces'; +import { GenerateUUIDv4, PolicyType, SchemaEntity } from '@guardian/interfaces'; import { IPolicyBlock } from '@policy-engine/policy-engine.interface'; +import { IPolicyUser } from '@policy-engine/policy-user'; +import { Recording, Running } from '@policy-engine/record'; export class ComponentsService { + /** + * Policy topic ID + */ public readonly topicId: string; + /** + * Policy ID + */ public readonly policyId: string; + /** + * Policy Owner + */ + public readonly owner: string; + /** + * Policy ID + */ public readonly dryRunId: string; - + /** + * Token templates + */ private policyTokens: any[]; + /** + * Group templates + */ private policyGroups: any[]; + /** + * Roles + */ private policyRoles: string[]; + /** + * Schemas + */ private readonly schemasByID: Map; + /** + * Schemas + */ private readonly schemasByType: Map; - + /** + * Root block + */ + private root: IPolicyBlock; /** * Database instance * @public @@ -25,6 +58,7 @@ export class ComponentsService { public readonly databaseServer: DatabaseServer; constructor(policy: PolicyCollection, policyId: string) { + this.owner = policy.owner; this.policyId = policyId; this.topicId = policy.topicId; if (policy && policy.status === PolicyType.DRY_RUN) { @@ -38,6 +72,8 @@ export class ComponentsService { this.policyRoles = []; this.schemasByID = new Map(); this.schemasByType = new Map(); + this._recordingController = null; + this._runningController = null; } /** @@ -62,7 +98,7 @@ export class ComponentsService { */ public async loadArtifactByID(uuid: string): Promise { const artifactFile = await DatabaseServer.getArtifactFileByUUID(uuid); - if(artifactFile) { + if (artifactFile) { return artifactFile.toString(); } return null; @@ -136,10 +172,189 @@ export class ComponentsService { } /** - * Register Instance + * Register root * @param name */ - public async registerInstance(blockInstance: IPolicyBlock): Promise { - return; + public async registerRoot(blockInstance: IPolicyBlock): Promise { + this.root = blockInstance; + } + + /** + * Select Policy Group + * @param policy + * @param user + * @param uuid + */ + public async selectGroup( + user: IPolicyUser, + uuid: string + ): Promise { + const templates = this.getGroupTemplates(); + if (templates.length === 0) { + return false; + } + await this.databaseServer.setActiveGroup( + this.policyId, + user.did, + uuid + ); + return true; + } + + /** + * Generate new UUID + */ + public async generateUUID(): Promise { + if (this._runningController) { + return await this._runningController.nextUUID(); + } + const uuid = GenerateUUIDv4(); + if (this._recordingController) { + await this._recordingController.generateUUID(uuid); + } + return uuid; + } + + /** + * Generate new DID + */ + public async generateDID(topicId: string): Promise { + if (this._runningController) { + return await this._runningController.nextDID(topicId); + } + const didDocument = await DIDDocument.create(null, topicId); + if (this._recordingController) { + await this._recordingController.generateDidDocument(didDocument); + } + return didDocument; + } + + /** + * Recording Controller + */ + private _recordingController: Recording; + + /** + * Running Controller + */ + private _runningController: Running; + + public get recordingController(): Recording | null { + return this._recordingController; + } + + public get runningController(): Running | null { + return this._runningController; + } + + public get runAndRecordController(): Recording | Running | null { + return this._recordingController || this._runningController; + } + + /** + * Start Recording + */ + public async startRecording(): Promise { + if (this._runningController) { + return false; + } + if (!this._recordingController) { + this._recordingController = new Recording(this.policyId, this.owner); + } + return await this._recordingController.start(); + } + + /** + * Stop Recording + */ + public async stopRecording(): Promise { + if (this._recordingController) { + const old = this._recordingController; + this._recordingController = null; + return await old.stop(); + } + if (this._runningController) { + const old = this._runningController; + this._runningController = null; + return old.finished(); + } + return false; + } + + /** + * Stop Running + */ + public async stopRunning(): Promise { + if (this._recordingController) { + const old = this._recordingController; + this._recordingController = null; + return await old.stop(); + } + if (this._runningController) { + const old = this._runningController; + this._runningController = null; + return old.finished(); + } + return false; + } + + /** + * Run Record + * @param actions + * @param results + * @param options + */ + public async runRecord( + actions: any[], + results: any[], + options: any + ): Promise { + if (this._recordingController) { + return false; + } + if (!this._runningController) { + this._runningController = new Running( + this.root, + this.policyId, + this.owner, + actions, + results, + options + ); + } + return this._runningController.start(); + } + + /** + * Skip running delay + * @param options + */ + public async fastForward(options: any): Promise { + if (this._runningController) { + return this._runningController.fastForward(options); + } + return false; + } + + /** + * Retry running step + * @param options + */ + public async retryStep(options: any): Promise { + if (this._runningController) { + return this._runningController.retryStep(); + } + return null; + } + + /** + * Skip running step + * @param options + */ + public async skipStep(options: any): Promise { + if (this._runningController) { + return this._runningController.skipStep(); + } + return null; } } \ No newline at end of file diff --git a/policy-service/src/policy-engine/helpers/decorators/basic-block.ts b/policy-service/src/policy-engine/helpers/decorators/basic-block.ts index 76ee2498d8..e3b5b7c7d9 100644 --- a/policy-service/src/policy-engine/helpers/decorators/basic-block.ts +++ b/policy-service/src/policy-engine/helpers/decorators/basic-block.ts @@ -6,7 +6,7 @@ import { AnyBlockType, IPolicyBlock, IPolicyDocument, ISerializedBlock, } from ' import { PolicyComponentsUtils } from '../../policy-components-utils'; import { IPolicyEvent, PolicyLink } from '@policy-engine/interfaces/policy-event'; import { PolicyInputEventType, PolicyOutputEventType } from '@policy-engine/interfaces/policy-event-type'; -import { Logger, DatabaseServer } from '@guardian/common'; +import { Logger, DatabaseServer, Policy } from '@guardian/common'; import deepEqual from 'deep-equal'; import { IPolicyUser, PolicyUser } from '@policy-engine/policy-user'; import { ComponentsService } from '../components-service'; @@ -104,7 +104,7 @@ export function BasicBlock(options: Partial) { /** * Policy instance */ - public policyInstance: any; + public policyInstance: Policy; /** * Topic id */ @@ -293,7 +293,13 @@ export function BasicBlock(options: Partial) { * @param link */ public addSourceLink(link: PolicyLink): void { - this.sourceLinks.push(link) + if ( + !this.sourceLinks.some((sourceLink) => + sourceLink.equals(link) + ) + ) { + this.sourceLinks.push(link); + } } /** @@ -301,7 +307,13 @@ export function BasicBlock(options: Partial) { * @param link */ public addTargetLink(link: PolicyLink): void { - this.targetLinks.push(link) + if ( + !this.targetLinks.some((targetLink) => + targetLink.equals(link) + ) + ) { + this.targetLinks.push(link); + } } /** @@ -468,7 +480,7 @@ export function BasicBlock(options: Partial) { * @param policyId * @param policy */ - public setPolicyInstance(policyId: string, policy: any) { + public setPolicyInstance(policyId: string, policy: Policy) { this.policyInstance = policy; this.policyId = policyId; if (this.policyInstance && this.policyInstance.status === PolicyType.DRY_RUN) { diff --git a/policy-service/src/policy-engine/helpers/decorators/data-source-block.ts b/policy-service/src/policy-engine/helpers/decorators/data-source-block.ts index eec7214cb2..1402187644 100644 --- a/policy-service/src/policy-engine/helpers/decorators/data-source-block.ts +++ b/policy-service/src/policy-engine/helpers/decorators/data-source-block.ts @@ -87,7 +87,7 @@ export function DataSourceBlock(options: Partial) { * @param countResult * @protected */ - protected async getGlobalSourcesFilters(user: IPolicyUser) { + protected async getGlobalSourcesFilters(user: IPolicyUser) { const dynFilters = []; for (const child of this.children) { if (child.blockClassName === 'DataSourceAddon') { @@ -124,7 +124,7 @@ export function DataSourceBlock(options: Partial) { } filters.push(blockFilter); } - return {filters, dataType: sourceAddons[0].options.dataType}; + return { filters, dataType: sourceAddons[0].options.dataType }; } /** @@ -181,7 +181,7 @@ export function DataSourceBlock(options: Partial) { } skip = Math.max(start - previousCount, 0); - limit = paginationData.itemsPerPage - Math.min((previousCount - start), 0); + limit = paginationData.itemsPerPage - Math.min((previousCount - start), 0); const childData = await currentSource.getFromSource(user, globalFilters, false, { offset: skip, diff --git a/policy-service/src/policy-engine/interfaces/policy-event.ts b/policy-service/src/policy-engine/interfaces/policy-event.ts index 3ce9a82185..fcb1fe7833 100644 --- a/policy-service/src/policy-engine/interfaces/policy-event.ts +++ b/policy-service/src/policy-engine/interfaces/policy-event.ts @@ -195,4 +195,17 @@ export class PolicyLink { public destroy(): void { return; } + + /** + * Equals + */ + public equals(link: PolicyLink): boolean { + return ( + this.target?.tag === link?.target?.tag && + this.source?.tag === link?.source?.tag && + this.inputType === link?.inputType && + this.outputType === link?.outputType && + this.actor === link?.actor + ); + } } diff --git a/policy-service/src/policy-engine/multi-policy-service/mint-service.ts b/policy-service/src/policy-engine/multi-policy-service/mint-service.ts index e29624451b..1162cbe1e1 100644 --- a/policy-service/src/policy-engine/multi-policy-service/mint-service.ts +++ b/policy-service/src/policy-engine/multi-policy-service/mint-service.ts @@ -1,29 +1,6 @@ import { AnyBlockType } from '@policy-engine/policy-engine.interface'; -import { - ContractParamType, - ExternalMessageEvents, - GenerateUUIDv4, - IRootConfig, - NotificationAction, - WorkerTaskType -} from '@guardian/interfaces'; -import { - ExternalEventChannel, - Logger, - Token, - MultiPolicy, - KeyType, - Wallet, - DatabaseServer, - MessageAction, - MessageServer, - SynchronizationMessage, - TopicConfig, - VcDocumentDefinition as VcDocument, - Workers, - NotificationHelper, - Users, -} from '@guardian/common'; +import { ContractParamType, ExternalMessageEvents, GenerateUUIDv4, IRootConfig, NotificationAction, WorkerTaskType } from '@guardian/interfaces'; +import { DatabaseServer, ExternalEventChannel, KeyType, Logger, MessageAction, MessageServer, MultiPolicy, NotificationHelper, SynchronizationMessage, Token, TopicConfig, Users, VcDocumentDefinition as VcDocument, Wallet, Workers, } from '@guardian/common'; import { AccountId, PrivateKey, TokenId } from '@hashgraph/sdk'; import { PolicyUtils } from '@policy-engine/helpers/utils'; import { IPolicyUser } from '@policy-engine/policy-user'; @@ -134,8 +111,13 @@ export class MintService { 1, 10 ); }; - const mintAndTransferNFT = (metaData: string[]) => - mintNFT(metaData).then(transferNFT); + const mintAndTransferNFT = async (metaData: string[]) => { + try { + return await transferNFT(await mintNFT(metaData)); + } catch (e) { + return null; + } + } const mintId = Date.now(); MintService.log(`Mint(${mintId}): Start (Count: ${tokenValue})`, ref); @@ -166,7 +148,7 @@ export class MintService { try { const results = await Promise.all(dataChunk.map(mintAndTransferNFT)); for (const serials of results) { - if (serials) { + if (Array.isArray(serials)) { for (const n of serials) { result.push(n); } @@ -410,7 +392,7 @@ export class MintService { multipleConfig ? `Multi mint` : `Mint completed`, multipleConfig ? `Request to mint is submitted` - : `All ${token.tokenName} tokens have been minted and transferred`, + : `${token.tokenName} tokens have been minted and transferred`, NotificationAction.POLICY_VIEW, ref.policyId ); @@ -491,7 +473,7 @@ export class MintService { notifier?.success( `Mint completed`, - `All ${token.tokenName} tokens have been minted and transferred` + `${token.tokenName} tokens have been minted and transferred` ); new ExternalEventChannel().publishMessage( diff --git a/policy-service/src/policy-engine/policy-components-utils.ts b/policy-service/src/policy-engine/policy-components-utils.ts index 7bb8598274..998a730a5d 100644 --- a/policy-service/src/policy-engine/policy-components-utils.ts +++ b/policy-service/src/policy-engine/policy-components-utils.ts @@ -1,6 +1,23 @@ -import { EventActor, EventCallback, PolicyBlockFullArgumentList, PolicyBlockMap, PolicyInputEventType, PolicyLink, PolicyOutputEventType, PolicyTagMap } from '@policy-engine/interfaces'; +import { + EventActor, + EventCallback, + PolicyBlockFullArgumentList, + PolicyBlockMap, + PolicyInputEventType, + PolicyLink, + PolicyOutputEventType, + PolicyTagMap +} from '@policy-engine/interfaces'; import { BlockType, GenerateUUIDv4, ModuleStatus, PolicyEvents, PolicyType } from '@guardian/interfaces'; -import { AnyBlockType, IPolicyBlock, IPolicyContainerBlock, IPolicyInstance, IPolicyInterfaceBlock, ISerializedBlock, ISerializedBlockExtend } from './policy-engine.interface'; +import { + AnyBlockType, + IPolicyBlock, + IPolicyContainerBlock, + IPolicyInstance, + IPolicyInterfaceBlock, + ISerializedBlock, + ISerializedBlockExtend +} from './policy-engine.interface'; import { DatabaseServer, Policy, PolicyTool } from '@guardian/common'; import { STATE_KEY } from '@policy-engine/helpers/constants'; import { GetBlockByType } from '@policy-engine/blocks/get-block-by-type'; @@ -711,14 +728,14 @@ export class PolicyComponentsUtils { */ public static async RegisterPolicyInstance( policyId: string, - policy: Policy + policy: Policy, + components: ComponentsService ) { const dryRun = policy.status === PolicyType.DRY_RUN ? policyId : null; - const databaseServer = new DatabaseServer(dryRun); const policyInstance: IPolicyInstance = { policyId, dryRun, - databaseServer, + components, isMultipleGroup: !!policy.policyGroups?.length, instanceTopicId: policy.instanceTopicId, synchronizationTopicId: policy.synchronizationTopicId, @@ -979,7 +996,7 @@ export class PolicyComponentsUtils { policy: IPolicyInstance | IPolicyInterfaceBlock, user: IPolicyUser ): Promise { - return await policy.databaseServer.getGroupsByUser( + return await policy.components.databaseServer.getGroupsByUser( policy.policyId, user.did, { @@ -988,24 +1005,6 @@ export class PolicyComponentsUtils { ); } - /** - * Select Policy Group - * @param policy - * @param user - * @param uuid - */ - public static async SelectGroup( - policy: IPolicyInstance | IPolicyInterfaceBlock, - user: IPolicyUser, - uuid: string - ): Promise { - await policy.databaseServer.setActiveGroup( - policy.policyId, - user.did, - uuid - ); - } - /** * Get Policy Full Info * @param policy @@ -1189,4 +1188,15 @@ export class PolicyComponentsUtils { ); } } + + /** + * Get policy components + * @param policyId + */ + public static GetPolicyComponents(policyId: string): ComponentsService | null { + if (PolicyComponentsUtils.PolicyById.has(policyId)) { + return PolicyComponentsUtils.PolicyById.get(policyId).components; + } + return null; + } } diff --git a/policy-service/src/policy-engine/policy-engine.interface.ts b/policy-service/src/policy-engine/policy-engine.interface.ts index 15c45ed390..a7e17786ec 100644 --- a/policy-service/src/policy-engine/policy-engine.interface.ts +++ b/policy-service/src/policy-engine/policy-engine.interface.ts @@ -1,7 +1,7 @@ import { PolicyRole } from '@guardian/interfaces'; import { BlockCacheType, PolicyOutputEventType } from '@policy-engine/interfaces'; import { EventConfig, IPolicyEvent } from './interfaces'; -import { DatabaseServer } from '@guardian/common'; +import { DatabaseServer, Policy } from '@guardian/common'; import { IPolicyUser } from './policy-user'; import { IHederaAccount } from './helpers/utils'; import { ComponentsService } from './helpers/components-service'; @@ -104,7 +104,7 @@ export interface IPolicyBlock { /** * Policy instance */ - policyInstance: any; + policyInstance: Policy; /** * Topic id */ @@ -165,7 +165,7 @@ export interface IPolicyBlock { * @param policyId * @param policy */ - setPolicyInstance(policyId: string, policy: any): void; + setPolicyInstance(policyId: string, policy: Policy): void; /** * Set topic id @@ -912,11 +912,6 @@ export interface IPolicyInstance { */ readonly dryRun: string; - /** - * Database Server - */ - readonly databaseServer: DatabaseServer; - /** * Is Multiple Group */ @@ -936,4 +931,9 @@ export interface IPolicyInstance { * Policy Owner */ readonly owner: string; + + /** + * Policy Owner + */ + readonly components: ComponentsService } diff --git a/policy-service/src/policy-engine/record-utils.ts b/policy-service/src/policy-engine/record-utils.ts new file mode 100644 index 0000000000..5e4d530df5 --- /dev/null +++ b/policy-service/src/policy-engine/record-utils.ts @@ -0,0 +1,286 @@ +import { Recording } from './record/recording'; +import { Running } from './record'; +import { PolicyComponentsUtils } from './policy-components-utils'; +import { IPolicyUser } from './policy-user'; +import { AnyBlockType } from './policy-engine.interface'; + +/** + * Record utils + */ +export class RecordUtils { + /** + * Get record controller + * @param policyId + * @public + * @static + */ + public static GetRecordingController(policyId: string): Recording | null { + const components = PolicyComponentsUtils.GetPolicyComponents(policyId); + if (components) { + return components.recordingController; + } + return null; + } + + /** + * Get record controller + * @param policyId + * @public + * @static + */ + public static GetRunAndRecordController(policyId: string): Recording | Running | null { + const components = PolicyComponentsUtils.GetPolicyComponents(policyId); + if (components) { + return components + .runAndRecordController; + } + return null; + } + + /** + * Start recording + * @param policyId + * @public + * @static + */ + public static async StartRecording(policyId: string): Promise { + const components = PolicyComponentsUtils.GetPolicyComponents(policyId); + if (!components) { + return false; + } + return await components.startRecording(); + } + + /** + * Stop recording + * @param policyId + * @public + * @static + */ + public static async StopRecording(policyId: string): Promise { + const components = PolicyComponentsUtils.GetPolicyComponents(policyId); + if (!components) { + return false; + } + return await components.stopRecording(); + } + + /** + * Stop running + * @param policyId + * @public + * @static + */ + public static async StopRunning(policyId: string): Promise { + const components = PolicyComponentsUtils.GetPolicyComponents(policyId); + if (!components) { + return false; + } + return await components.stopRunning(); + } + + /** + * Fast Forward + * @param policyId + * @param options + * @public + * @static + */ + public static async FastForward(policyId: string, options: any): Promise { + const components = PolicyComponentsUtils.GetPolicyComponents(policyId); + if (!components) { + return false; + } + return await components.fastForward(options); + } + + /** + * Retry Step + * @param policyId + * @param options + * @public + * @static + */ + public static async RetryStep(policyId: string, options: any): Promise { + const components = PolicyComponentsUtils.GetPolicyComponents(policyId); + if (!components) { + return false; + } + return await components.retryStep(options); + } + + /** + * Skip Step + * @param policyId + * @param options + * @public + * @static + */ + public static async SkipStep(policyId: string, options: any): Promise { + const components = PolicyComponentsUtils.GetPolicyComponents(policyId); + if (!components) { + return false; + } + return await components.skipStep(options); + } + + /** + * Get recording or running status + * @param policyId + * @public + * @static + */ + public static GetRecordStatus(policyId: string): any { + const record = RecordUtils.GetRunAndRecordController(policyId); + if (record) { + return record.getStatus(); + } else { + return { policyId }; + } + } + + /** + * Get recorded actions + * @param policyId + * @public + * @static + */ + public static async GetRecordedActions(policyId: string): Promise { + const record = RecordUtils.GetRunAndRecordController(policyId); + if (record) { + return await record.getActions(); + } else { + return null; + } + } + + /** + * Get recorded actions + * @param policyId + * @public + * @static + */ + public static async GetRecordResults(policyId: string): Promise { + const record = RecordUtils.GetRunAndRecordController(policyId); + if (record) { + return await record.getResults(); + } else { + return null; + } + } + + /** + * Record policy + * @param policyId + * @param actions + * @param results + * @param options + * @public + * @static + */ + public static async RunRecord( + policyId: string, + actions: any[], + results: any[], + options: any + ): Promise { + const components = PolicyComponentsUtils.GetPolicyComponents(policyId); + if (!components) { + return false; + } + return await components.runRecord(actions, results, options); + } + + /** + * Record SelectGroup + * @param policyId + * @param user + * @param uuid + * @public + * @static + */ + public static async RecordSelectGroup( + policyId: string, + user: IPolicyUser, + uuid: string + ): Promise { + const record = RecordUtils.GetRecordingController(policyId); + if (record) { + await record.selectGroup(user, uuid); + } + } + + /** + * Record SetBlockData + * @param policyId + * @param user + * @param block + * @param data + * @public + * @static + */ + public static async RecordSetBlockData( + policyId: string, + user: IPolicyUser, + block: AnyBlockType, + data: any + ): Promise { + const record = RecordUtils.GetRecordingController(policyId); + if (record) { + await record.setBlockData(user, block, data); + } + } + + /** + * Record ExternalData + * @param policyId + * @param data + * @public + * @static + */ + public static async RecordExternalData( + policyId: string, + data: any + ): Promise { + const record = RecordUtils.GetRecordingController(policyId); + if (record) { + await record.externalData(data); + } + } + + /** + * Record CreateUser + * @param policyId + * @param did + * @param data + * @public + * @static + */ + public static async RecordCreateUser( + policyId: string, + did: string, + data: any + ): Promise { + const record = RecordUtils.GetRecordingController(policyId); + if (record) { + await record.createUser(did, data); + } + } + + /** + * Record SetUser + * @param policyId + * @param did + * @public + * @static + */ + public static async RecordSetUser( + policyId: string, + did: string, + ): Promise { + const record = RecordUtils.GetRecordingController(policyId); + if (record) { + await record.setUser(did); + } + } +} \ No newline at end of file diff --git a/policy-service/src/policy-engine/record/action.type.ts b/policy-service/src/policy-engine/record/action.type.ts new file mode 100644 index 0000000000..20f6918240 --- /dev/null +++ b/policy-service/src/policy-engine/record/action.type.ts @@ -0,0 +1,12 @@ +/** + * Record action type + */ +export enum RecordAction { + SelectGroup = 'SELECT_POLICY_GROUP', + SetBlockData = 'SET_BLOCK_DATA', + SetExternalData = 'SET_EXTERNAL_DATA', + CreateUser = 'CREATE_USER', + SetUser = 'SET_USER', + GenerateUUID = 'GENERATE_UUID', + GenerateDID = 'GENERATE_DID' +} \ No newline at end of file diff --git a/policy-service/src/policy-engine/record/index.ts b/policy-service/src/policy-engine/record/index.ts new file mode 100644 index 0000000000..3b9adc1835 --- /dev/null +++ b/policy-service/src/policy-engine/record/index.ts @@ -0,0 +1,6 @@ +export * from './recording'; +export * from './running'; +export * from './status.type'; +export * from './action.type'; +export * from './method.type'; +export * from './record-item'; \ No newline at end of file diff --git a/policy-service/src/policy-engine/record/method.type.ts b/policy-service/src/policy-engine/record/method.type.ts new file mode 100644 index 0000000000..457615a7c8 --- /dev/null +++ b/policy-service/src/policy-engine/record/method.type.ts @@ -0,0 +1,9 @@ +/** + * Record method + */ +export enum RecordMethod { + Start = 'START', + Stop = 'STOP', + Action = 'ACTION', + Generate = 'GENERATE' +} \ No newline at end of file diff --git a/policy-service/src/policy-engine/record/record-item.ts b/policy-service/src/policy-engine/record/record-item.ts new file mode 100644 index 0000000000..2c3dbfefe8 --- /dev/null +++ b/policy-service/src/policy-engine/record/record-item.ts @@ -0,0 +1,40 @@ +import { RecordAction } from './action.type'; +import { RecordMethod } from './method.type'; + +/** + * Record item + */ +export interface RecordItem { + /** + * Record uuid + */ + uuid?: string, + /** + * Policy ID + */ + policyId?: string, + /** + * Method type + */ + method?: RecordMethod, + /** + * Action type + */ + action?: RecordAction, + /** + * Recorded time + */ + time?: number, + /** + * User DID + */ + user?: string, + /** + * Block tag + */ + target?: string, + /** + * Document + */ + document?: any, +} \ No newline at end of file diff --git a/policy-service/src/policy-engine/record/recording.ts b/policy-service/src/policy-engine/record/recording.ts new file mode 100644 index 0000000000..e51af58118 --- /dev/null +++ b/policy-service/src/policy-engine/record/recording.ts @@ -0,0 +1,260 @@ +import { DIDDocument, DatabaseServer } from '@guardian/common'; +import { GenerateUUIDv4, PolicyEvents } from '@guardian/interfaces'; +import { BlockTreeGenerator } from '@policy-engine/block-tree-generator'; +import { AnyBlockType } from '@policy-engine/policy-engine.interface'; +import { IPolicyUser } from '@policy-engine/policy-user'; +import { RecordingStatus } from './status.type'; +import { RecordAction } from './action.type'; +import { RecordMethod } from './method.type'; +import { RecordItem } from './record-item'; + +/** + * Recording controller + */ +export class Recording { + /** + * Controller type + */ + public readonly type: string = 'Recording'; + /** + * Controller ID + */ + public readonly uuid: string; + /** + * Policy ID + */ + public readonly policyId: string; + /** + * Policy owner + */ + public readonly owner: string; + /** + * Block messenger + */ + private readonly tree: BlockTreeGenerator; + /** + * Recording status + */ + private _status: RecordingStatus; + + constructor(policyId: string, owner: string) { + this.policyId = policyId; + this.owner = owner; + this.uuid = GenerateUUIDv4(); + this.tree = new BlockTreeGenerator(); + this._status = RecordingStatus.New; + } + + /** + * Record action + * @param action + * @param target + * @param user + * @param document + * @private + */ + private async record( + action: string, + target: string, + user: string, + document: any + ): Promise { + await DatabaseServer.createRecord({ + uuid: this.uuid, + policyId: this.policyId, + method: RecordMethod.Action, + action, + time: Date.now(), + user, + target, + document + }); + this.tree.sendMessage(PolicyEvents.RECORD_UPDATE_BROADCAST, this.getStatus()); + } + + /** + * Start recording + * @public + */ + public async start(): Promise { + await DatabaseServer.createRecord({ + uuid: this.uuid, + policyId: this.policyId, + method: RecordMethod.Start, + action: null, + time: Date.now(), + user: this.owner, + target: null, + document: null + }); + this._status = RecordingStatus.Recording; + this.tree.sendMessage(PolicyEvents.RECORD_UPDATE_BROADCAST, this.getStatus()); + return true; + } + + /** + * Stop recording + * @public + */ + public async stop(): Promise { + await DatabaseServer.createRecord({ + uuid: this.uuid, + policyId: this.policyId, + method: RecordMethod.Stop, + action: null, + time: Date.now(), + user: null, + target: null, + document: null + }); + this._status = RecordingStatus.Stopped; + this.tree.sendMessage(PolicyEvents.RECORD_UPDATE_BROADCAST, this.getStatus()); + return true; + } + + /** + * Record event (Select Group) + * @param user + * @param uuid + * @public + */ + public async selectGroup(user: IPolicyUser, uuid: string): Promise { + await this.record(RecordAction.SelectGroup, null, user?.did, { uuid }); + } + + /** + * Record event (Set Block Data) + * @param user + * @param block + * @param data + * @public + */ + public async setBlockData(user: IPolicyUser, block: AnyBlockType, data: any): Promise { + await this.record(RecordAction.SetBlockData, block?.tag, user?.did, data); + } + + /** + * Record event (Set External Data) + * @param data + * @public + */ + public async externalData(data: any): Promise { + await this.record(RecordAction.SetExternalData, null, null, data); + } + + /** + * Record event (Create User) + * @param did + * @param data + * @public + */ + public async createUser(did: string, data: any): Promise { + await this.record(RecordAction.CreateUser, null, did, data); + } + + /** + * Record event (Set User) + * @param did + * @public + */ + public async setUser(did: string): Promise { + await this.record(RecordAction.SetUser, null, did, null); + } + + /** + * Record event (Generate UUID) + * @param uuid + * @public + */ + public async generateUUID(uuid: string): Promise { + await DatabaseServer.createRecord({ + uuid: this.uuid, + policyId: this.policyId, + method: RecordMethod.Generate, + action: RecordAction.GenerateUUID, + time: Date.now(), + user: null, + target: null, + document: { uuid } + }); + } + + /** + * Record event (Generate DID) + * @param didDocument + * @public + */ + public async generateDidDocument(didDocument: DIDDocument): Promise { + const did = didDocument.getDid(); + await DatabaseServer.createRecord({ + uuid: this.uuid, + policyId: this.policyId, + method: RecordMethod.Generate, + action: RecordAction.GenerateDID, + time: Date.now(), + user: null, + target: null, + document: { did } + }); + } + + /** + * Get Recorded actions + * @public + */ + public async getActions(): Promise { + return await DatabaseServer.getRecord( + { + uuid: this.uuid, + policyId: this.policyId, + method: { + $in: [ + RecordMethod.Start, + RecordMethod.Action, + RecordMethod.Stop + ] + } + }, + { + fields: [ + 'uuid', + 'policyId', + 'method', + 'action', + 'time', + 'user', + 'target' + ] + } + ) as any; + } + + /** + * Get status + * @public + */ + public get status(): RecordingStatus { + return this._status; + } + + /** + * Get full status + * @public + */ + public getStatus() { + return { + type: this.type, + policyId: this.policyId, + uuid: this.uuid, + status: this._status + } + } + + /** + * Get results + * @public + */ + public async getResults(): Promise { + return null; + } +} \ No newline at end of file diff --git a/policy-service/src/policy-engine/record/running.ts b/policy-service/src/policy-engine/record/running.ts new file mode 100644 index 0000000000..997ffea54a --- /dev/null +++ b/policy-service/src/policy-engine/record/running.ts @@ -0,0 +1,680 @@ +import { GenerateUUIDv4, PolicyEvents, TopicType } from '@guardian/interfaces'; +import { RunningStatus } from './status.type'; +import { BlockTreeGenerator } from '@policy-engine/block-tree-generator'; +import { RecordAction } from './action.type'; +import { RecordMethod } from './method.type'; +import { IPolicyBlock } from '@policy-engine/policy-engine.interface'; +import { IPolicyUser, PolicyUser } from '@policy-engine/policy-user'; +import { PolicyComponentsUtils } from '@policy-engine/policy-components-utils'; +import { DIDDocument, DatabaseServer, IRecordResult, RecordImportExport } from '@guardian/common'; +import { RecordItem } from './record-item'; +import { GenerateDID, GenerateUUID, IGenerateValue, RecordItemStack, Utils } from './utils'; +import { AccountId, PrivateKey } from '@hashgraph/sdk'; + +/** + * Running controller + */ +export class Running { + /** + * Controller type + */ + public readonly type: string = 'Running'; + /** + * Policy ID + */ + public readonly policyId: string; + /** + * Policy owner + */ + public readonly owner: string; + /** + * Policy root block + */ + public readonly policyInstance: IPolicyBlock; + /** + * Options + */ + public readonly options: any; + /** + * Block messenger + */ + private readonly tree: BlockTreeGenerator; + /** + * Recorded actions (type = User Actions) + */ + private readonly _actions: RecordItemStack; + /** + * Recorded actions (type = Generate UUID) + */ + private readonly _generateUUID: RecordItemStack; + /** + * Recorded actions (type = Generate DID) + */ + private readonly _generateDID: RecordItemStack; + /** + * Recorded result + */ + private readonly _results: IRecordResult[]; + /** + * Record ID + */ + private _id: number; + /** + * Status + */ + private _status: RunningStatus; + /** + * Last error + */ + private _lastError: string; + /** + * list of created IDs + */ + private _generatedItems: IGenerateValue[]; + /** + * list of created DIDs + */ + private _generatedDIDs: IGenerateValue[]; + /** + * Start time + */ + private _startTime: number; + /** + * End time + */ + private _endTime: number; + /** + * Current delay + */ + private _currentDelay: any; + + constructor( + policyInstance: IPolicyBlock, + policyId: string, + owner: string, + actions: RecordItem[], + results: IRecordResult[], + options: any + ) { + this.policyInstance = policyInstance; + this.policyId = policyId; + this.owner = owner; + this.options = options; + this.tree = new BlockTreeGenerator(); + this._status = RunningStatus.New; + this._lastError = null; + this._id = -1; + this._actions = new RecordItemStack(); + this._generateUUID = new RecordItemStack(); + this._generateDID = new RecordItemStack(); + if (Array.isArray(actions)) { + this._actions.setItems(actions.filter(item => + item.method !== RecordMethod.Generate + )); + this._generateUUID.setItems(actions.filter(item => + item.method === RecordMethod.Generate && + item.action === RecordAction.GenerateUUID + )); + this._generateDID.setItems(actions.filter(item => + item.method === RecordMethod.Generate && + item.action === RecordAction.GenerateDID + )); + } + this._generatedItems = []; + this._generatedDIDs = []; + this._results = results; + } + + /** + * Start running + * @public + */ + public start(): boolean { + this._status = RunningStatus.Running; + this._lastError = null; + this._id = Date.now(); + this._generatedItems = []; + this._generatedDIDs = []; + this._actions.clearIndex(); + this._generateUUID.clearIndex(); + this._generateDID.clearIndex(); + this._startTime = Date.now(); + this.tree.sendMessage(PolicyEvents.RECORD_UPDATE_BROADCAST, this.getStatus()); + this._run(this._id).then(); + return true; + } + + /** + * Stop running + * @public + */ + public stop(): boolean { + this._status = RunningStatus.Stopped; + this._lastError = null; + this._endTime = Date.now(); + this.tree.sendMessage(PolicyEvents.RECORD_UPDATE_BROADCAST, this.getStatus()); + return true; + } + + /** + * Finish running + * @public + */ + public finished(): boolean { + this._id = -1; + this._status = RunningStatus.Finished; + this._lastError = null; + this._endTime = Date.now(); + this.tree.sendMessage(PolicyEvents.RECORD_UPDATE_BROADCAST, this.getStatus()); + return true; + } + + /** + * Stop running with error + * @param message + * @public + */ + public error(message: string): boolean { + this._status = RunningStatus.Error; + this._lastError = message; + this._endTime = Date.now(); + this.tree.sendMessage(PolicyEvents.RECORD_UPDATE_BROADCAST, this.getStatus()); + return true; + } + + /** + * Start running (with results) + * @public + */ + public async run(): Promise { + this._status = RunningStatus.Running; + this._lastError = null; + this._id = Date.now(); + this._generatedItems = []; + this._generatedDIDs = []; + this._actions.clearIndex(); + this._generateUUID.clearIndex(); + this._generateDID.clearIndex(); + this._startTime = Date.now(); + this.tree.sendMessage(PolicyEvents.RECORD_UPDATE_BROADCAST, this.getStatus()); + await this._run(this._id); + return await this.results(); + } + + /** + * Skip delay + * @param options + * @public + */ + public async fastForward(options: any): Promise { + try { + const skipIndex = Number(options?.index); + if (this._currentDelay) { + const { index, resolve, timer } = this._currentDelay; + if ((skipIndex && skipIndex === index) || (!skipIndex)) { + this._currentDelay = null; + clearTimeout(timer); + resolve(); + return true; + } + } + return false; + } catch (error) { + return false; + } + } + + /** + * Retry current action + * @public + */ + public async retryStep(): Promise { + try { + if (this._status === RunningStatus.Error) { + this._status = RunningStatus.Running; + this._lastError = null; + this.tree.sendMessage(PolicyEvents.RECORD_UPDATE_BROADCAST, this.getStatus()); + this._run(this._id).then(); + return true; + } else { + return false; + } + } catch (error) { + return false; + } + } + + /** + * Skip current action + * @public + */ + public async skipStep(): Promise { + try { + if (this._status === RunningStatus.Error) { + this._status = RunningStatus.Running; + this._lastError = null; + this._actions.next(); + this.tree.sendMessage(PolicyEvents.RECORD_UPDATE_BROADCAST, this.getStatus()); + this._run(this._id).then(); + return true; + } else { + return false; + } + } catch (error) { + return false; + } + } + + /** + * Get current results + * @public + */ + public async results(): Promise { + if (this._status !== RunningStatus.Stopped) { + return null; + } + const results: IRecordResult[] = []; + const db = new DatabaseServer(this.policyId); + const vcs = await db.getVcDocuments({ + updateDate: { + $gte: new Date(this._startTime), + $lt: new Date(this._endTime) + } + }); + for (const vc of vcs) { + results.push({ + id: vc.document.id, + type: 'vc', + document: vc.document + }); + } + const vps = await db.getVpDocuments({ + updateDate: { + $gte: new Date(this._startTime), + $lt: new Date(this._endTime) + } + }); + for (const vp of vps) { + results.push({ + id: vp.document.id, + type: 'vp', + document: vp.document + }); + } + return results; + } + + /** + * Run + * @param id + * @private + */ + private async _run(id: number): Promise { + while (this.isRunning(id)) { + const result = await this.next(); + if (!this.isRunning(id)) { + return; + } + if (result.code === 0) { + this.stop(); + return; + } + if (result.code < 0) { + this.error(result.error); + return; + } + this.tree.sendMessage(PolicyEvents.RECORD_UPDATE_BROADCAST, this.getStatus()); + await this.delay(result.delay, result.index); + } + } + + /** + * Check status + * @param id + * @private + */ + private isRunning(id: number): boolean { + return this._id === id && this._status === RunningStatus.Running; + } + + /** + * Create delay + * @param time + * @param index + * @private + */ + private async delay(time: number, index: number): Promise { + this._currentDelay = null; + return new Promise(resolve => { + const timer = setTimeout(() => { + this._currentDelay = null; + resolve(); + }, time); + this._currentDelay = { + index, + resolve, + timer + }; + }); + } + + /** + * Get user + * @param did + * @private + */ + private async getUser(did: string): Promise { + const userFull = new PolicyUser(did); + userFull.setVirtualUser({ did }); + const groups = await this.policyInstance + .components + .databaseServer + .getGroupsByUser(this.policyId, did); + for (const group of groups) { + if (group.active !== false) { + return userFull.setGroup(group); + } + } + return userFull; + } + + /** + * Replay event + * @param action + * @private + */ + private async runAction(action: RecordItem): Promise { + if (action.method === RecordMethod.Start) { + const recordOwner = action.user; + const value = new GenerateDID(recordOwner, this.owner); + this._generatedItems.push(value); + this._generatedDIDs.push(value); + await DatabaseServer.setVirtualUser(this.policyId, this.owner); + return null; + } + if (action.method === RecordMethod.Stop) { + return null; + } + if (action.method === RecordMethod.Action) { + const did = this.replaceDID(action.user); + const userFull = await this.getUser(did); + switch (action.action) { + case RecordAction.SelectGroup: { + const doc = await this.getActionDocument(action); + this.policyInstance.components.selectGroup(userFull, doc); + return null; + } + case RecordAction.SetBlockData: { + const doc = await this.getActionDocument(action); + const block = PolicyComponentsUtils.GetBlockByTag(this.policyId, action.target); + if (block && (await block.isAvailable(userFull))) { + if (typeof block.setData === 'function') { + await block.setData(userFull, doc); + return null; + } + } + return `Block (${action.target}) not available.`; + } + case RecordAction.SetExternalData: { + const doc = await this.getActionDocument(action); + for (const block of PolicyComponentsUtils.ExternalDataBlocks.values()) { + if (block.policyId === this.policyId) { + await (block as any).receiveData(doc); + } + } + return null; + } + case RecordAction.CreateUser: { + const topic = await DatabaseServer.getTopicByType(this.owner, TopicType.UserTopic); + const newPrivateKey = PrivateKey.generate(); + const newAccountId = new AccountId(Date.now()); + const didObject = await DIDDocument.create(newPrivateKey, topic.topicId); + const userDID = didObject.getDid(); + const document = didObject.getDocument(); + const users = await DatabaseServer.getVirtualUsers(this.policyId); + for (const user of users) { + if (user.did === userDID) { + return `User with DID (${userDID}) already exists.`; + } + } + const username = `Virtual User ${users.length}`; + await DatabaseServer.createVirtualUser( + this.policyId, + username, + userDID, + newAccountId.toString(), + newPrivateKey.toString() + ); + await this.policyInstance.components.databaseServer.saveDid({ + did: userDID, + document + }); + const value = new GenerateDID(userFull.did, userDID); + this._generatedItems.push(value); + this._generatedDIDs.push(value); + return null; + } + case RecordAction.SetUser: { + await DatabaseServer.setVirtualUser(this.policyId, did); + return null; + } + default: { + return `Action (${action.method}: ${action.action}) not defined.`; + } + } + } + return `Action (${action.method}: ${action.action}) not defined.`; + } + + /** + * Replace dynamic ids in documents + * @param action + * @private + */ + private async getActionDocument(action: RecordItem): Promise { + try { + let document = action.document; + if (document) { + document = await this.replaceId(document); + document = await this.replaceRow(document); + } + switch (action.action) { + case RecordAction.SelectGroup: { + return document?.uuid; + } + case RecordAction.SetBlockData: + case RecordAction.SetExternalData: { + return document; + } + case RecordAction.CreateUser: { + return document?.document; + } + default: { + return document; + } + } + } catch (error) { + return action.document; + } + } + + /** + * Replace DID + * @param did + * @private + */ + private replaceDID(did: string): string { + for (const value of this._generatedDIDs) { + if (value.oldValue === did) { + return value.newValue; + } + } + return did; + } + + /** + * Replace ids + * @param obj + * @private + */ + private async replaceId(obj: any): Promise { + for (const value of this._generatedItems) { + obj = Utils.replaceAllValues(obj, value); + } + return obj; + } + + /** + * Replace ids + * @param obj + * @private + */ + private async replaceRow(obj: any): Promise { + const result = Utils.findAllDocuments(obj); + for (const row of result) { + if (row.type === 'vc') { + const item = await this.policyInstance.databaseServer.getVcDocument(row.filters); + obj = row.replace(obj, item); + } + if (row.type === 'vp') { + const item = await this.policyInstance.databaseServer.getVpDocument(row.filters); + obj = row.replace(obj, item); + } + } + return obj; + } + + /** + * Get next action + * @private + */ + private async next() { + const result = { + index: -1, + delay: -1, + code: 0, + error: null + }; + try { + const action = this._actions.current; + if (!action) { + return result; + } + + const error = await this.runAction(action); + if (error) { + result.delay = -1; + result.code = -1; + result.error = error; + return result; + } + + const next = this._actions.next(); + if (next) { + result.index = this._actions.index; + result.code = 1; + const delay = (next.time - action.time); + if (Number.isFinite(delay) && delay > 0) { + result.delay = delay; + } else { + result.delay = 0; + } + return result; + } + + return result; + } catch (error) { + result.delay = -1; + result.code = -1; + result.error = this.getErrorMessage(error); + return result; + } + } + + /** + * Get error message + * @private + */ + private getErrorMessage(error: string | Error | any): string { + if (typeof error === 'string') { + return error; + } else if (error.message) { + return error.message; + } else if (error.error) { + return error.error; + } else if (error.name) { + return error.name; + } else { + return 'Unidentified error'; + } + } + + /** + * Get next uuid + * @public + */ + public async nextUUID(): Promise { + const uuid = GenerateUUIDv4(); + const action = this._generateUUID.current; + const old = action?.document?.uuid; + this._generateUUID.nextIndex(); + this._generatedItems.push(new GenerateUUID(old, uuid)); + return uuid; + } + + /** + * Get next did + * @public + */ + public async nextDID(topicId: string): Promise { + const didDocument = await DIDDocument.create(null, topicId); + const did = didDocument.getDid(); + const action = this._generateDID.current; + const old = action?.document?.did; + this._generateDID.nextIndex(); + const value = new GenerateDID(old, did); + this._generatedItems.push(value); + this._generatedDIDs.push(value); + return didDocument; + } + + /** + * Get full status + * @public + */ + public getStatus() { + return { + id: this._id, + type: this.type, + policyId: this.policyId, + status: this._status, + index: this._actions.index, + error: this._lastError, + count: this._actions.count + } + } + + /** + * Get recorded actions (type = User Actions) + * @public + */ + public async getActions(): Promise { + return this._actions.items; + } + + /** + * Get current and recorded results + * @public + */ + public async getResults(): Promise { + if (this._id) { + const results = await RecordImportExport.loadRecordResults(this.policyId, this._startTime, this._endTime); + return { + documents: results, + recorded: this._results + }; + } else { + return null; + } + } +} \ No newline at end of file diff --git a/policy-service/src/policy-engine/record/status.type.ts b/policy-service/src/policy-engine/record/status.type.ts new file mode 100644 index 0000000000..51d6810b2e --- /dev/null +++ b/policy-service/src/policy-engine/record/status.type.ts @@ -0,0 +1,19 @@ +/** + * Recording status + */ +export enum RecordingStatus { + New = 'New', + Recording = 'Recording', + Stopped = 'Stopped', +} + +/** + * Running status + */ +export enum RunningStatus { + New = 'New', + Running = 'Running', + Stopped = 'Stopped', + Error = 'Error', + Finished = 'Finished', +} \ No newline at end of file diff --git a/policy-service/src/policy-engine/record/utils.ts b/policy-service/src/policy-engine/record/utils.ts new file mode 100644 index 0000000000..50ae364da2 --- /dev/null +++ b/policy-service/src/policy-engine/record/utils.ts @@ -0,0 +1,368 @@ +import { RecordItem } from './record-item'; + +/** + * Generate item + */ +export interface IGenerateValue { + /** + * Old value + * @public + * @readonly + */ + readonly oldValue: T; + /** + * New value + * @public + * @readonly + */ + readonly newValue: T; + /** + * Replace value + * @param value + * @public + */ + replace(value: U): U; +} + +/** + * Generate UUID + */ +export class GenerateUUID implements IGenerateValue { + /** + * Old value + * @public + * @readonly + */ + public readonly oldValue: string; + /** + * New value + * @public + * @readonly + */ + public readonly newValue: string; + /** + * Value map + * @private + * @readonly + */ + private readonly _map: Map; + + constructor(oldValue: string, newValue: string) { + this.oldValue = oldValue; + this.newValue = newValue; + this._map = new Map(); + this._map.set(this.oldValue, this.newValue); + this._map.set(`urn:uuid:${this.oldValue}`, `urn:uuid:${this.newValue}`); + } + + /** + * Replace value + * @param value + * @public + */ + public replace(value: U): U { + if (typeof value === 'string' && this._map.has(value)) { + return this._map.get(value) as U; + } + return value; + } +} + +/** + * Generate DID + */ +export class GenerateDID implements IGenerateValue { + /** + * Old value + * @public + * @readonly + */ + public readonly oldValue: string; + /** + * New value + * @public + * @readonly + */ + public readonly newValue: string; + + constructor(oldValue: string, newValue: string) { + this.oldValue = oldValue; + this.newValue = newValue; + } + + /** + * Replace value + * @param value + * @public + */ + public replace(value: U): U { + if (typeof value === 'string' && value === this.oldValue) { + return this.newValue as U; + } + return value; + } +} + +/** + * Row document + */ +export class RowDocument { + /** + * Parent object + * @public + * @readonly + */ + public readonly parent: any; + /** + * Path in parent object + * @public + * @readonly + */ + public readonly key: any; + /** + * Document type + * @public + * @readonly + */ + public readonly type: 'vc' | 'vp' | 'did'; + /** + * Filters + * @public + * @readonly + */ + public readonly filters: any; + + constructor(document: any, parent: any, key: any) { + this.parent = parent; + this.key = key; + this.type = this.getRowType(document); + this.filters = { 'document.id': document.document.id }; + } + + private getRowType(obj: any): 'vc' | 'vp' | 'did' { + if (obj.dryRunClass === 'VcDocumentCollection') { + return 'vc'; + } + if (obj.dryRunClass === 'VpDocumentCollection') { + return 'vp'; + } + if (obj.dryRunClass === 'DidDocumentCollection') { + return 'did'; + } + if (obj.did) { + return 'did'; + } + if (obj.schema) { + return 'vc'; + } + return 'vp'; + } + + /** + * Replace document in parent object + * @param root - root object + * @param row - new document + * @public + */ + public replace(root: any, row: any): any { + if (row) { + if (this.parent && this.key) { + this.parent[this.key] = row; + return root; + } else { + return row; + } + } else { + return root; + } + } + + /** + * Check document id (document has db id and document id) + * @param obj - root object + * @public + */ + public static check(obj: any): boolean { + return obj && obj.id && obj._id && obj.document && obj.document.id; + } +} + +/** + * Utils + */ +export class Utils { + /** + * Replace all values + * @param obj + * @param value + * @public + * @static + */ + public static replaceAllValues( + obj: any, + value: IGenerateValue + ): any { + if (!obj) { + return obj; + } + if (typeof obj === 'object') { + if (Array.isArray(obj)) { + for (let i = 0; i < obj.length; i++) { + obj[i] = Utils.replaceAllValues(obj[i], value); + } + } else { + const keys = Object.keys(obj); + for (const key of keys) { + obj[key] = Utils.replaceAllValues(obj[key], value); + } + } + } + return value.replace(obj); + } + + /** + * Find all documents + * @param obj + * @param results + * @param parent + * @param parentKey + * @private + * @static + */ + private static _findAllDocuments( + obj: any, + results: RowDocument[], + parent: any, + parentKey: any + ): void { + if (obj && typeof obj === 'object') { + if (Array.isArray(obj)) { + for (let i = 0; i < obj.length; i++) { + Utils._findAllDocuments(obj[i], results, obj, i); + } + } else { + if (RowDocument.check(obj)) { + results.push(new RowDocument(obj, parent, parentKey)); + } else { + const keys = Object.keys(obj); + for (const key of keys) { + Utils._findAllDocuments(obj[key], results, obj, key); + } + } + } + } + } + + /** + * Find all documents + * @param obj + * @public + * @static + */ + public static findAllDocuments(obj: any): RowDocument[] { + const results: RowDocument[] = []; + if (obj && typeof obj === 'object') { + Utils._findAllDocuments(obj, results, null, null); + } + return results; + } +} + +/** + * Record actions stack + */ +export class RecordItemStack { + /** + * List of actions + */ + private _items: RecordItem[]; + /** + * Current index + */ + private _index: number; + + constructor() { + this._items = []; + this._index = 0; + } + + /** + * Set actions + * @param items + * @public + */ + public setItems(items: RecordItem[]): void { + if (Array.isArray(items)) { + this._items = items; + } else { + this._items = []; + } + this._index = 0; + } + + /** + * Clear index + * @public + */ + public clearIndex(): void { + this._index = 0; + } + + /** + * Next index + * @public + */ + public nextIndex(): void { + this._index++; + } + + /** + * Next actions + * @public + */ + public next(): RecordItem | undefined { + this._index++; + return this._items[this._index]; + } + + /** + * Prev actions + * @public + */ + public prev(): RecordItem | undefined { + this._index--; + return this._items[this._index]; + } + + /** + * Get current action + * @public + */ + public get current(): RecordItem | undefined { + return this._items[this._index]; + } + + /** + * Get current index + * @public + */ + public get index(): number { + return this._index; + } + + /** + * Get all actions + * @public + */ + public get items(): RecordItem[] { + return this._items; + } + + /** + * Get actions count + * @public + */ + public get count(): number { + return this._items.length; + } +} diff --git a/swagger-analytics.yaml b/swagger-analytics.yaml index c211036271..2d0ca90c0f 100644 --- a/swagger-analytics.yaml +++ b/swagger-analytics.yaml @@ -208,7 +208,7 @@ info: the heart of the Guardian solution is a sophisticated Policy Workflow Engine (PWE) that enables applications to offer a requirements-based tokenization implementation. - version: 2.19.1 + version: 2.20.0-prerelease contact: name: API developer url: https://envisionblockchain.com diff --git a/swagger.yaml b/swagger.yaml index d9b3f80e49..d00bb65fab 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -8195,6 +8195,472 @@ paths: security: - bearer: [] - bearerAuth: [] + /record/{policyId}/status: + get: + operationId: RecordApi_getRecordStatus + summary: Get recording or running status. + description: >- + Get recording or running status. Only users with the Standard Registry + role are allowed to make the request. + parameters: + - name: policyId + required: true + in: path + description: Policy Id + schema: + type: string + requestBody: + required: true + description: Object that contains options + content: + application/json: + schema: + $ref: '#/components/schemas/Object' + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/RecordStatusDTO' + '401': + description: Unauthorized + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: &ref_13 + - record + security: + - bearerAuth: [] + - bearer: [] + /record/{policyId}/recording/start: + post: + operationId: RecordApi_startRecord + summary: Start recording. + description: >- + Start recording. Only users with the Standard Registry role are allowed + to make the request. + parameters: + - name: policyId + required: true + in: path + description: Policy Id + schema: + type: string + requestBody: + required: true + description: Object that contains options + content: + application/json: + schema: + $ref: '#/components/schemas/Object' + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + type: boolean + '401': + description: Unauthorized + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_13 + security: + - bearerAuth: [] + - bearer: [] + /record/{policyId}/recording/stop: + post: + operationId: RecordApi_stopRecord + summary: Stop recording. + description: >- + Stop recording. Only users with the Standard Registry role are allowed + to make the request. + parameters: + - name: policyId + required: true + in: path + description: Policy Id + schema: + type: string + requestBody: + required: true + description: Object that contains options + content: + application/json: + schema: + $ref: '#/components/schemas/Object' + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + type: string + format: binary + '401': + description: Unauthorized + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_13 + security: + - bearerAuth: [] + - bearer: [] + /record/{policyId}/recording/actions: + get: + operationId: RecordApi_getRecordActions + summary: Get recorded actions. + description: >- + Get recorded actions. Only users with the Standard Registry role are + allowed to make the request. + parameters: + - name: policyId + required: true + in: path + description: Policy Id + schema: + type: string + requestBody: + required: true + description: Object that contains options + content: + application/json: + schema: + $ref: '#/components/schemas/Object' + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/RecordActionDTO' + '401': + description: Unauthorized + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_13 + security: + - bearerAuth: [] + - bearer: [] + /record/{policyId}/running/start: + post: + operationId: RecordApi_runRecord + summary: Run record from a zip file. + description: >- + Run record from a zip file. Only users with the Standard Registry role + are allowed to make the request. + parameters: + - name: policyId + required: true + in: path + description: Policy Id + schema: + type: string + requestBody: + required: true + description: A zip file containing record to be run. + content: + application/json: + schema: + type: string + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + type: boolean + '401': + description: Unauthorized + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_13 + security: + - bearerAuth: [] + - bearer: [] + /record/{policyId}/running/stop: + post: + operationId: RecordApi_stopRunning + summary: Stop running. + description: >- + Stop running. Only users with the Standard Registry role are allowed to + make the request. + parameters: + - name: policyId + required: true + in: path + description: Policy Id + schema: + type: string + requestBody: + required: true + description: Object that contains options + content: + application/json: + schema: + $ref: '#/components/schemas/Object' + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + type: boolean + '401': + description: Unauthorized + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_13 + security: + - bearerAuth: [] + - bearer: [] + /record/{policyId}/running/results: + get: + operationId: RecordApi_getRecordResults + summary: Get running results. + description: >- + Get running results. Only users with the Standard Registry role are + allowed to make the request. + parameters: + - name: policyId + required: true + in: path + description: Policy Id + schema: + type: string + requestBody: + required: true + description: Object that contains options + content: + application/json: + schema: + $ref: '#/components/schemas/Object' + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/RunningResultDTO' + '401': + description: Unauthorized + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_13 + security: + - bearerAuth: [] + - bearer: [] + /record/{policyId}/running/details: + get: + operationId: RecordApi_getRecordDetails + summary: Get running details. + description: >- + Get running details. Only users with the Standard Registry role are + allowed to make the request. + parameters: + - name: policyId + required: true + in: path + description: Policy Id + schema: + type: string + requestBody: + required: true + description: Object that contains options + content: + application/json: + schema: + $ref: '#/components/schemas/Object' + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/RunningDetailsDTO' + '401': + description: Unauthorized + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_13 + security: + - bearerAuth: [] + - bearer: [] + /record/{policyId}/running/fast-forward: + post: + operationId: RecordApi_fastForward + summary: Fast Forward. + description: >- + Fast Forward. Only users with the Standard Registry role are allowed to + make the request. + parameters: + - name: policyId + required: true + in: path + description: Policy Id + schema: + type: string + requestBody: + required: true + description: Object that contains options + content: + application/json: + schema: + $ref: '#/components/schemas/Object' + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + type: boolean + '401': + description: Unauthorized + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_13 + security: + - bearerAuth: [] + - bearer: [] + /record/{policyId}/running/retry: + post: + operationId: RecordApi_retryStep + summary: Retry step. + description: >- + Retry step. Only users with the Standard Registry role are allowed to + make the request. + parameters: + - name: policyId + required: true + in: path + description: Policy Id + schema: + type: string + requestBody: + required: true + description: Object that contains options + content: + application/json: + schema: + $ref: '#/components/schemas/Object' + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + type: boolean + '401': + description: Unauthorized + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_13 + security: + - bearerAuth: [] + - bearer: [] + /record/{policyId}/running/skip: + post: + operationId: RecordApi_skipStep + summary: Skip step. + description: >- + Skip step. Only users with the Standard Registry role are allowed to + make the request. + parameters: + - name: policyId + required: true + in: path + description: Policy Id + schema: + type: string + requestBody: + required: true + description: Object that contains options + content: + application/json: + schema: + $ref: '#/components/schemas/Object' + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + type: boolean + '401': + description: Unauthorized + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_13 + security: + - bearerAuth: [] + - bearer: [] /schemas/type/{type}: get: tags: @@ -8615,7 +9081,7 @@ info: the heart of the Guardian solution is a sophisticated Policy Workflow Engine (PWE) that enables applications to offer a requirements-based tokenization implementation. - version: 2.19.1 + version: 2.20.0-prerelease contact: name: API developer url: https://envisionblockchain.com @@ -9482,6 +9948,105 @@ components: - progress - type - taskId + Object: + type: object + properties: {} + RecordStatusDTO: + type: object + properties: + type: + type: string + policyId: + type: string + uuid: + type: string + status: + type: string + required: + - type + - policyId + - uuid + - status + RecordActionDTO: + type: object + properties: + uuid: + type: string + policyId: + type: string + method: + type: string + action: + type: string + time: + type: string + user: + type: string + target: + type: string + required: + - uuid + - policyId + - method + - action + - time + - user + - target + ResultInfoDTO: + type: object + properties: + tokens: + type: number + documents: + type: number + required: + - tokens + - documents + ResultDocumentDTO: + type: object + properties: + type: + type: string + schema: + type: string + rate: + type: string + documents: + type: object + required: + - type + - schema + - rate + - documents + RunningResultDTO: + type: object + properties: + info: + $ref: '#/components/schemas/ResultInfoDTO' + total: + type: number + documents: + $ref: '#/components/schemas/ResultDocumentDTO' + required: + - info + - total + - documents + RunningDetailsDTO: + type: object + properties: + left: + type: object + right: + type: object + total: + type: number + documents: + type: object + required: + - left + - right + - total + - documents ToolDTO: type: object properties: diff --git a/topic-viewer/package.json b/topic-viewer/package.json index d460c4b6a7..52e14ee3ea 100644 --- a/topic-viewer/package.json +++ b/topic-viewer/package.json @@ -29,5 +29,6 @@ "dev": "tsc -w", "start": "node dist/index.js" }, - "version": "2.19.1" + "version": "2.20.0-prerelease", + "stableVersion": "2.19.1" } diff --git a/tree-viewer/package.json b/tree-viewer/package.json index a1d9c0c86a..2afe605f8c 100644 --- a/tree-viewer/package.json +++ b/tree-viewer/package.json @@ -27,5 +27,6 @@ "dev": "tsc -w", "start": "node dist/index.js" }, - "version": "2.19.1" + "version": "2.20.0-prerelease", + "stableVersion": "2.19.1" } diff --git a/worker-service/package.json b/worker-service/package.json index 46c319132e..cbd9590feb 100644 --- a/worker-service/package.json +++ b/worker-service/package.json @@ -4,8 +4,8 @@ "@azure/core-rest-pipeline": "1.12.1" }, "dependencies": { - "@guardian/common": "^2.19.1", - "@guardian/interfaces": "^2.19.1", + "@guardian/common": "^2.20.0-prerelease", + "@guardian/interfaces": "^2.20.0-prerelease", "@hashgraph/sdk": "2.34.1", "@nestjs/common": "^9.4.1", "@nestjs/core": "^9.4.1", @@ -62,5 +62,6 @@ "start": "node dist/index.js", "test": "mocha tests/**/*.test.js --reporter mocha-junit-reporter --reporter-options mochaFile=../test_results/worker-service.xml --exit" }, - "version": "2.19.1" + "version": "2.20.0-prerelease", + "stableVersion": "2.19.1" } diff --git a/worker-service/src/api/helpers/hedera-sdk-helper.ts b/worker-service/src/api/helpers/hedera-sdk-helper.ts index a95a5ff18a..ec5a9ae590 100644 --- a/worker-service/src/api/helpers/hedera-sdk-helper.ts +++ b/worker-service/src/api/helpers/hedera-sdk-helper.ts @@ -668,7 +668,7 @@ export class HederaSDKHelper { .setMaxTransactionFee(MAX_FEE) .freezeWith(client); const signTx = await transaction.sign(_supplyKey); - const receipt = await this.executeAndReceipt(client, signTx, 'TokenMintNFTTransaction'); + const receipt = await this.executeAndReceipt(client, signTx, 'TokenMintNFTTransaction', data); const transactionStatus = receipt.status; if (transactionStatus === Status.Success) { @@ -1059,12 +1059,25 @@ export class HederaSDKHelper { ): Promise { if (this.dryRun) { await this.virtualTransactionLog(this.dryRun, type); + let serials = []; + if (type === 'TokenMintNFTTransaction') { + if (metadata && metadata.length) { + serials = new Array(Math.min(10, metadata?.length)); + const id = Date.now() % 1000000000; + for (let i = 0; i < serials.length; i++) { + serials[i] = Long.fromInt(id + i); + } + } + } + if (type === 'NFTTransferTransaction') { + serials = metadata; + } return { status: Status.Success, topicId: new TokenId(Date.now()), tokenId: new TokenId(Date.now()), accountId: new AccountId(Date.now()), - serials: [Long.fromInt(1)] + serials } as any } else { const id = GenerateUUIDv4(); diff --git a/yarn.lock b/yarn.lock index 933d60aae5..23e690314e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1081,7 +1081,7 @@ __metadata: languageName: node linkType: hard -"@guardian/common@2.19.1, @guardian/common@^2.19.1, @guardian/common@workspace:common": +"@guardian/common@2.20.0-prerelease, @guardian/common@^2.20.0-prerelease, @guardian/common@workspace:common": version: 0.0.0-use.local resolution: "@guardian/common@workspace:common" dependencies: @@ -1089,7 +1089,7 @@ __metadata: "@azure/identity": ^3.2.2 "@azure/keyvault-secrets": ^4.7.0 "@google-cloud/secret-manager": ^4.2.2 - "@guardian/interfaces": ^2.19.1 + "@guardian/interfaces": ^2.20.0-prerelease "@hashgraph/sdk": 2.34.1 "@mattrglobal/jsonld-signatures-bbs": ^1.1.2 "@meeco/cryppo": ^2.0.2 @@ -1143,7 +1143,7 @@ __metadata: languageName: unknown linkType: soft -"@guardian/interfaces@2.19.1, @guardian/interfaces@^2.19.1, @guardian/interfaces@workspace:interfaces": +"@guardian/interfaces@2.20.0-prerelease, @guardian/interfaces@^2.20.0-prerelease, @guardian/interfaces@workspace:interfaces": version: 0.0.0-use.local resolution: "@guardian/interfaces@workspace:interfaces" dependencies: @@ -3669,8 +3669,8 @@ __metadata: version: 0.0.0-use.local resolution: "analytics-service@workspace:analytics-service" dependencies: - "@guardian/common": ^2.19.1 - "@guardian/interfaces": ^2.19.1 + "@guardian/common": ^2.20.0-prerelease + "@guardian/interfaces": ^2.20.0-prerelease "@nestjs/common": ^9.4.1 "@nestjs/core": ^9.4.1 "@nestjs/jwt": ^10.0.3 @@ -3871,8 +3871,8 @@ __metadata: version: 0.0.0-use.local resolution: "api-gateway@workspace:api-gateway" dependencies: - "@guardian/common": ^2.19.1 - "@guardian/interfaces": ^2.19.1 + "@guardian/common": ^2.20.0-prerelease + "@guardian/interfaces": ^2.20.0-prerelease "@nestjs/common": ^9.4.1 "@nestjs/core": ^9.4.1 "@nestjs/jwt": ^10.0.3 @@ -4323,8 +4323,8 @@ __metadata: version: 0.0.0-use.local resolution: "auth-service@workspace:auth-service" dependencies: - "@guardian/common": ^2.19.1 - "@guardian/interfaces": ^2.19.1 + "@guardian/common": ^2.20.0-prerelease + "@guardian/interfaces": ^2.20.0-prerelease "@meeco/cryppo": ^2.0.2 "@mikro-orm/core": 5.7.12 "@mikro-orm/mongodb": 5.7.12 @@ -7884,8 +7884,8 @@ __metadata: version: 0.0.0-use.local resolution: "guardian-service@workspace:guardian-service" dependencies: - "@guardian/common": ^2.19.1 - "@guardian/interfaces": ^2.19.1 + "@guardian/common": ^2.20.0-prerelease + "@guardian/interfaces": ^2.20.0-prerelease "@hashgraph/sdk": 2.34.1 "@mattrglobal/jsonld-signatures-bbs": ^1.1.2 "@meeco/cryppo": ^2.0.2 @@ -10455,8 +10455,8 @@ __metadata: version: 0.0.0-use.local resolution: "logger-service@workspace:logger-service" dependencies: - "@guardian/common": ^2.19.1 - "@guardian/interfaces": ^2.19.1 + "@guardian/common": ^2.20.0-prerelease + "@guardian/interfaces": ^2.20.0-prerelease "@mikro-orm/core": 5.7.12 "@mikro-orm/mongodb": 5.7.12 "@nestjs/common": ^9.4.1 @@ -11249,7 +11249,7 @@ __metadata: version: 0.0.0-use.local resolution: "mrv-sender@workspace:mrv-sender" dependencies: - "@guardian/common": ^2.19.1 + "@guardian/common": ^2.20.0-prerelease "@transmute/credentials-context": 0.7.0-unstable.80 "@transmute/did-context": 0.7.0-unstable.80 "@transmute/ed25519-signature-2018": 0.7.0-unstable.80 @@ -11800,8 +11800,8 @@ __metadata: version: 0.0.0-use.local resolution: "notification-service@workspace:notification-service" dependencies: - "@guardian/common": ^2.19.1 - "@guardian/interfaces": ^2.19.1 + "@guardian/common": ^2.20.0-prerelease + "@guardian/interfaces": ^2.20.0-prerelease "@mikro-orm/core": 5.7.12 "@mikro-orm/mongodb": 5.7.12 "@nestjs/common": ^9.4.1 @@ -12635,8 +12635,8 @@ __metadata: version: 0.0.0-use.local resolution: "policy-service@workspace:policy-service" dependencies: - "@guardian/common": 2.19.1 - "@guardian/interfaces": 2.19.1 + "@guardian/common": 2.20.0-prerelease + "@guardian/interfaces": 2.20.0-prerelease "@hashgraph/sdk": 2.34.1 "@mattrglobal/jsonld-signatures-bbs": ^1.1.2 "@meeco/cryppo": 2.0.2 @@ -15957,8 +15957,8 @@ __metadata: version: 0.0.0-use.local resolution: "worker-service@workspace:worker-service" dependencies: - "@guardian/common": ^2.19.1 - "@guardian/interfaces": ^2.19.1 + "@guardian/common": ^2.20.0-prerelease + "@guardian/interfaces": ^2.20.0-prerelease "@hashgraph/sdk": 2.34.1 "@nestjs/common": ^9.4.1 "@nestjs/core": ^9.4.1