diff --git a/api-gateway/src/api/service/formulas.ts b/api-gateway/src/api/service/formulas.ts new file mode 100644 index 0000000000..f3d1923905 --- /dev/null +++ b/api-gateway/src/api/service/formulas.ts @@ -0,0 +1,505 @@ +import { IAuthUser, PinoLogger } from '@guardian/common'; +import { Body, Controller, Delete, Get, HttpCode, HttpException, HttpStatus, Param, Post, Put, Query, Response } from '@nestjs/common'; +import { Permissions, UserPermissions } from '@guardian/interfaces'; +import { ApiBody, ApiInternalServerErrorResponse, ApiOkResponse, ApiOperation, ApiTags, ApiQuery, ApiExtraModels, ApiParam } from '@nestjs/swagger'; +import { Examples, InternalServerErrorDTO, FormulaDTO, FormulaRelationshipsDTO, pageHeader, FormulasOptionsDTO, FormulasDataDTO } from '#middlewares'; +import { Guardians, InternalException, EntityOwner } from '#helpers'; +import { AuthUser, Auth } from '#auth'; + +@Controller('formulas') +@ApiTags('formulas') +export class FormulasApi { + constructor(private readonly logger: PinoLogger) { } + + /** + * Creates a new formula + */ + @Post('/') + @Auth(Permissions.FORMULAS_FORMULA_CREATE) + @ApiOperation({ + summary: 'Creates a new formula.', + description: 'Creates a new formula.', + }) + @ApiBody({ + description: 'Configuration.', + type: FormulaDTO, + required: true + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: FormulaDTO, + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(FormulaDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.CREATED) + async createFormula( + @AuthUser() user: IAuthUser, + @Body() formula: FormulaDTO + ): Promise { + try { + if (!formula) { + throw new HttpException('Invalid config.', HttpStatus.UNPROCESSABLE_ENTITY); + } + const owner = new EntityOwner(user); + const guardian = new Guardians(); + return await guardian.createFormula(formula, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Get page + */ + @Get('/') + @Auth(Permissions.FORMULAS_FORMULA_READ) + @ApiOperation({ + summary: 'Return a list of all formulas.', + description: 'Returns all formulas.', + }) + @ApiQuery({ + name: 'pageIndex', + type: Number, + description: 'The number of pages to skip before starting to collect the result set', + required: false, + example: 0 + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + description: 'The numbers of items to return', + required: false, + example: 20 + }) + @ApiQuery({ + name: 'policyId', + type: String, + description: 'Policy Id', + required: false, + example: Examples.DB_ID + }) + @ApiOkResponse({ + description: 'Successful operation.', + isArray: true, + headers: pageHeader, + type: FormulaDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(FormulaDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async getFormulas( + @AuthUser() user: IAuthUser, + @Response() res: any, + @Query('pageIndex') pageIndex?: number, + @Query('pageSize') pageSize?: number, + @Query('policyId') policyId?: string + ): Promise { + try { + const owner = new EntityOwner(user); + const guardians = new Guardians(); + const { items, count } = await guardians.getFormulas({ + policyId, + pageIndex, + pageSize + }, owner); + return res.header('X-Total-Count', count).send(items); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Get formula by id + */ + @Get('/:formulaId') + @Auth(Permissions.FORMULAS_FORMULA_READ) + @ApiOperation({ + summary: 'Retrieves formula.', + description: 'Retrieves formula for the specified ID.' + }) + @ApiParam({ + name: 'formulaId', + type: String, + description: 'Formula Identifier', + required: true, + example: Examples.DB_ID + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: FormulaDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(FormulaDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async getFormulaById( + @AuthUser() user: IAuthUser, + @Param('formulaId') formulaId: string + ): Promise { + try { + if (!formulaId) { + throw new HttpException('Invalid ID.', HttpStatus.UNPROCESSABLE_ENTITY); + } + const owner = new EntityOwner(user); + const guardian = new Guardians(); + return await guardian.getFormulaById(formulaId, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Update formula + */ + @Put('/:formulaId') + @Auth(Permissions.FORMULAS_FORMULA_CREATE) + @ApiOperation({ + summary: 'Updates formula.', + description: 'Updates formula configuration for the specified formula ID.', + }) + @ApiParam({ + name: 'formulaId', + type: 'string', + required: true, + description: 'Formula Identifier', + example: Examples.DB_ID, + }) + @ApiBody({ + description: 'Object that contains a configuration.', + required: true, + type: FormulaDTO + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: FormulaDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) + @ApiExtraModels(FormulaDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async updateFormula( + @AuthUser() user: IAuthUser, + @Param('formulaId') formulaId: string, + @Body() item: FormulaDTO + ): Promise { + try { + if (!formulaId) { + throw new HttpException('Invalid ID.', HttpStatus.UNPROCESSABLE_ENTITY); + } + const owner = new EntityOwner(user); + const guardians = new Guardians(); + const oldItem = await guardians.getFormulaById(formulaId, owner); + if (!oldItem) { + throw new HttpException('Item not found.', HttpStatus.NOT_FOUND); + } + return await guardians.updateFormula(formulaId, item, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Delete formula + */ + @Delete('/:formulaId') + @Auth(Permissions.FORMULAS_FORMULA_CREATE) + @ApiOperation({ + summary: 'Deletes the formula.', + description: 'Deletes the formula with the provided ID.', + }) + @ApiParam({ + name: 'formulaId', + type: 'string', + required: true, + description: 'Formula Identifier', + example: Examples.DB_ID, + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: Boolean + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) + @ApiExtraModels(InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async deleteFormula( + @AuthUser() user: IAuthUser, + @Param('formulaId') formulaId: string + ): Promise { + try { + if (!formulaId) { + throw new HttpException('Invalid ID.', HttpStatus.UNPROCESSABLE_ENTITY) + } + const owner = new EntityOwner(user); + const guardians = new Guardians(); + return await guardians.deleteFormula(formulaId, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Get relationships by id + */ + @Get('/:formulaId/relationships') + @Auth(Permissions.FORMULAS_FORMULA_CREATE) + @ApiOperation({ + summary: 'Retrieves Formula relationships.', + description: 'Retrieves Formula relationships for the specified ID.' + }) + @ApiParam({ + name: 'formulaId', + type: String, + description: 'Formula Identifier', + required: true, + example: Examples.DB_ID + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: FormulaRelationshipsDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(FormulaRelationshipsDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async getSchemaRuleRelationships( + @AuthUser() user: IAuthUser, + @Param('formulaId') formulaId: string + ): Promise { + try { + if (!formulaId) { + throw new HttpException('Invalid ID.', HttpStatus.UNPROCESSABLE_ENTITY); + } + const owner = new EntityOwner(user); + const guardian = new Guardians(); + return await guardian.getFormulaRelationships(formulaId, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Import formula + */ + @Post('/:policyId/import/file') + @Auth(Permissions.FORMULAS_FORMULA_CREATE) + @ApiOperation({ + summary: 'Imports new formula from a zip file.', + description: 'Imports new formula from the provided zip file into the local DB.', + }) + @ApiParam({ + name: 'policyId', + type: String, + description: 'Policy Id', + required: true, + example: Examples.DB_ID + }) + @ApiBody({ + description: 'A zip file containing formula to be imported.', + required: true + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: FormulaDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) + @ApiExtraModels(FormulaDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.CREATED) + async importFormula( + @AuthUser() user: IAuthUser, + @Param('policyId') policyId: string, + @Body() zip: any + ): Promise { + const guardian = new Guardians(); + try { + const owner = new EntityOwner(user); + return await guardian.importFormula(zip, policyId, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Export formula + */ + @Get('/:formulaId/export/file') + @Auth(Permissions.FORMULAS_FORMULA_READ) + @ApiOperation({ + summary: 'Returns a zip file containing formula.', + description: 'Returns a zip file containing formula.', + }) + @ApiParam({ + name: 'formulaId', + type: String, + description: 'Formula Identifier', + required: true, + example: Examples.DB_ID + }) + @ApiOkResponse({ + description: 'Successful operation. Response zip file.' + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) + @ApiExtraModels(InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async exportFormula( + @AuthUser() user: IAuthUser, + @Param('formulaId') formulaId: string, + @Response() res: any + ): Promise { + const guardian = new Guardians(); + try { + const owner = new EntityOwner(user); + const file: any = await guardian.exportFormula(formulaId, owner); + res.header('Content-disposition', `attachment; filename=theme_${Date.now()}`); + res.header('Content-type', 'application/zip'); + return res.send(file); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Preview formula + */ + @Post('/import/file/preview') + @Auth(Permissions.FORMULAS_FORMULA_CREATE) + @ApiOperation({ + summary: 'Imports a zip file containing formula.', + description: 'Imports a zip file containing formula.', + }) + @ApiBody({ + description: 'File.', + }) + @ApiOkResponse({ + description: 'Formula preview.', + type: FormulaDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(FormulaDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async previewFormula( + @AuthUser() user: IAuthUser, + @Body() body: any + ): Promise { + try { + const owner = new EntityOwner(user); + const guardian = new Guardians(); + return await guardian.previewFormula(body, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Publish formula + */ + @Put('/:formulaId/publish') + @Auth(Permissions.FORMULAS_FORMULA_CREATE) + @ApiOperation({ + summary: 'Publishes formula.', + description: 'Publishes formula for the specified formula ID.', + }) + @ApiParam({ + name: 'formulaId', + type: String, + description: 'Formula Identifier', + required: true, + example: Examples.DB_ID + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: FormulaDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) + @ApiExtraModels(FormulaDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async publishPolicyLabel( + @AuthUser() user: IAuthUser, + @Param('formulaId') formulaId: string + ): Promise { + try { + if (!formulaId) { + throw new HttpException('Invalid ID.', HttpStatus.UNPROCESSABLE_ENTITY); + } + const owner = new EntityOwner(user); + const guardians = new Guardians(); + const oldItem = await guardians.getFormulaById(formulaId, owner); + if (!oldItem) { + throw new HttpException('Item not found.', HttpStatus.NOT_FOUND); + } + return await guardians.publishFormula(formulaId, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Get formulas and data + */ + @Post('/data') + @Auth() + @ApiOperation({ + summary: '', + description: '', + }) + @ApiBody({ + description: 'Options.', + type: FormulasOptionsDTO, + required: true + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: FormulasDataDTO, + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(FormulasDataDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.CREATED) + async getSchemaRuleData( + @AuthUser() user: IAuthUser, + @Body() options: FormulasOptionsDTO + ): Promise { + try { + if (!options) { + throw new HttpException('Invalid config.', HttpStatus.UNPROCESSABLE_ENTITY); + } + if (!UserPermissions.has(user, [Permissions.POLICIES_POLICY_EXECUTE, Permissions.POLICIES_POLICY_MANAGE])) { + return null; + } else { + const owner = new EntityOwner(user); + const guardian = new Guardians(); + return await guardian.getFormulasData(options, owner); + } + } catch (error) { + await InternalException(error, this.logger); + } + } +} diff --git a/api-gateway/src/api/service/schema-rules.ts b/api-gateway/src/api/service/schema-rules.ts index f43f25a05a..c166fd243c 100644 --- a/api-gateway/src/api/service/schema-rules.ts +++ b/api-gateway/src/api/service/schema-rules.ts @@ -2,7 +2,7 @@ import { IAuthUser, PinoLogger } from '@guardian/common'; import { Body, Controller, Delete, Get, HttpCode, HttpException, HttpStatus, Param, Post, Put, Query, Response } from '@nestjs/common'; import { Permissions, UserPermissions } from '@guardian/interfaces'; import { ApiBody, ApiInternalServerErrorResponse, ApiOkResponse, ApiOperation, ApiTags, ApiQuery, ApiExtraModels, ApiParam } from '@nestjs/swagger'; -import { Examples, InternalServerErrorDTO, SchemaRuleDTO, SchemaRuleDataDTO, SchemaRuleRelationshipsDTO, pageHeader } from '#middlewares'; +import { Examples, InternalServerErrorDTO, SchemaRuleDTO, SchemaRuleDataDTO, SchemaRuleOptionsDTO, SchemaRuleRelationshipsDTO, pageHeader } from '#middlewares'; import { Guardians, InternalException, EntityOwner } from '#helpers'; import { AuthUser, Auth } from '#auth'; @@ -390,24 +390,25 @@ export class SchemaRulesApi { description: '', }) @ApiBody({ - description: 'Configuration.', - type: SchemaRuleDataDTO, + description: 'Options.', + type: SchemaRuleOptionsDTO, required: true }) @ApiOkResponse({ description: 'Successful operation.', type: SchemaRuleDataDTO, + isArray: true }) @ApiInternalServerErrorResponse({ description: 'Internal server error.', type: InternalServerErrorDTO, }) - @ApiExtraModels(SchemaRuleDataDTO, InternalServerErrorDTO) + @ApiExtraModels(SchemaRuleOptionsDTO, SchemaRuleDataDTO, InternalServerErrorDTO) @HttpCode(HttpStatus.CREATED) async getSchemaRuleData( @AuthUser() user: IAuthUser, - @Body() options: any - ): Promise { + @Body() options: SchemaRuleOptionsDTO + ): Promise { try { if (!options) { throw new HttpException('Invalid config.', HttpStatus.UNPROCESSABLE_ENTITY); diff --git a/api-gateway/src/app.module.ts b/api-gateway/src/app.module.ts index 163d17416c..6c4a35d5b7 100644 --- a/api-gateway/src/app.module.ts +++ b/api-gateway/src/app.module.ts @@ -44,6 +44,7 @@ import { PolicyStatisticsApi } from './api/service/policy-statistics.js'; import { SchemaRulesApi } from './api/service/schema-rules.js'; import { loggerMongoProvider, pinoLoggerProvider } from './helpers/providers/index.js'; import { PolicyLabelsApi } from './api/service/policy-labels.js'; +import { FormulasApi } from './api/service/formulas.js'; // const JSON_REQUEST_LIMIT = process.env.JSON_REQUEST_LIMIT || '1mb'; // const RAW_REQUEST_LIMIT = process.env.RAW_REQUEST_LIMIT || '1gb'; @@ -97,6 +98,7 @@ import { PolicyLabelsApi } from './api/service/policy-labels.js'; PermissionsApi, PolicyStatisticsApi, SchemaRulesApi, + FormulasApi, PolicyLabelsApi, WorkerTasksController ], diff --git a/api-gateway/src/helpers/guardians.ts b/api-gateway/src/helpers/guardians.ts index 7eba0430bb..6da3f5e027 100644 --- a/api-gateway/src/helpers/guardians.ts +++ b/api-gateway/src/helpers/guardians.ts @@ -18,7 +18,6 @@ import { IToken, ITokenInfo, IUser, - IVC, IVCDocument, IVPDocument, MessageAPI, @@ -49,7 +48,12 @@ import { PolicyLabelRelationshipsDTO, PolicyLabelDocumentRelationshipsDTO, PolicyLabelComponentsDTO, - PolicyLabelFiltersDTO + PolicyLabelFiltersDTO, + FormulaDTO, + SchemaRuleOptionsDTO, + FormulasOptionsDTO, + FormulasDataDTO, + FormulaRelationshipsDTO } from '#middlewares'; /** @@ -2953,7 +2957,7 @@ export class Guardians extends NatsService { * @param definition * @param owner * - * @returns theme + * @returns statistic */ public async updateStatisticDefinition( definitionId: string, @@ -2981,7 +2985,7 @@ export class Guardians extends NatsService { * @param definitionId * @param owner * - * @returns Operation Success + * @returns statistic */ public async publishStatisticDefinition(definitionId: string, owner: IOwner): Promise { return await this.sendMessage(MessageAPI.PUBLISH_STATISTIC_DEFINITION, { definitionId, owner }); @@ -3028,7 +3032,7 @@ export class Guardians extends NatsService { * @param assessmentId * @param owner * - * @returns Operation Success + * @returns assessment */ public async getStatisticAssessment( definitionId: string, @@ -3045,7 +3049,7 @@ export class Guardians extends NatsService { * @param assessmentId * @param owner * - * @returns Operation Success + * @returns relationships */ public async getStatisticAssessmentRelationships( definitionId: string, @@ -3111,7 +3115,7 @@ export class Guardians extends NatsService { * * @param ruleId * @param owner - * @returns Operation Success + * @returns schema rule */ public async getSchemaRuleById(ruleId: string, owner: IOwner): Promise { return await this.sendMessage(MessageAPI.GET_SCHEMA_RULE, { ruleId, owner }); @@ -3136,7 +3140,7 @@ export class Guardians extends NatsService { * @param definition * @param owner * - * @returns theme + * @returns schema rule */ public async updateSchemaRule( ruleId: string, @@ -3164,7 +3168,7 @@ export class Guardians extends NatsService { * @param ruleId * @param owner * - * @returns Operation Success + * @returns schema rule */ public async activateSchemaRule(ruleId: string, owner: IOwner): Promise { return await this.sendMessage(MessageAPI.ACTIVATE_SCHEMA_RULE, { ruleId, owner }); @@ -3176,7 +3180,7 @@ export class Guardians extends NatsService { * @param ruleId * @param owner * - * @returns Operation Success + * @returns schema rule */ public async inactivateSchemaRule(ruleId: string, owner: IOwner): Promise { return await this.sendMessage(MessageAPI.INACTIVATE_SCHEMA_RULE, { ruleId, owner }); @@ -3188,9 +3192,9 @@ export class Guardians extends NatsService { * @param options * @param owner * - * @returns Operation Success + * @returns Schema Rule Data */ - public async getSchemaRuleData(options: any, owner: IOwner): Promise { + public async getSchemaRuleData(options: SchemaRuleOptionsDTO, owner: IOwner): Promise { return await this.sendMessage(MessageAPI.GET_SCHEMA_RULE_DATA, { options, owner }); } @@ -3222,7 +3226,6 @@ export class Guardians extends NatsService { return await this.sendMessage(MessageAPI.PREVIEW_SCHEMA_RULE_FILE, { zip, owner }); } - /** * Get Indexer availability */ @@ -3258,7 +3261,7 @@ export class Guardians extends NatsService { * * @param definitionId * @param owner - * @returns Operation Success + * @returns policy label */ public async getPolicyLabelById(definitionId: string, owner: IOwner): Promise { return await this.sendMessage(MessageAPI.GET_POLICY_LABEL, { definitionId, owner }); @@ -3283,7 +3286,7 @@ export class Guardians extends NatsService { * @param label * @param owner * - * @returns theme + * @returns policy label */ public async updatePolicyLabel( definitionId: string, @@ -3311,7 +3314,7 @@ export class Guardians extends NatsService { * @param definitionId * @param owner * - * @returns Operation Success + * @returns policy label */ public async publishPolicyLabel(definitionId: string, owner: IOwner): Promise { return await this.sendMessage(MessageAPI.PUBLISH_POLICY_LABEL, { definitionId, owner }); @@ -3450,7 +3453,7 @@ export class Guardians extends NatsService { * @param documentId * @param owner * - * @returns Operation Success + * @returns policy label document */ public async getLabelDocument( definitionId: string, @@ -3469,7 +3472,7 @@ export class Guardians extends NatsService { * @param documentId * @param owner * - * @returns Operation Success + * @returns relationships */ public async getLabelDocumentRelationships( definitionId: string, @@ -3480,4 +3483,132 @@ export class Guardians extends NatsService { { definitionId, documentId, owner } ); } + + /** + * Create formula + * + * @param formula + * @param owner + * + * @returns formula + */ + public async createFormula(formula: FormulaDTO, owner: IOwner): Promise { + return await this.sendMessage(MessageAPI.CREATE_FORMULA, { formula, owner }); + } + + /** + * Return formulas + * + * @param filters + * @param owner + * + * @returns {ResponseAndCount} + */ + public async getFormulas(filters: IFilter, owner: IOwner): Promise> { + return await this.sendMessage(MessageAPI.GET_FORMULAS, { filters, owner }); + } + + /** + * Get formula + * + * @param formulaId + * @param owner + * @returns formula + */ + public async getFormulaById(formulaId: string, owner: IOwner): Promise { + return await this.sendMessage(MessageAPI.GET_FORMULA, { formulaId, owner }); + } + + /** + * Update formula + * + * @param formulaId + * @param definition + * @param owner + * + * @returns formula + */ + public async updateFormula( + formulaId: string, + formula: FormulaDTO, + owner: IOwner + ): Promise { + return await this.sendMessage(MessageAPI.UPDATE_FORMULA, { formulaId, formula, owner }); + } + + /** + * Delete formula + * + * @param formulaId + * @param owner + * + * @returns Operation Success + */ + public async deleteFormula(formulaId: string, owner: IOwner): Promise { + return await this.sendMessage(MessageAPI.DELETE_FORMULA, { formulaId, owner }); + } + + /** + * Load formula file for import + * @param zip + * @param owner + */ + public async importFormula(zip: any, policyId: string, owner: IOwner): Promise { + return await this.sendMessage(MessageAPI.IMPORT_FORMULA_FILE, { zip, policyId, owner }); + } + + /** + * Get formula export file + * @param formulaId + * @param owner + */ + public async exportFormula(formulaId: string, owner: IOwner) { + const file = await this.sendMessage(MessageAPI.EXPORT_FORMULA_FILE, { formulaId, owner }) as any; + return Buffer.from(file, 'base64'); + } + + /** + * Get formula info from file + * @param zip + * @param owner + */ + public async previewFormula(zip: any, owner: IOwner) { + return await this.sendMessage(MessageAPI.PREVIEW_FORMULA_FILE, { zip, owner }); + } + + /** + * Get formula relationships + * + * @param formulaId + * @param owner + * + * @returns Operation Success + */ + public async getFormulaRelationships(formulaId: string, owner: IOwner): Promise { + return await this.sendMessage(MessageAPI.GET_FORMULA_RELATIONSHIPS, { formulaId, owner }); + } + + /** + * Get Formulas Data + * + * @param options + * @param owner + * + * @returns Formulas Data + */ + public async getFormulasData(options: FormulasOptionsDTO, owner: IOwner): Promise { + return await this.sendMessage(MessageAPI.GET_FORMULAS_DATA, { options, owner }); + } + + /** + * Publish Formula + * + * @param formulaId + * @param owner + * + * @returns statistic + */ + public async publishFormula(formulaId: string, owner: IOwner): Promise { + return await this.sendMessage(MessageAPI.PUBLISH_FORMULA, { formulaId, owner }); + } } diff --git a/api-gateway/src/middlewares/validation/schemas/formulas.dto.ts b/api-gateway/src/middlewares/validation/schemas/formulas.dto.ts new file mode 100644 index 0000000000..20f80a665b --- /dev/null +++ b/api-gateway/src/middlewares/validation/schemas/formulas.dto.ts @@ -0,0 +1,220 @@ +import { ApiExtraModels, ApiProperty } from '@nestjs/swagger'; +import { Examples } from '../examples.js'; +import { IsArray, IsObject, IsOptional, IsString } from 'class-validator'; +import { EntityStatus } from '@guardian/interfaces'; +import { VcDocumentDTO } from './document.dto.js'; +import { SchemaDTO } from './schemas.dto.js'; +import { PolicyDTO } from './policies.dto.js'; + +export class FormulaDTO { + @ApiProperty({ + type: 'string', + required: false, + example: Examples.DB_ID + }) + id?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.UUID + }) + @IsOptional() + @IsString() + uuid?: string; + + @ApiProperty({ + type: 'string', + required: true, + example: 'Tool name' + }) + @IsString() + name: string; + + @ApiProperty({ + type: 'string', + required: false, + example: 'Description' + }) + @IsOptional() + @IsString() + description?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.DID + }) + @IsOptional() + @IsString() + creator?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.DID + }) + @IsOptional() + @IsString() + owner?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.MESSAGE_ID + }) + @IsOptional() + @IsString() + messageId?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.DB_ID + }) + @IsOptional() + @IsString() + policyId?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.ACCOUNT_ID + }) + @IsOptional() + @IsString() + policyTopicId?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.ACCOUNT_ID + }) + @IsOptional() + @IsString() + policyInstanceTopicId?: string; + + @ApiProperty({ + type: 'string', + required: false, + enum: EntityStatus, + example: EntityStatus.DRAFT + }) + @IsOptional() + @IsString() + status?: EntityStatus; + + @ApiProperty({ + type: 'object', + nullable: true, + required: false + }) + @IsOptional() + @IsObject() + config?: any; +} + +@ApiExtraModels(PolicyDTO, SchemaDTO, FormulaDTO) +export class FormulaRelationshipsDTO { + @ApiProperty({ + type: () => PolicyDTO, + required: false, + }) + @IsOptional() + @IsObject() + policy?: PolicyDTO; + + @ApiProperty({ + type: () => SchemaDTO, + required: false, + isArray: true, + }) + @IsOptional() + @IsArray() + schemas?: SchemaDTO[]; + + @ApiProperty({ + type: () => FormulaDTO, + required: false, + isArray: true, + }) + @IsOptional() + @IsObject() + formulas?: FormulaDTO[]; +} + +export class FormulasOptionsDTO { + @ApiProperty({ + type: 'string', + required: false, + example: Examples.DB_ID + }) + @IsOptional() + @IsString() + policyId?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.DB_ID + }) + @IsOptional() + @IsString() + schemaId?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.DB_ID + }) + @IsOptional() + @IsString() + documentId?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.DB_ID + }) + @IsOptional() + @IsString() + parentId?: string; +} + +@ApiExtraModels(FormulaDTO, SchemaDTO, VcDocumentDTO) +export class FormulasDataDTO { + @ApiProperty({ + type: () => FormulaDTO, + required: false, + isArray: true + }) + @IsOptional() + @IsArray() + formulas?: FormulaDTO[]; + + @ApiProperty({ + type: () => VcDocumentDTO, + required: false, + }) + @IsOptional() + @IsObject() + document?: VcDocumentDTO; + + @ApiProperty({ + type: () => VcDocumentDTO, + required: false, + isArray: true, + }) + @IsOptional() + @IsArray() + relationships?: VcDocumentDTO[]; + + @ApiProperty({ + type: () => SchemaDTO, + required: false, + isArray: true, + }) + @IsOptional() + @IsArray() + schemas?: SchemaDTO[]; +} \ No newline at end of file diff --git a/api-gateway/src/middlewares/validation/schemas/index.ts b/api-gateway/src/middlewares/validation/schemas/index.ts index 23aad8015f..b1f8a3fa8f 100644 --- a/api-gateway/src/middlewares/validation/schemas/index.ts +++ b/api-gateway/src/middlewares/validation/schemas/index.ts @@ -31,4 +31,5 @@ export * from './profiles.dto.js' export * from './worker-tasks.dto.js' export * from './policy-statistics.dto.js' export * from './schema-rules.dto.js' -export * from './policy-labels.dto.js' \ No newline at end of file +export * from './policy-labels.dto.js' +export * from './formulas.dto.js' \ No newline at end of file diff --git a/api-gateway/src/middlewares/validation/schemas/schema-rules.dto.ts b/api-gateway/src/middlewares/validation/schemas/schema-rules.dto.ts index 012b64843e..c519241574 100644 --- a/api-gateway/src/middlewares/validation/schemas/schema-rules.dto.ts +++ b/api-gateway/src/middlewares/validation/schemas/schema-rules.dto.ts @@ -125,6 +125,44 @@ export class SchemaRuleRelationshipsDTO { schemas?: SchemaDTO[]; } +export class SchemaRuleOptionsDTO { + @ApiProperty({ + type: 'string', + required: false, + example: Examples.DB_ID + }) + @IsOptional() + @IsString() + policyId?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.DB_ID + }) + @IsOptional() + @IsString() + schemaId?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.DB_ID + }) + @IsOptional() + @IsString() + documentId?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.DB_ID + }) + @IsOptional() + @IsString() + parentId?: string; +} + @ApiExtraModels(SchemaRuleDTO, VcDocumentDTO) export class SchemaRuleDataDTO { @ApiProperty({ @@ -149,6 +187,6 @@ export class SchemaRuleDataDTO { isArray: true, }) @IsOptional() - @IsObject() - relationships?: VcDocumentDTO; + @IsArray() + relationships?: VcDocumentDTO[]; } \ No newline at end of file diff --git a/common/src/database-modules/database-server.ts b/common/src/database-modules/database-server.ts index f4b0c1f240..ce2d00b3fc 100644 --- a/common/src/database-modules/database-server.ts +++ b/common/src/database-modules/database-server.ts @@ -8,7 +8,6 @@ import { AggregateVC, ApprovalDocument as ApprovalDocumentCollection, Artifact as ArtifactCollection, - Artifact, ArtifactChunk as ArtifactChunkCollection, AssignEntity, BlockCache, @@ -19,6 +18,7 @@ import { DryRun, DryRunFiles, ExternalDocument, + Formula, MintRequest, MintTransaction, MultiDocuments, @@ -70,10 +70,55 @@ export class DatabaseServer extends AbstractDatabaseServer { /** * Documents handling chunk size */ - private static readonly DOCUMENTS_HANDLING_CHUNK_SIZE = process.env - .DOCUMENTS_HANDLING_CHUNK_SIZE - ? parseInt(process.env.DOCUMENTS_HANDLING_CHUNK_SIZE, 10) - : 500; + private static readonly DOCUMENTS_HANDLING_CHUNK_SIZE = process.env.DOCUMENTS_HANDLING_CHUNK_SIZE + ? parseInt(process.env.DOCUMENTS_HANDLING_CHUNK_SIZE, 10) + : 500; + + /** + * Dry-run + * @private + */ + private dryRun: string = null; + /** + * Dry-run + * @private + */ + private systemMode: boolean = false; + /** + * Dry-run + * @private + */ + private readonly classMap: Map = new Map(); + + constructor(dryRun: string = null) { + super(); + this.dryRun = dryRun || null; + + this.classMap.set(BlockCache, 'BlockCache'); + this.classMap.set(BlockState, 'BlockState'); + this.classMap.set(VcDocumentCollection, 'VcDocumentCollection'); + this.classMap.set(VpDocumentCollection, 'VpDocumentCollection'); + this.classMap.set(DidDocumentCollection, 'DidDocumentCollection'); + this.classMap.set(SchemaCollection, 'SchemaCollection'); + this.classMap.set(DocumentState, 'DocumentState'); + this.classMap.set(Policy, 'Policy'); + this.classMap.set(AggregateVC, 'AggregateVC'); + this.classMap.set(ApprovalDocumentCollection, 'ApprovalDocumentCollection'); + this.classMap.set(TokenCollection, 'TokenCollection'); + this.classMap.set(TopicCollection, 'TopicCollection'); + this.classMap.set(DryRun, 'DryRun'); + this.classMap.set(PolicyRolesCollection, 'PolicyRolesCollection'); + this.classMap.set(PolicyInvitations, 'PolicyInvitations'); + this.classMap.set(MultiDocuments, 'MultiDocuments'); + this.classMap.set(SplitDocuments, 'SplitDocuments'); + this.classMap.set(Tag, 'Tag'); + this.classMap.set(TagCache, 'TagCache'); + this.classMap.set(ExternalDocument, 'ExternalDocument'); + this.classMap.set(PolicyCategory, 'PolicyCategories'); + this.classMap.set(PolicyProperty, 'PolicyProperties'); + this.classMap.set(MintRequest, 'MintRequest'); + this.classMap.set(MintTransaction, 'MintTransaction'); + } /** * Add dry run id @@ -106,4031 +151,4044 @@ export class DatabaseServer extends AbstractDatabaseServer { } /** - * Grid fs connect + * Add dry run id + * @param entityClass + * @param item */ - public static connectGridFS() { - DataBaseHelper.connectGridFS(); + private addDryRunId(entityClass: new () => T, item: unknown): unknown | unknown[] { + return DatabaseServer.addDryRunId( + item, this.dryRun, this.classMap.get(entityClass), this.systemMode + ); } /** - * Set Dry Run id - * @param id + * Create much data + * @param entityClass Entity class + * @param item Item + * @param amount Amount */ - public static async setSystemMode(dryRunId: string, systemMode: boolean): Promise { - const items = await new DataBaseHelper(DryRun).find({dryRunId}); - for (const item of items) { - item.systemMode = systemMode; + private async createMuchData(entityClass: new () => T, item: Partial & { id: string, _id: string }, amount: number): Promise { + const naturalCount = Math.floor((amount / DatabaseServer.DOCUMENTS_HANDLING_CHUNK_SIZE)); + const restCount = (amount % DatabaseServer.DOCUMENTS_HANDLING_CHUNK_SIZE); + + if (this.dryRun) { + this.addDryRunId(entityClass, item); + for (let i = 0; i < naturalCount; i++) { + await new DataBaseHelper(DryRun).createMuchData(item, DatabaseServer.DOCUMENTS_HANDLING_CHUNK_SIZE); + } + await new DataBaseHelper(DryRun).createMuchData(item, restCount); + } else { + for (let i = 0; i < naturalCount; i++) { + await new DataBaseHelper(entityClass).createMuchData(item, DatabaseServer.DOCUMENTS_HANDLING_CHUNK_SIZE); + } + await new DataBaseHelper(entityClass).createMuchData(item, restCount); } - await new DataBaseHelper(DryRun).update(items); } /** - * Create savepoint - * @param dryRunId - * @param systemMode + * Find data by aggregation + * @param entityClass Entity class + * @param aggregation aggregate filter + * @returns */ - public static async createSavepoint(dryRunId: string): Promise { - const limit = {limit: DatabaseServer.DOCUMENTS_HANDLING_CHUNK_SIZE}; - const amount = await new DataBaseHelper(DryRun).count({dryRunId}); - const naturalCount = Math.floor(amount / DatabaseServer.DOCUMENTS_HANDLING_CHUNK_SIZE); - for (let i = 0; i < naturalCount; i++) { - const items = await new DataBaseHelper(DryRun).find({dryRunId}, limit); - for (const item of items) { - item.savepoint = true; - } - await new DataBaseHelper(DryRun).update(items); - } - const restItems = await new DataBaseHelper(DryRun).find({dryRunId}); - for (let item of restItems) { - item.savepoint = true; + public async aggregate(entityClass: new () => T, aggregation: FilterObject[]): Promise { + if (this.dryRun) { + const dryRunClass = this.classMap.get(entityClass); + + return await new DataBaseHelper(DryRun).aggregateDryRan(aggregation, this.dryRun, dryRunClass) as unknown as T[]; + } else { + return await new DataBaseHelper(entityClass).aggregate(aggregation); } - await new DataBaseHelper(DryRun).update(restItems); + } - // const files = await new DataBaseHelper(DryRunFiles).find({ policyId: dryRunId }); - // await new DataBaseHelper(DryRunFiles).remove(files); + /** + * Assign entity + * @param type + * @param entityId + * @param assigned + * @param did + * @param owner + */ + public static async assignEntity( + type: AssignedEntityType, + entityId: string, + assigned: boolean, + did: string, + owner: string + ): Promise { + const item = new DataBaseHelper(AssignEntity).create({ type, entityId, assigned, did, owner }); + return await new DataBaseHelper(AssignEntity).save(item); } /** - * Restore savepoint + * Check User In Group + * @param group + * + * @virtual + */ + public async checkUserInGroup(group: { policyId: string, did: string, owner: string, uuid: string }): Promise { + return await this.findOne(PolicyRolesCollection, { + policyId: group.policyId, + did: group.did, + owner: group.owner, + uuid: group.uuid + }); + } + + /** + * Clear Dry Run table + * @param systemMode + */ + public async clear(all: boolean) { + await DatabaseServer.clearDryRun(this.dryRun, all); + } + + /** + * Clear Dry Run table * @param dryRunId * @param systemMode */ - public static async restoreSavepoint(dryRunId: string): Promise { - const limit = {limit: DatabaseServer.DOCUMENTS_HANDLING_CHUNK_SIZE}; - const amount = await new DataBaseHelper(DryRun).count({dryRunId, savepoint: {$exists: false}}); + public static async clearDryRun(dryRunId: string, all: boolean): Promise { + const filter = all ? { dryRunId } : { dryRunId, systemMode: { $ne: true } }; + const limit = { limit: DatabaseServer.DOCUMENTS_HANDLING_CHUNK_SIZE }; + const amount = await new DataBaseHelper(DryRun).count(filter); const naturalCount = Math.floor(amount / DatabaseServer.DOCUMENTS_HANDLING_CHUNK_SIZE); for (let i = 0; i < naturalCount; i++) { - const items = await new DataBaseHelper(DryRun).find({dryRunId, savepoint: {$exists: false}}, limit); + const items = await new DataBaseHelper(DryRun).find(filter, limit); await new DataBaseHelper(DryRun).remove(items); } - const restItems = await new DataBaseHelper(DryRun).find({dryRunId, savepoint: {$exists: false}}); + const restItems = await new DataBaseHelper(DryRun).find(filter); await new DataBaseHelper(DryRun).remove(restItems); - // const files = await new DataBaseHelper(DryRunFiles).find({ policyId: dryRunId }); - // await new DataBaseHelper(DryRunFiles).remove(files); + const files = await new DataBaseHelper(DryRunFiles).find({ policyId: dryRunId }); + await new DataBaseHelper(DryRunFiles).remove(files); } /** - * Get schemas - * @param filters + * Clear policy cache data + * @param cachePolicyId Cache policy id */ - public static async getSchema(filters?: FilterObject | string): Promise { - return await new DataBaseHelper(SchemaCollection).findOne(filters); + public static async clearPolicyCacheData(cachePolicyId: string) { + const amount = await new DataBaseHelper(PolicyCacheData).count({ + cachePolicyId + }); + const naturalCount = Math.floor( + amount / DatabaseServer.DOCUMENTS_HANDLING_CHUNK_SIZE + ); + for (let i = 0; i < naturalCount; i++) { + const items = await new DataBaseHelper(PolicyCacheData).find( + { cachePolicyId }, + { limit: DatabaseServer.DOCUMENTS_HANDLING_CHUNK_SIZE } + ); + await new DataBaseHelper(PolicyCacheData).remove( + items.map((item) => { + item._id = item.newId; + item.id = item.newId.toString(); + return item; + }) + ); + } + const restItems = await new DataBaseHelper(PolicyCacheData).find({ + cachePolicyId + }); + await new DataBaseHelper(PolicyCacheData).remove( + restItems.map((item) => { + item._id = item.newId; + item.id = item.newId.toString(); + return item; + }) + ); } /** - * Create Statistic - * @param statistic + * Clear policy caches + * @param filters Filters */ - public static async createStatistic( - statistic: FilterObject - ): Promise { - const item = new DataBaseHelper(PolicyStatistic).create(statistic); - return await new DataBaseHelper(PolicyStatistic).save(item); + public static async clearPolicyCaches(filters?: FilterObject | string): Promise { + const policyCaches = await new DataBaseHelper(PolicyCache).find( + filters + ); + if (!policyCaches) { + return; + } + for (const policyCache of policyCaches) { + const cachePolicyId = policyCache.id; + await new DataBaseHelper(PolicyCache).remove(policyCache); + await DatabaseServer.clearPolicyCacheData(cachePolicyId); + } } /** - * Get Statistics - * @param filters - * @param options + * Set MongoDriver + * @param db */ - public static async getStatisticsAndCount( - filters?: FilterObject, - options?: FindOptions - ): Promise<[PolicyStatistic[], number]> { - return await new DataBaseHelper(PolicyStatistic).findAndCount(filters, options); + public static connectBD(db: MikroORM): void { + DataBaseHelper.connectBD(db); } /** - * Get Statistic By ID - * @param id + * Overriding the count method + * @param entityClass + * @param filters + * @param options */ - public static async getStatisticById(id: string): Promise { - return await new DataBaseHelper(PolicyStatistic).findOne(id); + public async count(entityClass: new () => T, filters: FilterQuery, options?: FindOptions): Promise { + if (this.dryRun) { + + const _filters = { + ...filters as FilterObject, + dryRunId: this.dryRun, + dryRunClass: this.classMap.get(entityClass) + }; + + return await new DataBaseHelper(DryRun).count(_filters, options); + } else { + return await new DataBaseHelper(entityClass).count(filters, options); + } } /** - * Get Statistic - * @param filters + * Get MultiPolicyTransaction count + * @param policyId */ - public static async getStatistic(filters: FilterQuery): Promise { - return await new DataBaseHelper(PolicyStatistic).findOne(filters); + public static async countMultiPolicyTransactions(policyId: string): Promise { + return await new DataBaseHelper(MultiPolicyTransaction).count({ policyId, status: 'Waiting' }); } /** - * Delete Statistic - * @param statistic + * Overriding the create method + * @param entityClass + * @param item */ - public static async removeStatistic(statistic: PolicyStatistic): Promise { - return await new DataBaseHelper(PolicyStatistic).remove(statistic); + public create(entityClass: new () => T, item: Partial): T { + if (this.dryRun) { + return (new DataBaseHelper(DryRun).create(item)) as unknown as T; + } else { + return new DataBaseHelper(entityClass).create(item); + } } /** - * Get Statistics - * @param filters - * @param options + * Create Aggregate Documents + * @param item + * @param blockId + * + * @virtual */ - public static async getStatistics( - filters?: FilterQuery, - options?: unknown - ): Promise { - return await new DataBaseHelper(PolicyStatistic).find(filters, options); + public async createAggregateDocuments(item: VcDocumentCollection & { blockId: string }, blockId: string): Promise { + item.blockId = blockId; + const newVC = this.create(AggregateVC, item); + await this.save(AggregateVC, newVC); } /** - * Update Statistic - * @param row + * Get schema + * @param item */ - public static async updateStatistic(row: PolicyStatistic): Promise { - return await new DataBaseHelper(PolicyStatistic).update(row); + public static async createAndSaveSchema(item: Partial): Promise { + return await new DataBaseHelper(SchemaCollection).save(item); } /** - * Get documents - * @param filters - * @param options + * Create External Topic + * @param row + * + * @virtual */ - public static async getStatisticDocumentsAndCount( - filters?: FilterObject, - options?: FindOptions - ): Promise<[VcDocumentCollection[], number]> { - return await new DataBaseHelper(VcDocumentCollection).findAndCount(filters, options); + public async createExternalTopic(row: unknown): Promise { + const item = this.create(ExternalDocument, row); + return await this.save(ExternalDocument, item); } /** - * Get documents - * @param filters - * @param options + * Create invite token + * @param policyId + * @param uuid + * @param owner + * @param role + * + * @virtual */ - public static async getStatisticDocuments( - filters?: FilterQuery, - options?: unknown - ): Promise { - return await new DataBaseHelper(VcDocumentCollection).find(filters, options); + public async createInviteToken(policyId: string, uuid: string, owner: string, role: string): Promise { + const doc = this.create(PolicyInvitations, { + uuid, + policyId, + owner, + role, + active: true + }); + await this.save(PolicyInvitations, doc); + return doc.id.toString(); } /** - * Get document - * @param filters - * @param options + * Create mint transactions + * @param transaction Transaction + * @param amount Amount */ - public static async getStatisticDocument( - filters?: FilterQuery, - options?: unknown - ): Promise { - return await new DataBaseHelper(VcDocumentCollection).findOne(filters, options); + public async createMintTransactions(transaction: Partial, amount: number): Promise { + await this.createMuchData(MintTransaction, transaction as Partial & { id: string, _id: string }, amount); } /** - * Create Statistic - * @param assessment + * Create createModules + * @param module */ - public static async createStatisticAssessment( - assessment: FilterObject - ): Promise { - const item = new DataBaseHelper(PolicyStatisticDocument).create(assessment); - return await new DataBaseHelper(PolicyStatisticDocument).save(item); + public static async createModules(module: PolicyModule): Promise { + 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); } /** - * Get statistic assessment - * @param filters + * Create Multi Policy object + * @param multiPolicy + * @returns MultiPolicy */ - public static async getStatisticAssessment( - filters: FilterQuery - ): Promise { - return await new DataBaseHelper(PolicyStatisticDocument).findOne(filters); + public static createMultiPolicy(multiPolicy: MultiPolicy): MultiPolicy { + return new DataBaseHelper(MultiPolicy).create(multiPolicy); } /** - * Get statistic assessments - * @param filters - * @param options + * Create MultiPolicyTransaction + * @param transaction */ - public static async getStatisticAssessmentsAndCount( - filters?: FilterObject, - options?: FindOptions - ): Promise<[PolicyStatisticDocument[], number]> { - return await new DataBaseHelper(PolicyStatisticDocument).findAndCount(filters, options); + public static async createMultiPolicyTransaction(transaction: FilterObject): Promise { + const item = new DataBaseHelper(MultiPolicyTransaction).create(transaction); + return await new DataBaseHelper(MultiPolicyTransaction).save(item); } /** - * Get statistic assessment count - * @param filters + * Create policy + * @param data */ - public static async getStatisticAssessmentCount( - filters?: FilterObject - ): Promise { - return await new DataBaseHelper(PolicyStatisticDocument).count(filters); + public static createPolicy(data: Partial): Policy { + if (!data.config) { + data.config = { + 'id': GenerateUUIDv4(), + 'blockType': 'interfaceContainerBlock', + 'permissions': [ + 'ANY_ROLE' + ] + }; + } + const model = new DataBaseHelper(Policy).create(data); + return model; } /** - * Create Schema Rule - * @param rule + * Assign entity + * @param config + * @param buffer */ - public static async createSchemaRule( - rule: FilterObject - ): Promise { - const item = new DataBaseHelper(SchemaRule).create(rule); - return await new DataBaseHelper(SchemaRule).save(item); + public static async createPolicyTest(config: { [key: string]: unknown }, buffer: Buffer): Promise { + const file = await DatabaseServer.saveFile(GenerateUUIDv4(), buffer); + const item = new DataBaseHelper(PolicyTest).create({ ...config, file }); + return await new DataBaseHelper(PolicyTest).save(item); } /** - * Get Schema Rule - * @param filters - * @param options + * Create Record + * @param record */ - public static async getSchemaRulesAndCount( - filters?: FilterObject, - options?: FindOptions - ): Promise<[SchemaRule[], number]> { - return await new DataBaseHelper(SchemaRule).findAndCount(filters, options); + public static async createRecord(record: FilterObject): Promise { + const item = new DataBaseHelper(Record).create(record); + return await new DataBaseHelper(Record).save(item); } /** - * Get Schema Rule - * @param filters - * @param options + * Create Residue object + * @param policyId + * @param blockId + * @param userId + * @param value + * @param document */ - public static async getSchemaRules( - filters?: FilterObject, - options?: unknown - ): Promise { - return await new DataBaseHelper(SchemaRule).find(filters, options); + public createResidue( + policyId: string, + blockId: string, + userId: string, + value: unknown, + document: unknown + ): SplitDocuments { + return this.create(SplitDocuments, { + policyId, + blockId, + userId, + value, + document + }); } /** - * Get Schema Rule By ID - * @param id + * Get schema + * @param item */ - public static async getSchemaRuleById(id: string): Promise { - return await new DataBaseHelper(SchemaRule).findOne(id); + public static createSchema(item: Partial): SchemaCollection { + return new DataBaseHelper(SchemaCollection).create(item); } /** - * Update Schema Rule - * @param rule + * Create tag + * @param tag */ - public static async updateSchemaRule(rule: SchemaRule): Promise { - return await new DataBaseHelper(SchemaRule).update(rule); + public async createTag(tag: Tag): Promise { + const item = this.create(Tag, tag); + return await this.save(Tag, item); } /** - * Delete Schema Rule - * @param rule + * Create tag + * @param tag */ - public static async removeSchemaRule(rule: SchemaRule): Promise { - return await new DataBaseHelper(SchemaRule).remove(rule); + public static async createTag(tag: FilterObject): Promise { + const item = new DataBaseHelper(Tag).create(tag); + return await new DataBaseHelper(Tag).save(item); } /** - * Create Policy Label - * @param label + * Create tag cache + * @param tag */ - public static async createPolicyLabel( - label: FilterObject - ): Promise { - const item = new DataBaseHelper(PolicyLabel).create(label); - return await new DataBaseHelper(PolicyLabel).save(item); + public async createTagCache(tag: Partial): Promise { + const item = this.create(TagCache, tag); + return await this.save(TagCache, item); } /** - * Get Policy Label - * @param filters - * @param options + * Create tag cache + * @param tag */ - public static async getPolicyLabelsAndCount( - filters?: FilterObject, - options?: FindOptions - ): Promise<[PolicyLabel[], number]> { - return await new DataBaseHelper(PolicyLabel).findAndCount(filters, options); + public static async createTagCache(tag: FilterObject): Promise { + const item = new DataBaseHelper(TagCache).create(tag); + return await new DataBaseHelper(TagCache).save(item); } /** - * Get Policy Label - * @param filters - * @param options - */ - public static async getPolicyLabels( - filters?: FilterObject, - options?: unknown - ): Promise { - return await new DataBaseHelper(PolicyLabel).find(filters, options); + * Create Theme + * @param theme + */ + public static async createTheme(theme: FilterObject): Promise { + const item = new DataBaseHelper(Theme).create(theme); + return await new DataBaseHelper(Theme).save(item); } /** - * Get Policy Label By ID - * @param id + * Create Token + * @param token + * @returns */ - public static async getPolicyLabelById(id: string): Promise { - return await new DataBaseHelper(PolicyLabel).findOne(id); + public async createToken(token: unknown): Promise { + const newToken = this.create(TokenCollection, token); + return await this.save(TokenCollection, newToken); } /** - * Update Policy Label - * @param label + * Create Tool + * @param tool */ - public static async updatePolicyLabel(label: PolicyLabel): Promise { - return await new DataBaseHelper(PolicyLabel).update(label); + public static async createTool(tool: PolicyTool): Promise { + const item = new DataBaseHelper(PolicyTool).create(tool); + return await new DataBaseHelper(PolicyTool).save(item); } /** - * Delete Policy Label - * @param label + * Create Virtual User + * @param policyId + * @param username + * @param did + * @param hederaAccountId + * @param hederaAccountKey + * @param active + * + * @virtual */ - public static async removePolicyLabel(label: PolicyLabel): Promise { - return await new DataBaseHelper(PolicyLabel).remove(label); + public static async createVirtualUser( + policyId: string, + username: string, + did: string, + hederaAccountId: string, + hederaAccountKey: string, + active: boolean, + systemMode?: boolean + ): Promise { + await new DataBaseHelper(DryRun).save(DatabaseServer.addDryRunId({ + did, + username, + hederaAccountId, + active + }, policyId, 'VirtualUsers', !!systemMode)); + + if (hederaAccountKey) { + await new DataBaseHelper(DryRun).save(DatabaseServer.addDryRunId({ + did, + type: did, + hederaAccountKey + }, policyId, 'VirtualKey', !!systemMode)); + } } /** - * Create Label Document - * @param document + * Create Virtual User + * @param policyId + * @param username + * @param did + * @param hederaAccountId + * @param hederaAccountKey + * @param active + * + * @virtual */ - public static async createLabelDocument( - document: FilterObject - ): Promise { - const item = new DataBaseHelper(PolicyLabelDocument).create(document); - return await new DataBaseHelper(PolicyLabelDocument).save(item); + public async createVirtualUser( + username: string, + did: string, + hederaAccountId: string, + hederaAccountKey: string, + active: boolean = false + ): Promise { + await DatabaseServer.createVirtualUser( + this.dryRun, + username, + did, + hederaAccountId, + hederaAccountKey, + active, + this.systemMode + ); } /** - * Get statistic assessments + * Overriding the create method + * @param entityClass * @param filters - * @param options */ - public static async getLabelDocumentsAndCount( - filters?: FilterObject, - options?: FindOptions - ): Promise<[PolicyLabelDocument[], number]> { - return await new DataBaseHelper(PolicyLabelDocument).findAndCount(filters, options); + public deleteEntity(entityClass: new () => T, filters: FilterObject | string | ObjectId): Promise { + return new DataBaseHelper(entityClass).delete(filters); } /** - * Get statistic assessment - * @param filters + * Delete user + * @param group + * + * @virtual */ - public static async getLabelDocument( - filters: FilterQuery - ): Promise { - return await new DataBaseHelper(PolicyLabelDocument).findOne(filters); + public async deleteGroup(group: PolicyRolesCollection): Promise { + return await this.remove(PolicyRolesCollection, group); } /** - * Restore States + * Delete policy + * @param id Policy ID */ - public static async restoreStates(policyId: string): Promise { - const states = await new DataBaseHelper(BlockState).find({policyId}); - for (const state of states) { - state.blockState = state.savedState; - await new DataBaseHelper(BlockState).save(state); - } + public static async deletePolicy(id: string): Promise { + await new DataBaseHelper(Policy).delete({ id }); } /** - * Copy States + * Get policy tests + * @param policyId + * @param id + * @returns tests */ - public static async copyStates(policyId: string): Promise { - const states = await new DataBaseHelper(BlockState).find({policyId}); - for (const state of states) { - state.savedState = state.blockState; - await new DataBaseHelper(BlockState).save(state); - } + public static async deletePolicyTest(policyId: string, id: string): Promise { + await new DataBaseHelper(PolicyTest).delete({ id, policyId }); } /** - * Dry-run - * @private + * Get policy tests + * @param policyId + * + * @returns tests */ - private dryRun: string = null; + public static async deletePolicyTests(policyId: string): Promise { + await new DataBaseHelper(PolicyTest).delete({ policyId }); + } + /** - * Dry-run - * @private + * Delete schemas + * @param id */ - private systemMode: boolean = false; + public static async deleteSchemas(id: string): Promise { + await new DataBaseHelper(SchemaCollection).delete({ id }); + } + /** - * Dry-run - * @private + * Overriding the find method + * @param entityClass + * @param filters + * @param options */ - private readonly classMap: Map = new Map(); + public async find(entityClass: new () => T, filters: FilterQuery | string | ObjectId, options?: unknown): Promise { + if (this.dryRun) { - constructor(dryRun: string = null) { - super(); - this.dryRun = dryRun || null; + const _filters = { + ...filters as FilterObject, + dryRunId: this.dryRun, + dryRunClass: this.classMap.get(entityClass) + }; - this.classMap.set(BlockCache, 'BlockCache'); - this.classMap.set(BlockState, 'BlockState'); - this.classMap.set(VcDocumentCollection, 'VcDocumentCollection'); - this.classMap.set(VpDocumentCollection, 'VpDocumentCollection'); - this.classMap.set(DidDocumentCollection, 'DidDocumentCollection'); - this.classMap.set(SchemaCollection, 'SchemaCollection'); - this.classMap.set(DocumentState, 'DocumentState'); - this.classMap.set(Policy, 'Policy'); - this.classMap.set(AggregateVC, 'AggregateVC'); - this.classMap.set(ApprovalDocumentCollection, 'ApprovalDocumentCollection'); - this.classMap.set(TokenCollection, 'TokenCollection'); - this.classMap.set(TopicCollection, 'TopicCollection'); - this.classMap.set(DryRun, 'DryRun'); - this.classMap.set(PolicyRolesCollection, 'PolicyRolesCollection'); - this.classMap.set(PolicyInvitations, 'PolicyInvitations'); - this.classMap.set(MultiDocuments, 'MultiDocuments'); - this.classMap.set(SplitDocuments, 'SplitDocuments'); - this.classMap.set(Tag, 'Tag'); - this.classMap.set(TagCache, 'TagCache'); - this.classMap.set(ExternalDocument, 'ExternalDocument'); - this.classMap.set(PolicyCategory, 'PolicyCategories'); - this.classMap.set(PolicyProperty, 'PolicyProperties'); - this.classMap.set(MintRequest, 'MintRequest'); - this.classMap.set(MintTransaction, 'MintTransaction'); + return (await new DataBaseHelper(DryRun).find(_filters, options)) as unknown as T[]; + } else { + return await new DataBaseHelper(entityClass).find(filters, options); + } } /** - * Add dry run id + * Overriding the findAll method * @param entityClass - * @param item + * @param options */ - private addDryRunId(entityClass: new () => T, item: unknown): unknown | unknown[] { - return DatabaseServer.addDryRunId( - item, this.dryRun, this.classMap.get(entityClass), this.systemMode - ); + public async findAll(entityClass: new () => T, options?: FindAllOptions): Promise { + return await new DataBaseHelper(entityClass).findAll(options); } /** - * Create much data - * @param entityClass Entity class - * @param item Item - * @param amount Amount + * Overriding the findAndCount method + * @param entityClass + * @param filters + * @param options */ - private async createMuchData(entityClass: new () => T, item: Partial & {id: string, _id: string}, amount: number): Promise { - const naturalCount = Math.floor((amount / DatabaseServer.DOCUMENTS_HANDLING_CHUNK_SIZE)); - const restCount = (amount % DatabaseServer.DOCUMENTS_HANDLING_CHUNK_SIZE); + public async findAndCount(entityClass: new () => T, filters: FilterQuery | string | ObjectId, options?: unknown): Promise<[T[], number]> { + return await new DataBaseHelper(entityClass).findAndCount(filters, options); + } + /** + * Overriding the findOne method + * @param entityClass + * @param filters + * @param options + */ + public async findOne(entityClass: new () => T, filters: FilterQuery, options: unknown = {}): Promise { if (this.dryRun) { - this.addDryRunId(entityClass, item); - for (let i = 0; i < naturalCount; i++) { - await new DataBaseHelper(DryRun).createMuchData(item, DatabaseServer.DOCUMENTS_HANDLING_CHUNK_SIZE); + if (typeof filters === 'string') { + return (await new DataBaseHelper(DryRun).findOne(filters, options)) as unknown as T; } - await new DataBaseHelper(DryRun).createMuchData(item, restCount); + + const _filters = { + ...filters as FilterObject, + dryRunId: this.dryRun, + dryRunClass: this.classMap.get(entityClass) + }; + + return (await new DataBaseHelper(DryRun).findOne(_filters, options)) as unknown as T; } else { - for (let i = 0; i < naturalCount; i++) { - await new DataBaseHelper(entityClass).createMuchData(item, DatabaseServer.DOCUMENTS_HANDLING_CHUNK_SIZE); - } - await new DataBaseHelper(entityClass).createMuchData(item, restCount); + return await new DataBaseHelper(entityClass).findOne(filters, options); } } /** - * Find data by aggregation - * @param entityClass Entity class - * @param aggregation aggregate filter - * @returns - */ - public async aggregate(entityClass: new () => T, aggregation: FilterObject[]): Promise { - if (this.dryRun) { - const dryRunClass = this.classMap.get(entityClass); + * Get Active External Topic + * @param policyId + * @param blockId + * + * @virtual + */ + public async getActiveExternalTopics( + policyId: string, + blockId: string + ): Promise { + return await this.find(ExternalDocument, { + policyId: { $eq: policyId }, + blockId: { $eq: blockId }, + active: { $eq: true } + }); + } - return await new DataBaseHelper(DryRun).aggregateDryRan(aggregation, this.dryRun, dryRunClass) as unknown as T[]; - } else { - return await new DataBaseHelper(entityClass).aggregate(aggregation); + /** + * Get Active Group By User + * @param policyId + * @param did + * + * @virtual + */ + public async getActiveGroupByUser(policyId: string, did: string): Promise { + if (!did) { + return null; } + return await this.findOne(PolicyRolesCollection, { policyId, did, active: true }); } /** - * Assign entity - * @param type - * @param entityId - * @param assigned - * @param did - * @param owner + * Get Aggregate Documents + * @param policyId + * @param blockId + * @param filters + * + * @virtual */ - public static async assignEntity( - type: AssignedEntityType, - entityId: string, - assigned: boolean, - did: string, - owner: string - ): Promise { - const item = new DataBaseHelper(AssignEntity).create({type, entityId, assigned, did, owner}); - return await new DataBaseHelper(AssignEntity).save(item); + public async getAggregateDocuments( + policyId: string, + blockId: string, + filters: FilterObject = {}, + ): Promise { + return await this.find(AggregateVC, { policyId, blockId, ...filters }); } /** - * Check User In Group + * Get aggregate document by policy identifier + * @param policyId Policy identifier + * @returns Aggregate documents + */ + public async getAggregateDocumentsByPolicy( + policyId: string, + ): Promise { + return await this.find(AggregateVC, { policyId }); + } + + /** + * Get members + * * @param group * * @virtual */ - public async checkUserInGroup(group: {policyId: string, did: string, owner: string, uuid: string}): Promise { - return await this.findOne(PolicyRolesCollection, { + public async getAllMembersByGroup(group: PolicyRolesCollection): Promise { + if (!group.uuid) { + return []; + } + return await this.find(PolicyRolesCollection, { policyId: group.policyId, - did: group.did, - owner: group.owner, uuid: group.uuid }); } /** - * Clear Dry Run table - * @param systemMode + * Get all policy users + * @param policyId + * + * @virtual */ - public async clear(all: boolean) { - await DatabaseServer.clearDryRun(this.dryRun, all); + public async getAllPolicyUsers(policyId: string): Promise { + return await this.find(PolicyRolesCollection, { policyId, active: true }); } /** - * Clear Dry Run table - * @param dryRunId - * @param systemMode + * Get all policy users + * @param policyId + * @param uuid + * @param role + * + * @virtual */ - public static async clearDryRun(dryRunId: string, all: boolean): Promise { - const filter = all ? { dryRunId } : { dryRunId, systemMode: { $ne: true } }; - const limit = { limit: DatabaseServer.DOCUMENTS_HANDLING_CHUNK_SIZE }; - const amount = await new DataBaseHelper(DryRun).count(filter); - const naturalCount = Math.floor(amount / DatabaseServer.DOCUMENTS_HANDLING_CHUNK_SIZE); - for (let i = 0; i < naturalCount; i++) { - const items = await new DataBaseHelper(DryRun).find(filter, limit); - await new DataBaseHelper(DryRun).remove(items); - } - const restItems = await new DataBaseHelper(DryRun).find(filter); - await new DataBaseHelper(DryRun).remove(restItems); - - const files = await new DataBaseHelper(DryRunFiles).find({ policyId: dryRunId }); - await new DataBaseHelper(DryRunFiles).remove(files); + public async getAllUsersByRole(policyId: string, uuid: string, role: string): Promise { + return await this.find(PolicyRolesCollection, { policyId, uuid, role }); } /** - * Clear policy cache data - * @param cachePolicyId Cache policy id + * get document aggregation filters for analytics + * @param nameFilter + * @param uuid + * + * @returns Result */ - public static async clearPolicyCacheData(cachePolicyId: string) { - const amount = await new DataBaseHelper(PolicyCacheData).count({ - cachePolicyId - }); - const naturalCount = Math.floor( - amount / DatabaseServer.DOCUMENTS_HANDLING_CHUNK_SIZE - ); - for (let i = 0; i < naturalCount; i++) { - const items = await new DataBaseHelper(PolicyCacheData).find( - {cachePolicyId}, - {limit: DatabaseServer.DOCUMENTS_HANDLING_CHUNK_SIZE} - ); - await new DataBaseHelper(PolicyCacheData).remove( - items.map((item) => { - item._id = item.newId; - item.id = item.newId.toString(); - return item; - }) - ); - } - const restItems = await new DataBaseHelper(PolicyCacheData).find({ - cachePolicyId - }); - await new DataBaseHelper(PolicyCacheData).remove( - restItems.map((item) => { - item._id = item.newId; - item.id = item.newId.toString(); - return item; - }) - ); + public getAnalyticsDocAggregationFilters(nameFilter: string, uuid: string): unknown[] { + return DataBaseHelper.getAnalyticsDocAggregationFilters(nameFilter, uuid); } /** - * Clear policy caches + * Get and count policy cache data * @param filters Filters + * @param options Options + * @returns Policy cache data and count */ - public static async clearPolicyCaches(filters?: FilterObject | string): Promise { - const policyCaches = await new DataBaseHelper(PolicyCache).find( - filters + public static async getAndCountPolicyCacheData( + filters?: FilterObject, + options?: unknown + ): Promise<[PolicyCacheData[], number]> { + return await new DataBaseHelper(PolicyCacheData).findAndCount( + filters, + options ); - if (!policyCaches) { - return; - } - for (const policyCache of policyCaches) { - const cachePolicyId = policyCache.id; - await new DataBaseHelper(PolicyCache).remove(policyCache); - await DatabaseServer.clearPolicyCacheData(cachePolicyId); - } } /** - * Set MongoDriver - * @param db + * Get Approval Document + * @param filters + * + * @virtual */ - public static connectBD(db: MikroORM): void { - DataBaseHelper.connectBD(db); + public async getApprovalDocument(filters: FilterQuery): Promise { + return await this.findOne(ApprovalDocumentCollection, filters); } /** - * Overriding the count method - * @param entityClass + * Get Approval Documents * @param filters * @param options + * @param countResult + * @virtual */ - public async count(entityClass: new () => T, filters: FilterQuery, options?: FindOptions): Promise { - if (this.dryRun) { - - const _filters = { - ...filters as FilterObject, - dryRunId: this.dryRun, - dryRunClass: this.classMap.get(entityClass) - }; - - return await new DataBaseHelper(DryRun).count(_filters, options); - } else { - return await new DataBaseHelper(entityClass).count(filters, options); + public async getApprovalDocuments(filters: FilterObject, options?: FindOptions, countResult?: boolean): Promise { + if (countResult) { + return await this.count(ApprovalDocumentCollection, filters, options); } + return await this.find(ApprovalDocumentCollection, filters, options); } /** - * Get MultiPolicyTransaction count - * @param policyId + * Get Approval Documents + * @param aggregation + * @virtual */ - public static async countMultiPolicyTransactions(policyId: string): Promise { - return await new DataBaseHelper(MultiPolicyTransaction).count({policyId, status: 'Waiting'}); + public async getApprovalDocumentsByAggregation(aggregation: FilterObject[]): Promise { + return await this.aggregate(ApprovalDocumentCollection, aggregation) as ApprovalDocumentCollection[]; } /** - * Overriding the create method - * @param entityClass - * @param item + * Get Artifact + * @param filters Filters + * @returns Artifact */ - public create(entityClass: new () => T, item: Partial): T { - if (this.dryRun) { - return (new DataBaseHelper(DryRun).create(item)) as unknown as T; - } else { - return new DataBaseHelper(entityClass).create(item); - } + public static async getArtifact(filters?: FilterQuery): Promise { + return await new DataBaseHelper(ArtifactCollection).findOne(filters); } /** - * Create Aggregate Documents - * @param item - * @param blockId - * - * @virtual + * Get Artifact File By UUID + * @param uuid File UUID + * @returns Buffer */ - public async createAggregateDocuments(item: VcDocumentCollection & {blockId: string}, blockId: string): Promise { - item.blockId = blockId; - const newVC = this.create(AggregateVC, item); - await this.save(AggregateVC, newVC); + public static async getArtifactFileByUUID(uuid: string): Promise { + const artifactChunks = (await new DataBaseHelper(ArtifactChunkCollection).find({ + uuid + }, { + orderBy: { + number: 'ASC' + } + })).map(item => item.data.buffer); + return artifactChunks.length > 0 ? Buffer.concat(artifactChunks) : Buffer.from(''); } /** - * Get schema - * @param item + * Get Artifacts + * @param filters Filters + * @param options Options + * @returns Artifacts */ - public static async createAndSaveSchema(item: Partial): Promise { - return await new DataBaseHelper(SchemaCollection).save(item); + public static async getArtifacts(filters?: FilterQuery, options?: FindOptions): Promise { + return await new DataBaseHelper(ArtifactCollection).find(filters, options); } /** - * Create External Topic - * @param row - * - * @virtual + * Get Artifacts + * @param filters Filters + * @param options Options + * @returns Artifacts */ - public async createExternalTopic(row: unknown): Promise { - const item = this.create(ExternalDocument, row); - return await this.save(ExternalDocument, item); + public static async getArtifactsAndCount(filters?: FilterObject, options?: FindOptions): Promise<[ArtifactCollection[], number]> { + return await new DataBaseHelper(ArtifactCollection).findAndCount(filters, options); } /** - * Create invite token - * @param policyId - * @param uuid - * @param owner - * @param role - * - * @virtual + * Get assigned entities + * @param did + * @param type */ - public async createInviteToken(policyId: string, uuid: string, owner: string, role: string): Promise { - const doc = this.create(PolicyInvitations, { - uuid, - policyId, - owner, - role, - active: true - }); - await this.save(PolicyInvitations, doc); - return doc.id.toString(); + public static async getAssignedEntities(did: string, type?: AssignedEntityType): Promise { + if (type) { + return await (new DataBaseHelper(AssignEntity)).find({ type, did }); + } else { + return await (new DataBaseHelper(AssignEntity)).find({ did }); + } } /** - * Create mint transactions - * @param transaction Transaction - * @param amount Amount + * Check entity + * @param type + * @param entityId + * @param did */ - public async createMintTransactions(transaction: Partial, amount: number): Promise { - await this.createMuchData(MintTransaction, transaction as Partial & {id: string, _id: string}, amount); + public static async getAssignedEntity(type: AssignedEntityType, entityId: string, did: string): Promise { + return await (new DataBaseHelper(AssignEntity)).findOne({ type, entityId, did }); } /** - * Create createModules - * @param module + * get document aggregation filters for analytics + * @param nameFilterMap + * @param nameFilterAttributes + * @param existingAttributes + * + * @returns Result */ - public static async createModules(module: PolicyModule): Promise { - 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); + public getAttributesAggregationFilters(nameFilterMap: string, nameFilterAttributes: string, existingAttributes: string[] | []): unknown[] { + return DataBaseHelper.getAttributesAggregationFilters(nameFilterMap, nameFilterAttributes, existingAttributes); } /** - * Create Multi Policy object - * @param multiPolicy - * @returns MultiPolicy + * Get Block State + * @param {string} policyId - policy ID + * @param {string} blockId - block UUID + * @param {string} did - user DID + * @param {string} name - variable name + * + * @returns {BlockCache | null} - variable value + * @virtual */ - public static createMultiPolicy(multiPolicy: MultiPolicy): MultiPolicy { - return new DataBaseHelper(MultiPolicy).create(multiPolicy); + public async getBlockCache( + policyId: string, + blockId: string, + did: string, + name: string + ): Promise { + return await this.findOne(BlockCache, { + policyId, + blockId, + did, + name + }); } /** - * Create MultiPolicyTransaction - * @param transaction + * Get Block State + * @param policyId + * @param uuid + * + * @virtual */ - public static async createMultiPolicyTransaction(transaction: FilterObject): Promise { - const item = new DataBaseHelper(MultiPolicyTransaction).create(transaction); - return await new DataBaseHelper(MultiPolicyTransaction).save(item); + public async getBlockState(policyId: string, uuid: string): Promise { + return await this.findOne(BlockState, { + policyId, + blockId: uuid + }); } /** - * Create policy - * @param data + * Get block states + * @param policyId Policy identifier + * @returns Block states */ - public static createPolicy(data: Partial): Policy { - if (!data.config) { - data.config = { - 'id': GenerateUUIDv4(), - 'blockType': 'interfaceContainerBlock', - 'permissions': [ - 'ANY_ROLE' - ] - }; - } - const model = new DataBaseHelper(Policy).create(data); - return model; + public async getBlockStates(policyId: string): Promise { + return await this.find(BlockState, { + policyId + }); } /** - * Assign entity - * @param config - * @param buffer + * Get Contract by ID + * @param id */ - public static async createPolicyTest(config: {[key: string]: unknown}, buffer: Buffer): Promise { - const file = await DatabaseServer.saveFile(GenerateUUIDv4(), buffer); - const item = new DataBaseHelper(PolicyTest).create({...config, file}); - return await new DataBaseHelper(PolicyTest).save(item); + public static async getContractById(id: string | null): Promise { + return await new DataBaseHelper(ContractCollection).findOne(id); } /** - * Create Record - * @param record + * Get Did Document + * @param did */ - public static async createRecord(record: FilterObject): Promise { - const item = new DataBaseHelper(Record).create(record); - return await new DataBaseHelper(Record).save(item); + public async getDidDocument(did: string): Promise { + return await this.findOne(DidDocumentCollection, { did }); } /** - * Create Residue object - * @param policyId - * @param blockId - * @param userId - * @param value - * @param document + * Get Did Document + * @param did */ - public createResidue( - policyId: string, - blockId: string, - userId: string, - value: unknown, - document: unknown - ): SplitDocuments { - return this.create(SplitDocuments, { - policyId, - blockId, - userId, - value, - document - }); + public static async getDidDocument(did: string): Promise { + return await (new DataBaseHelper(DidDocumentCollection)).findOne({ did }); } /** - * Get schema - * @param item + * Get Did Documents + * @param filters + * + * @param options + * @param countResult + * @virtual */ - public static createSchema(item: Partial): SchemaCollection { - return new DataBaseHelper(SchemaCollection).create(item); + public async getDidDocuments(filters: FilterObject, options?: FindOptions, countResult?: boolean): Promise { + if (countResult) { + return await this.count(DidDocumentCollection, filters, options); + } + return await this.find(DidDocumentCollection, filters, options); } /** - * Create tag - * @param tag + * Get Did Documents + * @param aggregation + * @virtual */ - public async createTag(tag: Tag): Promise { - const item = this.create(Tag, tag); - return await this.save(Tag, item); + public async getDidDocumentsByAggregation(aggregation: FilterObject[]): Promise { + return await this.aggregate(DidDocumentCollection, aggregation) as DidDocumentCollection[]; } /** - * Create tag - * @param tag + * get document aggregation filters + * @param props + * + * @returns Result */ - public static async createTag(tag: FilterObject): Promise { - const item = new DataBaseHelper(Tag).create(tag); - return await new DataBaseHelper(Tag).save(item); + public getDocumentAggregationFilters(props: IGetDocumentAggregationFilters): void { + return DataBaseHelper.getDocumentAggregationFilters(props); } /** - * Create tag cache - * @param tag + * Get Document States + * @param filters + * @param options + * + * @virtual */ - public async createTagCache(tag: Partial): Promise { - const item = this.create(TagCache, tag); - return await this.save(TagCache, item); + public async getDocumentStates(filters: FilterObject, options?: FindOptions): Promise { + return await this.find(DocumentState, filters, options); } /** - * Create tag cache - * @param tag + * Get Dry Run id + * @returns Dry Run id */ - public static async createTagCache(tag: FilterObject): Promise { - const item = new DataBaseHelper(TagCache).create(tag); - return await new DataBaseHelper(TagCache).save(item); + public getDryRun(): string { + return this.dryRun; } /** - * Create Theme - * @param theme + * Get External Topic + * @param policyId + * @param blockId + * @param userId + * + * @virtual */ - public static async createTheme(theme: FilterObject): Promise { - const item = new DataBaseHelper(Theme).create(theme); - return await new DataBaseHelper(Theme).save(item); + public async getExternalTopic( + policyId: string, + blockId: string, + userId: string + ): Promise { + return await this.findOne(ExternalDocument, { + policyId: { $eq: policyId }, + blockId: { $eq: blockId }, + owner: { $eq: userId } + }); } /** - * Create Token - * @param token - * @returns + * Get Policies By Category and Name + * @param {string[]} categoryIds - category ids + * @param {string} text - part of category name + * + * @returns {Policy[]} - found policies */ - public async createToken(token: unknown): Promise { - const newToken = this.create(TokenCollection, token); - return await this.save(TokenCollection, newToken); + public static async getFilteredPolicies(categoryIds: string[], text: string): Promise { + const conditions = await GetConditionsPoliciesByCategories(categoryIds, text); + return await new DataBaseHelper(Policy).find({ $and: conditions }); } /** - * Create Tool - * @param tool + * Get Group By Name + * @param policyId + * @param groupName + * + * @virtual */ - public static async createTool(tool: PolicyTool): Promise { - const item = new DataBaseHelper(PolicyTool).create(tool); - return await new DataBaseHelper(PolicyTool).save(item); + public async getGlobalGroup(policyId: string, groupName: string): Promise { + return await this.findOne(PolicyRolesCollection, { policyId, groupName }); } /** - * Create Virtual User + * Get Group By UUID * @param policyId - * @param username - * @param did - * @param hederaAccountId - * @param hederaAccountKey - * @param active + * @param uuid * * @virtual */ - public static async createVirtualUser( - policyId: string, - username: string, - did: string, - hederaAccountId: string, - hederaAccountKey: string, - active: boolean, - systemMode?: boolean - ): Promise { - await new DataBaseHelper(DryRun).save(DatabaseServer.addDryRunId({ - did, - username, - hederaAccountId, - active - }, policyId, 'VirtualUsers', !!systemMode)); + public async getGroupByID(policyId: string, uuid: string): Promise { + return await this.findOne(PolicyRolesCollection, { policyId, uuid }); + } - if (hederaAccountKey) { - await new DataBaseHelper(DryRun).save(DatabaseServer.addDryRunId({ - did, - type: did, - hederaAccountKey - }, policyId, 'VirtualKey', !!systemMode)); - } + /** + * Get Group By UUID + * @param policyId + * @param uuid + * + * @returns Group + */ + public static async getGroupByID(policyId: string, uuid: string): Promise { + return await new DataBaseHelper(PolicyRolesCollection).findOne({ policyId, uuid }); } /** - * Create Virtual User + * Get Groups By User * @param policyId - * @param username * @param did - * @param hederaAccountId - * @param hederaAccountKey - * @param active + * @param options * * @virtual */ - public async createVirtualUser( - username: string, - did: string, - hederaAccountId: string, - hederaAccountKey: string, - active: boolean = false - ): Promise { - await DatabaseServer.createVirtualUser( - this.dryRun, - username, - did, - hederaAccountId, - hederaAccountKey, - active, - this.systemMode - ); + public async getGroupsByUser(policyId: string, did: string, options?: unknown): Promise { + if (!did) { + return []; + } + return await this.find(PolicyRolesCollection, { policyId, did }, options); } /** - * Overriding the create method - * @param entityClass - * @param filters + * Get Groups By User + * @param policyId + * @param did + * @param options + * + * @returns Groups */ - public deleteEntity(entityClass: new () => T, filters: FilterObject | string | ObjectId): Promise { - return new DataBaseHelper(entityClass).delete(filters); + public static async getGroupsByUser(policyId: string, did: string, options?: FindOptions): Promise { + if (!did) { + return []; + } + return await new DataBaseHelper(PolicyRolesCollection).find({ policyId, did }, options); } /** - * Delete user - * @param group - * - * @virtual + * Get policies + * @param filters */ - public async deleteGroup(group: PolicyRolesCollection): Promise { - return await this.remove(PolicyRolesCollection, group); + public static async getListOfPolicies(filters?: FilterObject): Promise { + const options = { + fields: [ + 'id', + 'uuid', + 'name', + 'version', + 'previousVersion', + 'description', + 'status', + 'creator', + 'owner', + 'topicId', + 'policyTag', + 'messageId', + 'codeVersion', + 'createDate' + ] as unknown as PopulatePath.ALL[], + limit: 100 + }; + return await new DataBaseHelper(Policy).find(filters, options); } /** - * Delete policy - * @param id Policy ID + * Get mint request minted serials + * @param mintRequestId Mint request identifier + * @returns Serials */ - public static async deletePolicy(id: string): Promise { - await new DataBaseHelper(Policy).delete({id}); + public async getMintRequestSerials(mintRequestId: string): Promise { + return await this.getTransactionsSerials(mintRequestId); } /** - * Get policy tests - * @param policyId - * @param id - * @returns tests + * Get mint request transfer serials + * @param mintRequestId Mint request identifier + * @returns Serials */ - public static async deletePolicyTest(policyId: string, id: string): Promise { - await new DataBaseHelper(PolicyTest).delete({id, policyId}); + public async getMintRequestTransferSerials(mintRequestId: string): Promise { + return await this.getTransactionsSerials(mintRequestId, MintTransactionStatus.SUCCESS); } /** - * Get policy tests - * @param policyId - * - * @returns tests + * Get mint transactions + * @param filters Filters + * @returns Mint transaction */ - public static async deletePolicyTests(policyId: string): Promise { - await new DataBaseHelper(PolicyTest).delete({policyId}); + public async getMintTransaction(filters: FilterObject): Promise { + return await this.findOne(MintTransaction, filters); } /** - * Delete schemas - * @param id + * Get mint transactions + * @param filters Filters + * @param options Options + * @returns Mint transactions */ - public static async deleteSchemas(id: string): Promise { - await new DataBaseHelper(SchemaCollection).delete({id}); + public async getMintTransactions(filters: FilterObject, options?: FindOptions): Promise { + return await this.find(MintTransaction, filters, options); } /** - * Overriding the find method - * @param entityClass + * Get Module * @param filters - * @param options */ - public async find(entityClass: new () => T, filters: FilterQuery | string | ObjectId, options?: unknown): Promise { - if (this.dryRun) { - - const _filters = { - ...filters as FilterObject, - dryRunId: this.dryRun, - dryRunClass: this.classMap.get(entityClass) - }; + public static async getModule(filters: FilterQuery): Promise { + return await new DataBaseHelper(PolicyModule).findOne(filters); + } - return (await new DataBaseHelper(DryRun).find(_filters, options)) as unknown as T[]; - } else { - return await new DataBaseHelper(entityClass).find(filters, options); - } + /** + * Get Module By ID + * @param id + */ + public static async getModuleById(id: string | null): Promise { + return await new DataBaseHelper(PolicyModule).findOne(id); } /** - * Overriding the findAll method - * @param entityClass - * @param options + * Get Module By UUID + * @param uuid */ - public async findAll(entityClass: new () => T, options?: FindAllOptions): Promise { - return await new DataBaseHelper(entityClass).findAll(options); + public static async getModuleByUUID(uuid: string): Promise { + return await new DataBaseHelper(PolicyModule).findOne({ uuid }); } /** - * Overriding the findAndCount method - * @param entityClass + * Get Modules * @param filters * @param options */ - public async findAndCount(entityClass: new () => T, filters: FilterQuery | string | ObjectId, options?: unknown): Promise<[T[], number]> { - return await new DataBaseHelper(entityClass).findAndCount(filters, options); + public static async getModules(filters?: FilterQuery, options?: FindOptions): Promise { + return await new DataBaseHelper(PolicyModule).find(filters, options); } /** - * Overriding the findOne method - * @param entityClass + * Get Modules * @param filters * @param options */ - public async findOne(entityClass: new () => T, filters: FilterQuery, options: unknown = {}): Promise { - if (this.dryRun) { - if (typeof filters === 'string') { - return (await new DataBaseHelper(DryRun).findOne(filters, options)) as unknown as T; - } - - const _filters = { - ...filters as FilterObject, - dryRunId: this.dryRun, - dryRunClass: this.classMap.get(entityClass) - }; - - return (await new DataBaseHelper(DryRun).findOne(_filters, options)) as unknown as T; - } else { - return await new DataBaseHelper(entityClass).findOne(filters, options); - } + public static async getModulesAndCount(filters?: FilterObject, options?: FindOptions): Promise<[PolicyModule[], number]> { + return await new DataBaseHelper(PolicyModule).findAndCount(filters, options); } /** - * Get Active External Topic - * @param policyId - * @param blockId - * - * @virtual + * Get Multi Policy link + * @param instanceTopicId + * @param owner + * @returns MultiPolicy */ - public async getActiveExternalTopics( - policyId: string, - blockId: string - ): Promise { - return await this.find(ExternalDocument, { - policyId: {$eq: policyId}, - blockId: {$eq: blockId}, - active: {$eq: true} - }); + public static async getMultiPolicy(instanceTopicId: string, owner: string): Promise { + return await new DataBaseHelper(MultiPolicy).findOne({ instanceTopicId, owner }); } /** - * Get Active Group By User + * Get MultiPolicyTransaction * @param policyId - * @param did - * - * @virtual + * @param owner */ - public async getActiveGroupByUser(policyId: string, did: string): Promise { - if (!did) { - return null; - } - return await this.findOne(PolicyRolesCollection, {policyId, did, active: true}); + public static async getMultiPolicyTransactions(policyId: string, user: string): Promise { + return await new DataBaseHelper(MultiPolicyTransaction).find({ policyId, user, status: 'Waiting' }); } /** - * Get Aggregate Documents - * @param policyId - * @param blockId - * @param filters + * Get MultiSign Statuses + * @param uuid + * @param documentId + * @param group * * @virtual */ - public async getAggregateDocuments( - policyId: string, - blockId: string, - filters: FilterObject = {}, - ): Promise { - return await this.find(AggregateVC, { policyId, blockId, ...filters }); + public async getMultiSignDocuments(uuid: string, documentId: string, group: string): Promise { + return await this.find(MultiDocuments, { + uuid: { $eq: uuid }, + documentId: { $eq: documentId }, + group: { $eq: group }, + userId: { $ne: 'Group' } + }); } /** - * Get aggregate document by policy identifier - * @param policyId Policy identifier - * @returns Aggregate documents + * Get multi sign documents by document identifiers + * @param documentIds Document identifiers + * @returns Multi sign documents */ - public async getAggregateDocumentsByPolicy( - policyId: string, - ): Promise { - return await this.find(AggregateVC, { policyId }); + public async getMultiSignDocumentsByDocumentIds( + documentIds: string[] + ): Promise { + return await this.find(MultiDocuments, { + documentId: { $in: documentIds }, + }); } /** - * Get members - * + * Get MultiSign Statuses by group + * @param uuid * @param group * * @virtual */ - public async getAllMembersByGroup(group: PolicyRolesCollection): Promise { - if (!group.uuid) { - return []; - } - return await this.find(PolicyRolesCollection, { - policyId: group.policyId, - uuid: group.uuid + public async getMultiSignDocumentsByGroup(uuid: string, group: string): Promise { + return await this.find(MultiDocuments, { + uuid: { $eq: uuid }, + group: { $eq: group }, + userId: { $eq: 'Group' }, + status: { $eq: 'NEW' } }); } /** - * Get all policy users - * @param policyId - * - * @virtual - */ - public async getAllPolicyUsers(policyId: string): Promise { - return await this.find(PolicyRolesCollection, {policyId, active: true}); - } - - /** - * Get all policy users - * @param policyId + * Get MultiSign Status by document or user * @param uuid - * @param role + * @param documentId + * @param userId * * @virtual */ - public async getAllUsersByRole(policyId: string, uuid: string, role: string): Promise { - return await this.find(PolicyRolesCollection, {policyId, uuid, role}); + public async getMultiSignStatus(uuid: string, documentId: string, userId: string = 'Group'): Promise { + return await this.findOne(MultiDocuments, { uuid, documentId, userId }); } /** - * get document aggregation filters for analytics - * @param nameFilter - * @param uuid - * - * @returns Result + * Get policies + * @param filters + * @param options */ - public getAnalyticsDocAggregationFilters(nameFilter: string, uuid: string): unknown[] { - return DataBaseHelper.getAnalyticsDocAggregationFilters(nameFilter, uuid); + public static async getPolicies(filters?: FilterObject, options?: unknown): Promise { + return await new DataBaseHelper(Policy).find(filters, options); } /** - * Get and count policy cache data - * @param filters Filters - * @param options Options - * @returns Policy cache data and count + * Get policies and count + * @param filters + * @param options */ - public static async getAndCountPolicyCacheData( - filters?: FilterObject, - options?: unknown - ): Promise<[PolicyCacheData[], number]> { - return await new DataBaseHelper(PolicyCacheData).findAndCount( - filters, - options - ); + public static async getPoliciesAndCount(filters: FilterObject, options?: FindOptions): Promise<[Policy[], number]> { + return await new DataBaseHelper(Policy).findAndCount(filters, options); } /** - * Get Approval Document - * @param filters + * Get Policy + * @param policyId * * @virtual */ - public async getApprovalDocument(filters: FilterQuery): Promise { - return await this.findOne(ApprovalDocumentCollection, filters); + public async getPolicy(policyId: string | null): Promise { + return await new DataBaseHelper(Policy).findOne(policyId); } + //Static + /** - * Get Approval Documents + * Get policy * @param filters - * @param options - * @param countResult - * @virtual */ - public async getApprovalDocuments(filters: FilterObject, options?: FindOptions, countResult?: boolean): Promise { - if (countResult) { - return await this.count(ApprovalDocumentCollection, filters, options); - } - return await this.find(ApprovalDocumentCollection, filters, options); + public static async getPolicy(filters: FilterObject): Promise { + return await new DataBaseHelper(Policy).findOne(filters); } /** - * Get Approval Documents - * @param aggregation - * @virtual + * Get policy by id + * @param policyId */ - public async getApprovalDocumentsByAggregation(aggregation: FilterObject[]): Promise { - return await this.aggregate(ApprovalDocumentCollection, aggregation) as ApprovalDocumentCollection[]; + public static async getPolicyById(policyId: string | null): Promise { + return await new DataBaseHelper(Policy).findOne(policyId); } /** - * Get Artifact - * @param filters Filters - * @returns Artifact + * Get policy by tag + * @param policyTag */ - public static async getArtifact(filters?: FilterQuery): Promise { - return await new DataBaseHelper(ArtifactCollection).findOne(filters); + public static async getPolicyByTag(policyTag: string): Promise { + return await new DataBaseHelper(Policy).findOne({ policyTag }); } /** - * Get Artifact File By UUID - * @param uuid File UUID - * @returns Buffer + * Get policy by uuid + * @param uuid */ - public static async getArtifactFileByUUID(uuid: string): Promise { - const artifactChunks = (await new DataBaseHelper(ArtifactChunkCollection).find({ - uuid - }, { - orderBy: { - number: 'ASC' - } - })).map(item => item.data.buffer); - return artifactChunks.length > 0 ? Buffer.concat(artifactChunks) : Buffer.from(''); + public static async getPolicyByUUID(uuid: string): Promise { + return await new DataBaseHelper(Policy).findOne({ uuid }); } /** - * Get Artifacts + * Get policy cache * @param filters Filters - * @param options Options - * @returns Artifacts + * @returns Policy cache */ - public static async getArtifacts(filters?: FilterQuery, options?: FindOptions): Promise { - return await new DataBaseHelper(ArtifactCollection).find(filters, options); + public static async getPolicyCache(filters: FilterObject): Promise { + return await new DataBaseHelper(PolicyCache).findOne(filters); } /** - * Get Artifacts + * Get policy cache data * @param filters Filters * @param options Options - * @returns Artifacts - */ - public static async getArtifactsAndCount(filters?: FilterObject, options?: FindOptions): Promise<[ArtifactCollection[], number]> { - return await new DataBaseHelper(ArtifactCollection).findAndCount(filters, options); - } - - /** - * Get assigned entities - * @param did - * @param type + * @returns Policy cache data */ - public static async getAssignedEntities(did: string, type?: AssignedEntityType): Promise { - if (type) { - return await (new DataBaseHelper(AssignEntity)).find({type, did}); - } else { - return await (new DataBaseHelper(AssignEntity)).find({did}); - } + public static async getPolicyCacheData( + filters?: FilterObject, + options?: FindOptions + ): Promise { + return await new DataBaseHelper(PolicyCacheData).find(filters, options); } /** - * Check entity - * @param type - * @param entityId - * @param did + * Get policy caches + * @param filters Filters + * @returns Policy caches */ - public static async getAssignedEntity(type: AssignedEntityType, entityId: string, did: string): Promise { - return await (new DataBaseHelper(AssignEntity)).findOne({type, entityId, did}); + public static async getPolicyCaches(filters?: FilterObject): Promise { + return await new DataBaseHelper(PolicyCache).find(filters); } /** - * get document aggregation filters for analytics - * @param nameFilterMap - * @param nameFilterAttributes - * @param existingAttributes + * Get Policy Categories * - * @returns Result + * @virtual */ - public getAttributesAggregationFilters(nameFilterMap: string, nameFilterAttributes: string, existingAttributes: string[] | []): unknown[] { - return DataBaseHelper.getAttributesAggregationFilters(nameFilterMap, nameFilterAttributes, existingAttributes); + public static async getPolicyCategories(): Promise { + return await new DataBaseHelper(PolicyCategory).find(PolicyCategory as FilterQuery); } /** - * Get Block State - * @param {string} policyId - policy ID - * @param {string} blockId - block UUID - * @param {string} did - user DID - * @param {string} name - variable name - * - * @returns {BlockCache | null} - variable value - * @virtual + * Get policy count + * @param filters */ - public async getBlockCache( - policyId: string, - blockId: string, - did: string, - name: string - ): Promise { - return await this.findOne(BlockCache, { - policyId, - blockId, - did, - name - }); + public static async getPolicyCount(filters: FilterObject): Promise { + return await new DataBaseHelper(Policy).count(filters); } /** - * Get Block State - * @param policyId - * @param uuid + * Get Policy Properties * * @virtual */ - public async getBlockState(policyId: string, uuid: string): Promise { - return await this.findOne(BlockState, { - policyId, - blockId: uuid - }); + public static async getPolicyProperties(): Promise { + return await new DataBaseHelper(PolicyProperty).find(PolicyProperty as FilterQuery); } /** - * Get block states - * @param policyId Policy identifier - * @returns Block states + * Get policy test + * @param policyId + * @param id + * @returns tests */ - public async getBlockStates(policyId: string): Promise { - return await this.find(BlockState, { - policyId - }); + public static async getPolicyTest(policyId: string, id: string): Promise { + return await new DataBaseHelper(PolicyTest).findOne({ id, policyId }); } /** - * Get Contract by ID - * @param id + * Get policy tests + * @param resultId + * + * @returns tests */ - public static async getContractById(id: string | null): Promise { - return await new DataBaseHelper(ContractCollection).findOne(id); + public static async getPolicyTestByRecord(resultId: string): Promise { + return await new DataBaseHelper(PolicyTest).findOne({ resultId }); } /** - * Get Did Document - * @param did + * Get policy tests + * @param policyId + * @returns tests */ - public async getDidDocument(did: string): Promise { - return await this.findOne(DidDocumentCollection, {did}); + public static async getPolicyTests(policyId: string): Promise { + return await new DataBaseHelper(PolicyTest).find({ policyId }); } /** - * Get Did Document - * @param did + * Get policy test + * @param policyId + * @param status + * @returns tests */ - public static async getDidDocument(did: string): Promise { - return await (new DataBaseHelper(DidDocumentCollection)).findOne({did}); + public static async getPolicyTestsByStatus(policyId: string, status: PolicyTestStatus): Promise { + return await new DataBaseHelper(PolicyTest).find({ status, policyId }); } /** - * Get Did Documents - * @param filters + * Get Publish Policies * - * @param options - * @param countResult * @virtual */ - public async getDidDocuments(filters: FilterObject, options?: FindOptions, countResult?: boolean): Promise { - if (countResult) { - return await this.count(DidDocumentCollection, filters, options); - } - return await this.find(DidDocumentCollection, filters, options); + public static async getPublishPolicies(): Promise { + return await new DataBaseHelper(Policy).find({ + status: { $eq: PolicyType.PUBLISH } + }); } /** - * Get Did Documents - * @param aggregation - * @virtual - */ - public async getDidDocumentsByAggregation(aggregation: FilterObject[]): Promise { - return await this.aggregate(DidDocumentCollection, aggregation) as DidDocumentCollection[]; - } - - /** - * get document aggregation filters - * @param props - * - * @returns Result - */ - public getDocumentAggregationFilters(props: IGetDocumentAggregationFilters): void { - return DataBaseHelper.getDocumentAggregationFilters(props); - } - - /** - * Get Document States - * @param filters - * @param options - * - * @virtual - */ - public async getDocumentStates(filters: FilterObject, options?: FindOptions): Promise { - return await this.find(DocumentState, filters, options); - } - - /** - * Get Dry Run id - * @returns Dry Run id + * Get Record + * @param filters Filters + * @param options Options + * @returns Record */ - public getDryRun(): string { - return this.dryRun; + public static async getRecord(filters?: FilterQuery, options?: FindOptions): Promise { + return await new DataBaseHelper(Record).find(filters, options); } /** - * Get External Topic + * Get Residue objects * @param policyId * @param blockId * @param userId - * - * @virtual */ - public async getExternalTopic( + public async getResidue( policyId: string, blockId: string, userId: string - ): Promise { - return await this.findOne(ExternalDocument, { - policyId: {$eq: policyId}, - blockId: {$eq: blockId}, - owner: {$eq: userId} + ): Promise { + return await this.find(SplitDocuments, { + policyId: { $eq: policyId }, + blockId: { $eq: blockId }, + userId: { $eq: userId } }); } /** - * Get Policies By Category and Name - * @param {string[]} categoryIds - category ids - * @param {string} text - part of category name - * - * @returns {Policy[]} - found policies + * Get retire pools + * @param tokenIds Token identifiers + * @returns Retire pools */ - public static async getFilteredPolicies(categoryIds: string[], text: string): Promise { - const conditions = await GetConditionsPoliciesByCategories(categoryIds, text); - return await new DataBaseHelper(Policy).find({$and: conditions}); + public static async getRetirePools(tokenIds: string[]): Promise { + return await new DataBaseHelper(RetirePool).find({ tokenIds: { $in: tokenIds } }); } /** - * Get Group By Name - * @param policyId - * @param groupName - * - * @virtual + * Get schema + * @param iri + * @param topicId */ - public async getGlobalGroup(policyId: string, groupName: string): Promise { - return await this.findOne(PolicyRolesCollection, { policyId, groupName }); + public async getSchemaByIRI(iri: string, topicId?: string): Promise { + if (topicId) { + return await new DataBaseHelper(SchemaCollection).findOne({ iri, topicId }); + } else { + return await new DataBaseHelper(SchemaCollection).findOne({ iri }); + } } /** - * Get Group By UUID - * @param policyId - * @param uuid - * - * @virtual + * Get schema + * @param id */ - public async getGroupByID(policyId: string, uuid: string): Promise { - return await this.findOne(PolicyRolesCollection, {policyId, uuid}); + public static async getSchemaById(id: string | null): Promise { + return await new DataBaseHelper(SchemaCollection).findOne(id); } /** - * Get Group By UUID - * @param policyId - * @param uuid - * - * @returns Group + * Get schema + * @param topicId + * @param entity */ - public static async getGroupByID(policyId: string, uuid: string): Promise { - return await new DataBaseHelper(PolicyRolesCollection).findOne({policyId, uuid}); + public async getSchemaByType(topicId: string, entity: SchemaEntity): Promise { + return await new DataBaseHelper(SchemaCollection).findOne({ + entity, + readonly: true, + topicId + }); } /** - * Get Groups By User - * @param policyId - * @param did - * @param options - * - * @virtual + * Get schema + * @param topicId + * @param entity */ - public async getGroupsByUser(policyId: string, did: string, options?: unknown): Promise { - if (!did) { - return []; - } - return await this.find(PolicyRolesCollection, { policyId, did }, options); + public static async getSchemaByType(topicId: string, entity: SchemaEntity): Promise { + return await new DataBaseHelper(SchemaCollection).findOne({ + entity, + readonly: true, + topicId + }); } /** - * Get Groups By User - * @param policyId - * @param did + * Get schemas + * @param filters * @param options - * - * @returns Groups */ - public static async getGroupsByUser(policyId: string, did: string, options?: FindOptions): Promise { - if (!did) { - return []; - } - return await new DataBaseHelper(PolicyRolesCollection).find({policyId, did}, options); + public static async getSchemas(filters?: FilterObject, options?: unknown): Promise { + return await new DataBaseHelper(SchemaCollection).find(filters, options); } /** - * Get policies + * Get schema * @param filters + * @param options */ - public static async getListOfPolicies(filters?: FilterObject): Promise { - const options = { - fields: [ - 'id', - 'uuid', - 'name', - 'version', - 'previousVersion', - 'description', - 'status', - 'creator', - 'owner', - 'topicId', - 'policyTag', - 'messageId', - 'codeVersion', - 'createDate' - ] as unknown as PopulatePath.ALL[], - limit: 100 - }; - return await new DataBaseHelper(Policy).find(filters, options); + public static async getSchemasAndCount(filters?: FilterObject, options?: FindOptions): Promise<[SchemaCollection[], number]> { + return await new DataBaseHelper(SchemaCollection).findAndCount(filters, options); } /** - * Get mint request minted serials - * @param mintRequestId Mint request identifier - * @returns Serials + * Get schema + * @param ids */ - public async getMintRequestSerials(mintRequestId: string): Promise { - return await this.getTransactionsSerials(mintRequestId); + public static async getSchemasByIds(ids: string[]): Promise { + return await new DataBaseHelper(SchemaCollection).find({ id: { $in: ids } }); } /** - * Get mint request transfer serials - * @param mintRequestId Mint request identifier - * @returns Serials + * Get schema + * @param filters */ - public async getMintRequestTransferSerials(mintRequestId: string): Promise { - return await this.getTransactionsSerials(mintRequestId, MintTransactionStatus.SUCCESS); + public static async getSchemasCount(filters?: FilterObject): Promise { + return await new DataBaseHelper(SchemaCollection).count(filters); } /** - * Get mint transactions - * @param filters Filters - * @returns Mint transaction + * Get split documents in policy + * @param policyId Policy identifier + * @returns Split documents */ - public async getMintTransaction(filters: FilterObject): Promise { - return await this.findOne(MintTransaction, filters); + public async getSplitDocumentsByPolicy( + policyId: string, + ): Promise { + return await this.find(SplitDocuments, { + policyId + }); } /** - * Get mint transactions - * @param filters Filters - * @param options Options - * @returns Mint transactions + * Get suggestions config + * @param did + * @returns config */ - public async getMintTransactions(filters: FilterObject, options?: FindOptions): Promise { - return await this.find(MintTransaction, filters, options); + public static async getSuggestionsConfig( + did: string + ): Promise { + return await new DataBaseHelper(SuggestionsConfig).findOne({ + user: did + }); } /** - * Get Module - * @param filters + * Get system schema + * @param entity */ - public static async getModule(filters: FilterQuery): Promise { - return await new DataBaseHelper(PolicyModule).findOne(filters); + public static async getSystemSchema(entity: SchemaEntity): Promise { + return await new DataBaseHelper(SchemaCollection).findOne({ + entity, + system: true, + active: true + }); } /** - * Get Module By ID - * @param id + * Get tag By UUID + * @param uuid */ - public static async getModuleById(id: string | null): Promise { - return await new DataBaseHelper(PolicyModule).findOne(id); + public async getTagById(uuid: string): Promise { + return await this.findOne(Tag, { uuid }); } /** - * Get Module By UUID + * Get tag By UUID * @param uuid */ - public static async getModuleByUUID(uuid: string): Promise { - return await new DataBaseHelper(PolicyModule).findOne({uuid}); + public static async getTagById(uuid: string): Promise { + return await new DataBaseHelper(Tag).findOne({ uuid }); } /** - * Get Modules + * Get tags * @param filters * @param options */ - public static async getModules(filters?: FilterQuery, options?: FindOptions): Promise { - return await new DataBaseHelper(PolicyModule).find(filters, options); + public async getTagCache(filters?: FilterObject, options?: FindOptions): Promise { + return await this.find(TagCache, filters, options); } /** - * Get Modules + * Get tags * @param filters * @param options */ - public static async getModulesAndCount(filters?: FilterObject, options?: FindOptions): Promise<[PolicyModule[], number]> { - return await new DataBaseHelper(PolicyModule).findAndCount(filters, options); + public static async getTagCache(filters?: FilterQuery, options?: FindOptions): Promise { + return await new DataBaseHelper(TagCache).find(filters, options); } /** - * Get Multi Policy link - * @param instanceTopicId - * @param owner - * @returns MultiPolicy + * Get tags + * @param filters + * @param options */ - public static async getMultiPolicy(instanceTopicId: string, owner: string): Promise { - return await new DataBaseHelper(MultiPolicy).findOne({instanceTopicId, owner}); + public async getTags(filters?: FilterQuery, options?: FindOptions): Promise { + return await this.find(Tag, filters, options); } /** - * Get MultiPolicyTransaction - * @param policyId - * @param owner + * Get tags + * @param filters + * @param options */ - public static async getMultiPolicyTransactions(policyId: string, user: string): Promise { - return await new DataBaseHelper(MultiPolicyTransaction).find({policyId, user, status: 'Waiting'}); + public static async getTags(filters?: FilterQuery, options?: unknown): Promise { + return await new DataBaseHelper(Tag).find(filters, options); } /** - * Get MultiSign Statuses - * @param uuid - * @param documentId - * @param group + * get tasks aggregation filters + * @param nameFilter + * @param processTimeout * - * @virtual + * @returns Result */ - public async getMultiSignDocuments(uuid: string, documentId: string, group: string): Promise { - return await this.find(MultiDocuments, { - uuid: { $eq: uuid }, - documentId: { $eq: documentId }, - group: { $eq: group }, - userId: { $ne: 'Group' } - }); + public getTasksAggregationFilters(nameFilter: string, processTimeout: number): unknown[] { + return DataBaseHelper.getTasksAggregationFilters(nameFilter, processTimeout); } /** - * Get multi sign documents by document identifiers - * @param documentIds Document identifiers - * @returns Multi sign documents + * Get Theme + * @param filters */ - public async getMultiSignDocumentsByDocumentIds( - documentIds: string[] - ): Promise { - return await this.find(MultiDocuments, { - documentId: { $in: documentIds }, - }); + public static async getTheme(filters: FilterQuery): Promise { + return await new DataBaseHelper(Theme).findOne(filters); } /** - * Get MultiSign Statuses by group - * @param uuid - * @param group - * - * @virtual + * Get Themes + * @param filters */ - public async getMultiSignDocumentsByGroup(uuid: string, group: string): Promise { - return await this.find(MultiDocuments, { - uuid: { $eq: uuid }, - group: { $eq: group }, - userId: { $eq: 'Group' }, - status: { $eq: 'NEW' } - }); + public static async getThemes(filters: FilterQuery): Promise { + return await new DataBaseHelper(Theme).find(filters); } /** - * Get MultiSign Status by document or user - * @param uuid - * @param documentId - * @param userId - * - * @virtual + * Get Token + * @param tokenId + * @param dryRun */ - public async getMultiSignStatus(uuid: string, documentId: string, userId: string = 'Group'): Promise { - return await this.findOne(MultiDocuments, {uuid, documentId, userId}); + public async getToken(tokenId: string, dryRun: string = null): Promise { + if (dryRun) { + return this.findOne(TokenCollection, { tokenId }); + } else { + return await new DataBaseHelper(TokenCollection).findOne({ tokenId }); + } } /** - * Get policies - * @param filters - * @param options + * Get Token + * @param tokenId */ - public static async getPolicies(filters?: FilterObject, options?: unknown): Promise { - return await new DataBaseHelper(Policy).find(filters, options); + public static async getToken(tokenId: string): Promise { + return await new DataBaseHelper(TokenCollection).findOne({ tokenId }); } /** - * Get policies and count - * @param filters - * @param options + * Get Token by ID + * @param id */ - public static async getPoliciesAndCount(filters: FilterObject, options?: FindOptions): Promise<[Policy[], number]> { - return await new DataBaseHelper(Policy).findAndCount(filters, options); + public static async getTokenById(id: string | null): Promise { + return await new DataBaseHelper(TokenCollection).findOne(id); } /** - * Get Policy - * @param policyId - * - * @virtual + * Get tokens + * @param filters Filters + * @returns Tokens */ - public async getPolicy(policyId: string | null): Promise { - return await new DataBaseHelper(Policy).findOne(policyId); + public static async getTokens(filters?: FilterQuery): Promise { + return await new DataBaseHelper(TokenCollection).find(filters); } - //Static - /** - * Get policy + * Get Tool * @param filters */ - public static async getPolicy(filters: FilterObject): Promise { - return await new DataBaseHelper(Policy).findOne(filters); + public static async getTool(filters: FilterQuery): Promise { + return await new DataBaseHelper(PolicyTool).findOne(filters); } /** - * Get policy by id - * @param policyId + * Get Tool By ID + * @param id */ - public static async getPolicyById(policyId: string | null): Promise { - return await new DataBaseHelper(Policy).findOne(policyId); + public static async getToolById(id: string | null): Promise { + return await new DataBaseHelper(PolicyTool).findOne(id); } /** - * Get policy by tag - * @param policyTag + * Get Tool By UUID + * @param uuid */ - public static async getPolicyByTag(policyTag: string): Promise { - return await new DataBaseHelper(Policy).findOne({policyTag}); + public static async getToolByUUID(uuid: string): Promise { + return await new DataBaseHelper(PolicyTool).findOne({ uuid }); } /** - * Get policy by uuid - * @param uuid + * Get Tools + * @param filters + * @param options */ - public static async getPolicyByUUID(uuid: string): Promise { - return await new DataBaseHelper(Policy).findOne({uuid}); + public static async getTools(filters?: FilterQuery, options?: unknown): Promise { + return await new DataBaseHelper(PolicyTool).find(filters, options); } /** - * Get policy cache - * @param filters Filters - * @returns Policy cache + * Get Tools + * @param filters + * @param options */ - public static async getPolicyCache(filters: FilterObject): Promise { - return await new DataBaseHelper(PolicyCache).findOne(filters); + public static async getToolsAndCount(filters?: FilterObject, options?: FindOptions): Promise<[PolicyTool[], number]> { + return await new DataBaseHelper(PolicyTool).findAndCount(filters, options); } /** - * Get policy cache data - * @param filters Filters - * @param options Options - * @returns Policy cache data + * Get Topic + * @param filters + * + * @virtual */ - public static async getPolicyCacheData( - filters?: FilterObject, - options?: FindOptions - ): Promise { - return await new DataBaseHelper(PolicyCacheData).find(filters, options); + public async getTopic( + filters: { + /** + * policyId + */ + policyId?: string, + /** + * type + */ + type?: TopicType, + /** + * name + */ + name?: string, + /** + * owner + */ + owner?: string, + /** + * topicId + */ + topicId?: string + } + ): Promise { + return await this.findOne(TopicCollection, filters); } /** - * Get policy caches - * @param filters Filters - * @returns Policy caches + * Get topic by id + * @param topicId */ - public static async getPolicyCaches(filters?: FilterObject): Promise { - return await new DataBaseHelper(PolicyCache).find(filters); + public async getTopicById(topicId: string): Promise { + return await this.findOne(TopicCollection, { topicId }); } /** - * Get Policy Categories - * - * @virtual + * Get topic by id + * @param topicId */ - public static async getPolicyCategories(): Promise { - return await new DataBaseHelper(PolicyCategory).find(PolicyCategory as FilterQuery); + public static async getTopicById(topicId: string): Promise { + return await new DataBaseHelper(TopicCollection).findOne({ topicId }); } /** - * Get policy count - * @param filters + * Get topic by type + * @param owner + * @param type */ - public static async getPolicyCount(filters: FilterObject): Promise { - return await new DataBaseHelper(Policy).count(filters); + public static async getTopicByType(owner: string, type: TopicType): Promise { + return await new DataBaseHelper(TopicCollection).findOne({ owner, type }); } /** - * Get Policy Properties + * Get Topics + * @param filters * * @virtual */ - public static async getPolicyProperties(): Promise { - return await new DataBaseHelper(PolicyProperty).find(PolicyProperty as FilterQuery); + public async getTopics( + filters: { + /** + * policyId + */ + policyId?: string, + /** + * type + */ + type?: TopicType, + /** + * name + */ + name?: string, + /** + * owner + */ + owner?: string, + /** + * topicId + */ + topicId?: string + } + ): Promise { + return await this.find(TopicCollection, filters); } /** - * Get policy test - * @param policyId - * @param id - * @returns tests + * Get transactions count + * @param filters Mint request identifier + * @returns Transactions count */ - public static async getPolicyTest(policyId: string, id: string): Promise { - return await new DataBaseHelper(PolicyTest).findOne({id, policyId}); + public async getTransactionsCount(filters: FilterObject): Promise { + return await this.count(MintTransaction, filters); } /** - * Get policy tests - * @param resultId + * Get transactions serials + * @param mintRequestId Mint request identifier + * @param transferStatus Transfer status * - * @returns tests + * @returns Serials */ - public static async getPolicyTestByRecord(resultId: string): Promise { - return await new DataBaseHelper(PolicyTest).findOne({resultId}); + public async getTransactionsSerials( + mintRequestId: string, + transferStatus?: MintTransactionStatus | unknown + ): Promise { + const aggregation = DataBaseHelper._getTransactionsSerialsAggregation( + mintRequestId, + transferStatus + ); + const result = await this.aggregate(MintTransaction, aggregation); + return result[0]?.serials || []; } /** - * Get policy tests - * @param policyId - * @returns tests + * Get transactions serials count + * @param mintRequestId Mint request identifier + * @param transferStatus Transfer status + * + * @returns Serials count */ - public static async getPolicyTests(policyId: string): Promise { - return await new DataBaseHelper(PolicyTest).find({policyId}); - } - - /** - * Get policy test - * @param policyId - * @param status - * @returns tests - */ - public static async getPolicyTestsByStatus(policyId: string, status: PolicyTestStatus): Promise { - return await new DataBaseHelper(PolicyTest).find({status, policyId}); - } + public async getTransactionsSerialsCount( + mintRequestId: string, + transferStatus?: MintTransactionStatus | unknown + ): Promise { + const aggregation = DataBaseHelper._getTransactionsSerialsAggregation( + mintRequestId, + transferStatus + ); - /** - * Get Publish Policies - * - * @virtual - */ - public static async getPublishPolicies(): Promise { - return await new DataBaseHelper(Policy).find({ - status: {$eq: PolicyType.PUBLISH} + DataBaseHelper.getTransactionsSerialsAggregationFilters({ + aggregation, + aggregateMethod: 'push', + nameFilter: MAP_TRANSACTION_SERIALS_AGGREGATION_FILTERS.COUNT }); - } - - /** - * Get Record - * @param filters Filters - * @param options Options - * @returns Record - */ - public static async getRecord(filters?: FilterQuery, options?: FindOptions): Promise { - return await new DataBaseHelper(Record).find(filters, options); - } - /** - * Get Residue objects - * @param policyId - * @param blockId - * @param userId - */ - public async getResidue( - policyId: string, - blockId: string, - userId: string - ): Promise { - return await this.find(SplitDocuments, { - policyId: { $eq: policyId }, - blockId: { $eq: blockId }, - userId: { $eq: userId } - }); - } + const result = await this.aggregate(MintTransaction, aggregation); - /** - * Get retire pools - * @param tokenIds Token identifiers - * @returns Retire pools - */ - public static async getRetirePools(tokenIds: string[]): Promise { - return await new DataBaseHelper(RetirePool).find({tokenIds: {$in: tokenIds}}); + //todo something wrong with logic, serials is array + return result[0]?.serials as unknown as number || 0; } /** - * Get schema - * @param iri - * @param topicId + * Get User In Group + * @param policyId + * @param did + * @param uuid + * + * @virtual */ - public async getSchemaByIRI(iri: string, topicId?: string): Promise { - if (topicId) { - return await new DataBaseHelper(SchemaCollection).findOne({iri, topicId}); - } else { - return await new DataBaseHelper(SchemaCollection).findOne({iri}); + public async getUserInGroup(policyId: string, did: string, uuid: string): Promise { + if (!did && !uuid) { + return null; } + return await this.findOne(PolicyRolesCollection, { policyId, did, uuid }); } /** - * Get schema - * @param id - */ - public static async getSchemaById(id: string | null): Promise { - return await new DataBaseHelper(SchemaCollection).findOne(id); - } - - /** - * Get schema - * @param topicId - * @param entity - */ - public async getSchemaByType(topicId: string, entity: SchemaEntity): Promise { - return await new DataBaseHelper(SchemaCollection).findOne({ - entity, - readonly: true, - topicId - }); - } - - /** - * Get schema - * @param topicId - * @param entity - */ - public static async getSchemaByType(topicId: string, entity: SchemaEntity): Promise { - return await new DataBaseHelper(SchemaCollection).findOne({ - entity, - readonly: true, - topicId - }); - } - - /** - * Get schemas - * @param filters - * @param options - */ - public static async getSchemas(filters?: FilterObject, options?: unknown): Promise { - return await new DataBaseHelper(SchemaCollection).find(filters, options); - } - - /** - * Get schema - * @param filters - * @param options - */ - public static async getSchemasAndCount(filters?: FilterObject, options?: FindOptions): Promise<[SchemaCollection[], number]> { - return await new DataBaseHelper(SchemaCollection).findAndCount(filters, options); - } - - /** - * Get schema - * @param ids - */ - public static async getSchemasByIds(ids: string[]): Promise { - return await new DataBaseHelper(SchemaCollection).find({id: {$in: ids}}); - } - - /** - * Get schema - * @param filters - */ - public static async getSchemasCount(filters?: FilterObject): Promise { - return await new DataBaseHelper(SchemaCollection).count(filters); - } - - /** - * Get split documents in policy - * @param policyId Policy identifier - * @returns Split documents - */ - public async getSplitDocumentsByPolicy( - policyId: string, - ): Promise { - return await this.find(SplitDocuments, { - policyId - }); - } - - /** - * Get suggestions config + * Get user role in policy + * @param policyId * @param did - * @returns config - */ - public static async getSuggestionsConfig( - did: string - ): Promise { - return await new DataBaseHelper(SuggestionsConfig).findOne({ - user: did - }); - } - - /** - * Get system schema - * @param entity */ - public static async getSystemSchema(entity: SchemaEntity): Promise { - return await new DataBaseHelper(SchemaCollection).findOne({ - entity, - system: true, - active: true - }); + public static async getUserRole(policyId: string, did: string): Promise { + if (!did) { + return null; + } + return await new DataBaseHelper(PolicyRolesCollection).find({ policyId, did }); } /** - * Get tag By UUID - * @param uuid + * Get user roles + * @param policyId + * @param did + * @returns + * + * @virtual */ - public async getTagById(uuid: string): Promise { - return await this.findOne(Tag, {uuid}); + public async getUserRoles(policyId: string, did: string): Promise { + return await this.find(PolicyRolesCollection, { policyId, did }); } /** - * Get tag By UUID - * @param uuid + * Get all policy users by role + * @param policyId + * @param role + * + * @virtual */ - public static async getTagById(uuid: string): Promise { - return await new DataBaseHelper(Tag).findOne({uuid}); + public async getUsersByRole(policyId: string, role: string): Promise { + return await this.find(PolicyRolesCollection, { policyId, role }); } /** - * Get tags + * Get VC * @param filters * @param options */ - public async getTagCache(filters?: FilterObject, options?: FindOptions): Promise { - return await this.find(TagCache, filters, options); + public static async getVC( + filters?: FilterQuery, + options?: FindOptions + ): Promise { + return await new DataBaseHelper(VcDocumentCollection).findOne(filters, options); } /** - * Get tags - * @param filters - * @param options + * Get VC + * @param id */ - public static async getTagCache(filters?: FilterQuery, options?: FindOptions): Promise { - return await new DataBaseHelper(TagCache).find(filters, options); + public static async getVCById(id: string | null): Promise | null { + return await new DataBaseHelper(VcDocumentCollection).findOne(id); } /** - * Get tags + * Get VCs * @param filters * @param options */ - public async getTags(filters?: FilterQuery, options?: FindOptions): Promise { - return await this.find(Tag, filters, options); + public static async getVCs(filters?: FilterQuery, options?: FindOptions): Promise { + return await new DataBaseHelper(VcDocumentCollection).find(filters, options); } /** - * Get tags + * Get VC * @param filters * @param options */ - public static async getTags(filters?: FilterQuery, options?: unknown): Promise { - return await new DataBaseHelper(Tag).find(filters, options); - } - - /** - * get tasks aggregation filters - * @param nameFilter - * @param processTimeout - * - * @returns Result - */ - public getTasksAggregationFilters(nameFilter: string, processTimeout: number): unknown[] { - return DataBaseHelper.getTasksAggregationFilters(nameFilter, processTimeout); - } - - /** - * Get Theme - * @param filters - */ - public static async getTheme(filters: FilterQuery): Promise { - return await new DataBaseHelper(Theme).findOne(filters); + public static async getVP(filters?: FilterQuery, options?: FindOptions): Promise { + return await new DataBaseHelper(VpDocumentCollection).findOne(filters, options); } /** - * Get Themes - * @param filters + * Get VC + * @param id */ - public static async getThemes(filters: FilterQuery): Promise { - return await new DataBaseHelper(Theme).find(filters); + public static async getVPById(id: string | null): Promise { + return await new DataBaseHelper(VpDocumentCollection).findOne(id); } /** - * Get Token - * @param tokenId - * @param dryRun + * Get VP mint information + * @param vpDocument VP + * @returns Serials and amount */ - public async getToken(tokenId: string, dryRun: string = null): Promise { - if (dryRun) { - return this.findOne(TokenCollection, {tokenId}); - } else { - return await new DataBaseHelper(TokenCollection).findOne({tokenId}); + public async getVPMintInformation( + vpDocument: VpDocument + ): Promise< + [ + serials: { serial: number; tokenId: string }[], + amount: number, + error: string, + wasTransferNeeded: boolean, + transferSerials: number[], + transferAmount: number, + tokenIds: string[], + target: string + ] + > { + const mintRequests = await this.getMintRequests({ + $or: [ + { + vpMessageId: vpDocument.messageId, + }, + { + secondaryVpIds: vpDocument.messageId, + }, + ], + } as FilterObject); + const serials = vpDocument.serials + ? vpDocument.serials.map((serial) => ({ + serial, + tokenId: vpDocument.tokenId, + })) + : []; + let amount = Number.isFinite(Number(vpDocument.amount)) + ? Number(vpDocument.amount) + : serials.length; + const transferSerials = vpDocument.serials + ? vpDocument.serials.map((serial) => ({ + serial, + tokenId: vpDocument.tokenId, + })) + : []; + let transferAmount = amount; + const errors = []; + let wasTransferNeeded = false; + const tokenIds = new Set(); + if (vpDocument.tokenId) { + tokenIds.add(vpDocument.tokenId); } + const target = mintRequests?.[0]?.target; + for (const mintRequest of mintRequests) { + if (mintRequest.error) { + errors.push(mintRequest.error); + } + wasTransferNeeded ||= mintRequest.wasTransferNeeded; + tokenIds.add(mintRequest.tokenId); + if (mintRequest.tokenType === TokenType.NON_FUNGIBLE) { + const requestSerials = await this.getMintRequestSerials( + mintRequest.id + ); + serials.push( + ...requestSerials.map((serial) => ({ + serial, + tokenId: mintRequest.tokenId, + })) + ); + amount += requestSerials.length; + + if (wasTransferNeeded) { + const requestTransferSerials = + await this.getMintRequestTransferSerials( + mintRequest.id + ); + transferSerials.push( + ...requestTransferSerials.map((serial) => ({ + serial, + tokenId: mintRequest.tokenId, + })) + ); + transferAmount += requestTransferSerials.length; + } + } else if (mintRequest.tokenType === TokenType.FUNGIBLE) { + const mintRequestTransaction = await this.getMintTransaction({ + mintRequestId: mintRequest.id, + mintStatus: MintTransactionStatus.SUCCESS, + }); + if (mintRequestTransaction) { + if (mintRequest.decimals > 0) { + amount += + mintRequest.amount / Math.pow(10, mintRequest.decimals); + } else { + amount += mintRequest.amount; + } + } + if (wasTransferNeeded) { + const mintRequestTransferTransaction = + await this.getMintTransaction({ + mintRequestId: mintRequest.id, + transferStatus: MintTransactionStatus.SUCCESS, + }); + if (mintRequestTransferTransaction) { + if (mintRequest.decimals > 0) { + transferAmount += + mintRequest.amount / + Math.pow(10, mintRequest.decimals); + } else { + transferAmount += mintRequest.amount; + } + } + } + } + } + + return [ + serials, + amount, + errors.join(', '), + wasTransferNeeded, + transferSerials, + transferAmount, + [...tokenIds], + target, + ]; } /** - * Get Token - * @param tokenId + * Get VCs + * @param filters + * @param options */ - public static async getToken(tokenId: string): Promise { - return await new DataBaseHelper(TokenCollection).findOne({tokenId}); + public static async getVPs(filters?: FilterQuery, options?: FindOptions): Promise { + return await new DataBaseHelper(VpDocumentCollection).find(filters, options); } /** - * Get Token by ID - * @param id + * Get Vc Document + * @param filters + * + * @virtual */ - public static async getTokenById(id: string | null): Promise { - return await new DataBaseHelper(TokenCollection).findOne(id); + public async getVcDocument(filters: FilterQuery): Promise { + return await this.findOne(VcDocumentCollection, filters); } /** - * Get tokens - * @param filters Filters - * @returns Tokens + * Get Vc Documents + * @param filters + * @param options + * @param countResult + * @virtual */ - public static async getTokens(filters?: FilterQuery): Promise { - return await new DataBaseHelper(TokenCollection).find(filters); + public async getVcDocuments( + filters: FilterObject, + options?: FindOptions, + countResult?: boolean + ): Promise { + if (countResult) { + return await this.count(VcDocumentCollection, filters, options); + } + return await this.find(VcDocumentCollection, filters, options) as T[]; } /** - * Get Tool - * @param filters + * Get Vc Documents + * @param aggregation + * @virtual */ - public static async getTool(filters: FilterQuery): Promise { - return await new DataBaseHelper(PolicyTool).findOne(filters); + public async getVcDocumentsByAggregation(aggregation: FilterObject[]): Promise { + return await this.aggregate(VcDocumentCollection, aggregation) as VcDocumentCollection[]; } /** - * Get Tool By ID - * @param id + * Get Virtual Documents + * @param policyId + * @param type + * @param pageIndex + * @param pageSize + * + * @virtual */ - public static async getToolById(id: string | null): Promise { - return await new DataBaseHelper(PolicyTool).findOne(id); + public static async getVirtualDocuments( + policyId: string, + type: string, + pageIndex?: string, + pageSize?: string + ): Promise<[DryRun[], number]> { + const filters = { + dryRunId: policyId, + dryRunClass: null + } + const otherOptions: { orderBy?: unknown, limit?: number, offset?: number, fields?: string[] } = {}; + const _pageSize = parseInt(pageSize, 10); + const _pageIndex = parseInt(pageIndex, 10); + if (Number.isInteger(_pageSize) && Number.isInteger(_pageIndex)) { + otherOptions.orderBy = { createDate: 'DESC' }; + otherOptions.limit = _pageSize; + otherOptions.offset = _pageIndex * _pageSize; + } + if (type === 'artifacts') { + filters.dryRunClass = { + $in: [ + 'VcDocumentCollection', + 'VpDocumentCollection', + 'DidDocumentCollection', + 'ApprovalDocumentCollection' + ] + }; + } else if (type === 'transactions') { + filters.dryRunClass = { $eq: 'Transactions' }; + otherOptions.fields = [ + 'id', + 'createDate', + 'type', + 'hederaAccountId' + ]; + } else if (type === 'ipfs') { + filters.dryRunClass = { $eq: 'Files' }; + otherOptions.fields = [ + 'id', + 'createDate', + 'document', + 'documentURL' + ]; + } + return await new DataBaseHelper(DryRun).findAndCount(filters, otherOptions); } /** - * Get Tool By UUID - * @param uuid + * Get Virtual Hedera Account + * @param hederaAccountId + * + * @virtual */ - public static async getToolByUUID(uuid: string): Promise { - return await new DataBaseHelper(PolicyTool).findOne({uuid}); + public async getVirtualHederaAccountInfo(hederaAccountId: string): Promise { + const item = (await new DataBaseHelper(DryRun).findOne({ + dryRunId: this.dryRun, + dryRunClass: 'HederaAccountInfo', + hederaAccountId + })); + return item?.tokenMap || {}; } /** - * Get Tools - * @param filters - * @param options + * Get Key from Virtual User + * @param did + * @param keyName + * + * @virtual */ - public static async getTools(filters?: FilterQuery, options?: unknown): Promise { - return await new DataBaseHelper(PolicyTool).find(filters, options); + public async getVirtualKey(did: string, keyName: string): Promise { + const item = (await new DataBaseHelper(DryRun).findOne({ + dryRunId: this.dryRun, + dryRunClass: 'VirtualKey', + did, + type: keyName + })); + return item?.hederaAccountKey; } /** - * Get Tools - * @param filters - * @param options + * Get virtual keys + * @param filters Filters + * @returns Virtual keys */ - public static async getToolsAndCount(filters?: FilterObject, options?: FindOptions): Promise<[PolicyTool[], number]> { - return await new DataBaseHelper(PolicyTool).findAndCount(filters, options); + public async getVirtualKeys(filters: FilterQuery): Promise { + const extendedFilters = filters as FilterQuery & { + dryRunId?: string; + dryRunClass?: string; + }; + + extendedFilters.dryRunId = this.dryRun; + extendedFilters.dryRunClass = 'VirtualKey'; + + return await new DataBaseHelper(DryRun).find(filters); } /** - * Get Topic - * @param filters + * Get Virtual Message + * @param dryRun + * @param messageId * * @virtual */ - public async getTopic( - filters: { - /** - * policyId - */ - policyId?: string, - /** - * type - */ - type?: TopicType, - /** - * name - */ - name?: string, - /** - * owner - */ - owner?: string, - /** - * topicId - */ - topicId?: string - } - ): Promise { - return await this.findOne(TopicCollection, filters); + public static async getVirtualMessage(dryRun: string, messageId: string): Promise { + return (await new DataBaseHelper(DryRun).findOne({ + dryRunId: dryRun, + dryRunClass: 'Message', + messageId + })); } /** - * Get topic by id + * Get Virtual Messages + * @param dryRun * @param topicId + * + * @virtual */ - public async getTopicById(topicId: string): Promise { - return await this.findOne(TopicCollection, {topicId}); + public static async getVirtualMessages(dryRun: string, topicId: string | TopicId): Promise { + return (await new DataBaseHelper(DryRun).find({ + dryRunId: dryRun, + dryRunClass: 'Message', + topicId + })); } /** - * Get topic by id - * @param topicId + * Get Virtual User + * @param did + * + * @virtual */ - public static async getTopicById(topicId: string): Promise { - return await new DataBaseHelper(TopicCollection).findOne({topicId}); + public async getVirtualUser(did: string): Promise { + return (await new DataBaseHelper(DryRun).findOne({ + dryRunId: this.dryRun, + dryRunClass: 'VirtualUsers', + did + })) as unknown as IAuthUser; } /** - * Get topic by type - * @param owner - * @param type + * Get Current Virtual User + * @param policyId + * + * @virtual */ - public static async getTopicByType(owner: string, type: TopicType): Promise { - return await new DataBaseHelper(TopicCollection).findOne({owner, type}); + public static async getVirtualUser(policyId: string): Promise { + return await new DataBaseHelper(DryRun).findOne({ + dryRunId: policyId, + dryRunClass: 'VirtualUsers', + active: true + }, { + fields: [ + 'id', + 'did', + 'username', + 'hederaAccountId', + 'active' + ] + } as unknown as FindOptions); } /** - * Get Topics + * Get All Virtual Users + * @param policyId + * + * @virtual + */ + public static async getVirtualUsers(policyId: string): Promise { + return (await new DataBaseHelper(DryRun).find({ + dryRunId: policyId, + dryRunClass: 'VirtualUsers' + }, { + fields: [ + 'id', + 'did', + 'username', + 'hederaAccountId', + 'active' + ] as unknown as PopulatePath.ALL[], + orderBy: { + createDate: 1 + } + })); + } + + /** + * Get Vp Document * @param filters * * @virtual */ - public async getTopics( - filters: { - /** - * policyId - */ - policyId?: string, - /** - * type - */ - type?: TopicType, - /** - * name - */ - name?: string, - /** - * owner - */ - owner?: string, - /** - * topicId - */ - topicId?: string + public async getVpDocument(filters: FilterQuery): Promise { + return await this.findOne(VpDocumentCollection, filters); + } + + /** + * Get Vp Documents + * @param filters + * + * @param options + * @param countResult + * @virtual + */ + public async getVpDocuments( + filters: FilterObject, + options?: FindOptions, + countResult?: boolean + ): Promise { + if (countResult) { + return await this.count(VpDocumentCollection, filters, options); } - ): Promise { - return await this.find(TopicCollection, filters); + return await this.find(VpDocumentCollection, filters, options) as T[]; } /** - * Get transactions count - * @param filters Mint request identifier - * @returns Transactions count + * Get Vp Documents + * @param aggregation + * @virtual */ - public async getTransactionsCount(filters: FilterObject): Promise { - return await this.count(MintTransaction, filters); + public async getVpDocumentsByAggregation(aggregation: FilterObject[]): Promise { + return await this.aggregate(VpDocumentCollection, aggregation) as VpDocumentCollection[]; } /** - * Get transactions serials - * @param mintRequestId Mint request identifier - * @param transferStatus Transfer status + * Load file + * @param id * - * @returns Serials + * @returns file ID */ - public async getTransactionsSerials( - mintRequestId: string, - transferStatus?: MintTransactionStatus | unknown - ): Promise { - const aggregation = DataBaseHelper._getTransactionsSerialsAggregation( - mintRequestId, - transferStatus - ); - const result = await this.aggregate(MintTransaction, aggregation); - return result[0]?.serials || []; + public static async loadFile(id: ObjectId): Promise { + return DataBaseHelper.loadFile(id); } /** - * Get transactions serials count - * @param mintRequestId Mint request identifier - * @param transferStatus Transfer status + * Parse invite token + * @param policyId + * @param invitationId * - * @returns Serials count + * @virtual */ - public async getTransactionsSerialsCount( - mintRequestId: string, - transferStatus?: MintTransactionStatus | unknown - ): Promise { - const aggregation = DataBaseHelper._getTransactionsSerialsAggregation( - mintRequestId, - transferStatus - ); + public async parseInviteToken(policyId: string, invitationId: string): Promise { + const invitation = await this.findOne(PolicyInvitations, invitationId); + if (invitation && invitation.policyId === policyId && invitation.active === true) { + invitation.active = false; + await this.save(PolicyInvitations, invitation); + return invitation; + } else { + return null; + } + } - DataBaseHelper.getTransactionsSerialsAggregationFilters({ - aggregation, - aggregateMethod: 'push', - nameFilter: MAP_TRANSACTION_SERIALS_AGGREGATION_FILTERS.COUNT - }); + /** + * Overriding the remove method + * @param entityClass + * @param entities + */ + public async remove(entityClass: new () => T, entities: T | T[]): Promise { + if (this.dryRun) { + await new DataBaseHelper(DryRun).remove(entities as unknown as DryRun | DryRun[]); + } else { + await new DataBaseHelper(entityClass).remove(entities); + } + } - const result = await this.aggregate(MintTransaction, aggregation); + /** + * Remove Aggregate Document + * @param hash + * @param blockId + * + * @virtual + */ + public async removeAggregateDocument(hash: string, blockId: string): Promise { + const item = await this.find(AggregateVC, { blockId, hash }); + await this.remove(AggregateVC, item); + } - //todo something wrong with logic, serials is array - return result[0]?.serials as unknown as number || 0; + /** + * Remove Aggregate Documents + * @param removeMsp + * + * @virtual + */ + public async removeAggregateDocuments(removeMsp: AggregateVC[]): Promise { + await this.remove(AggregateVC, removeMsp); } /** - * Get User In Group - * @param policyId - * @param did - * @param uuid - * - * @virtual + * Remove Artifact + * @param artifact Artifact */ - public async getUserInGroup(policyId: string, did: string, uuid: string): Promise { - if (!did && !uuid) { - return null; - } - return await this.findOne(PolicyRolesCollection, {policyId, did, uuid}); + public static async removeArtifact(artifact?: ArtifactCollection): Promise { + await new DataBaseHelper(ArtifactCollection).remove(artifact) + await new DataBaseHelper(ArtifactChunkCollection).delete({ + uuid: artifact.uuid + }); } /** - * Get user role in policy - * @param policyId + * Remove assign entity + * @param type + * @param entityId * @param did + * @param owner */ - public static async getUserRole(policyId: string, did: string): Promise { - if (!did) { - return null; + public static async removeAssignEntity( + type: AssignedEntityType, + entityId: string, + did: string, + owner?: string + ): Promise { + const filters: { type: AssignedEntityType, entityId: string, did: string, owner?: string } = { type, entityId, did }; + + if (owner) { + filters.owner = owner; + } + const item = await (new DataBaseHelper(AssignEntity)).findOne(filters); + if (item) { + await (new DataBaseHelper(AssignEntity)).remove(item); } - return await new DataBaseHelper(PolicyRolesCollection).find({policyId, did}); + return true; } /** - * Get user roles - * @param policyId - * @param did - * @returns - * - * @virtual + * Delete Module + * @param module */ - public async getUserRoles(policyId: string, did: string): Promise { - return await this.find(PolicyRolesCollection, {policyId, did}); + public static async removeModule(module: PolicyModule): Promise { + return await new DataBaseHelper(PolicyModule).remove(module); } /** - * Get all policy users by role - * @param policyId - * @param role - * - * @virtual + * Get policy tests + * @returns tests */ - public async getUsersByRole(policyId: string, role: string): Promise { - return await this.find(PolicyRolesCollection, {policyId, role}); + public static async removePolicyTests(tests: PolicyTest[]): Promise { + await new DataBaseHelper(PolicyTest).remove(tests); } /** - * Get VC - * @param filters - * @param options + * Remove Residue objects + * @param residue */ - public static async getVC( - filters?: FilterQuery, - options?: FindOptions - ): Promise { - return await new DataBaseHelper(VcDocumentCollection).findOne(filters, options); + public async removeResidue(residue: SplitDocuments[]): Promise { + await this.remove(SplitDocuments, residue); } /** - * Get VC - * @param id + * Delete tag + * @param tag */ - public static async getVCById(id: string | null): Promise | null { - return await new DataBaseHelper(VcDocumentCollection).findOne(id); + public async removeTag(tag: Tag): Promise { + return await this.remove(Tag, tag); } /** - * Get VCs - * @param filters - * @param options + * Delete tag + * @param tag */ - public static async getVCs(filters?: FilterQuery, options?: FindOptions): Promise { - return await new DataBaseHelper(VcDocumentCollection).find(filters, options); + public static async removeTag(tag: Tag): Promise { + return await new DataBaseHelper(Tag).remove(tag); } /** - * Get VC - * @param filters - * @param options + * Delete Theme + * @param theme */ - public static async getVP(filters?: FilterQuery, options?: FindOptions): Promise { - return await new DataBaseHelper(VpDocumentCollection).findOne(filters, options); + public static async removeTheme(theme: Theme): Promise { + return await new DataBaseHelper(Theme).remove(theme); } /** - * Get VC - * @param id + * Delete Tool + * @param tool */ - public static async getVPById(id: string | null): Promise { - return await new DataBaseHelper(VpDocumentCollection).findOne(id); + public static async removeTool(tool: PolicyTool): Promise { + return await new DataBaseHelper(PolicyTool).remove(tool); } /** - * Get VP mint information - * @param vpDocument VP - * @returns Serials and amount + * Overriding the save method + * @param entityClass + * @param item + * @param filter */ - public async getVPMintInformation( - vpDocument: VpDocument - ): Promise< - [ - serials: { serial: number; tokenId: string }[], - amount: number, - error: string, - wasTransferNeeded: boolean, - transferSerials: number[], - transferAmount: number, - tokenIds: string[], - target: string - ] - > { - const mintRequests = await this.getMintRequests({ - $or: [ - { - vpMessageId: vpDocument.messageId, - }, - { - secondaryVpIds: vpDocument.messageId, - }, - ], - } as FilterObject); - const serials = vpDocument.serials - ? vpDocument.serials.map((serial) => ({ - serial, - tokenId: vpDocument.tokenId, - })) - : []; - let amount = Number.isFinite(Number(vpDocument.amount)) - ? Number(vpDocument.amount) - : serials.length; - const transferSerials = vpDocument.serials - ? vpDocument.serials.map((serial) => ({ - serial, - tokenId: vpDocument.tokenId, - })) - : []; - let transferAmount = amount; - const errors = []; - let wasTransferNeeded = false; - const tokenIds = new Set(); - if (vpDocument.tokenId) { - tokenIds.add(vpDocument.tokenId); + async save(entityClass: new () => T, item: unknown | unknown[], filter?: FilterObject): Promise { + if (Array.isArray(item)) { + return await this.saveMany(entityClass, item, filter) as any; } - const target = mintRequests?.[0]?.target; - for (const mintRequest of mintRequests) { - if (mintRequest.error) { - errors.push(mintRequest.error); - } - wasTransferNeeded ||= mintRequest.wasTransferNeeded; - tokenIds.add(mintRequest.tokenId); - if (mintRequest.tokenType === TokenType.NON_FUNGIBLE) { - const requestSerials = await this.getMintRequestSerials( - mintRequest.id - ); - serials.push( - ...requestSerials.map((serial) => ({ - serial, - tokenId: mintRequest.tokenId, - })) - ); - amount += requestSerials.length; - if (wasTransferNeeded) { - const requestTransferSerials = - await this.getMintRequestTransferSerials( - mintRequest.id - ); - transferSerials.push( - ...requestTransferSerials.map((serial) => ({ - serial, - tokenId: mintRequest.tokenId, - })) - ); - transferAmount += requestTransferSerials.length; - } - } else if (mintRequest.tokenType === TokenType.FUNGIBLE) { - const mintRequestTransaction = await this.getMintTransaction({ - mintRequestId: mintRequest.id, - mintStatus: MintTransactionStatus.SUCCESS, - }); - if (mintRequestTransaction) { - if (mintRequest.decimals > 0) { - amount += - mintRequest.amount / Math.pow(10, mintRequest.decimals); - } else { - amount += mintRequest.amount; - } - } - if (wasTransferNeeded) { - const mintRequestTransferTransaction = - await this.getMintTransaction({ - mintRequestId: mintRequest.id, - transferStatus: MintTransactionStatus.SUCCESS, - }); - if (mintRequestTransferTransaction) { - if (mintRequest.decimals > 0) { - transferAmount += - mintRequest.amount / - Math.pow(10, mintRequest.decimals); - } else { - transferAmount += mintRequest.amount; - } - } - } - } + if (this.dryRun) { + this.addDryRunId(entityClass, item); + return await new DataBaseHelper(DryRun).save(item, filter) as unknown as T; } - return [ - serials, - amount, - errors.join(', '), - wasTransferNeeded, - transferSerials, - transferAmount, - [...tokenIds], - target, - ]; + return await new DataBaseHelper(entityClass).save(item as Partial, filter); + } + + /** + * Save Approval VC + * @param row + * + * @virtual + */ + public async saveApproval(row: Partial): Promise { + const doc = this.create(ApprovalDocumentCollection, row); + return await this.save(ApprovalDocumentCollection, doc); + } + + /** + * Save Artifact + * @param artifact Artifact + * @returns Saved Artifact + */ + public static async saveArtifact(artifact: ArtifactCollection): Promise { + return await new DataBaseHelper(ArtifactCollection).save(artifact); } /** - * Get VCs - * @param filters - * @param options + * Save Artifact File + * @param uuid File UUID + * @param data Data */ - public static async getVPs(filters?: FilterQuery, options?: FindOptions): Promise { - return await new DataBaseHelper(VpDocumentCollection).find(filters, options); + public static async saveArtifactFile(uuid: string, data: Buffer): Promise { + let offset = 0; + let fileNumber = 1; + while (offset < data.length) { + await new DataBaseHelper(ArtifactChunkCollection).save({ + uuid, + number: fileNumber, + data: new Binary(data.subarray(offset, offset + DatabaseServer.MAX_DOCUMENT_SIZE > data.length ? data.length : offset + DatabaseServer.MAX_DOCUMENT_SIZE)) + }); + offset = offset + DatabaseServer.MAX_DOCUMENT_SIZE; + fileNumber++; + } } /** - * Get Vc Document - * @param filters - * - * @virtual + * Save Artifacts + * @param artifacts Artifacts + * @returns Saved Artifacts */ - public async getVcDocument(filters: FilterQuery): Promise { - return await this.findOne(VcDocumentCollection, filters); + public static async saveArtifacts(artifacts: ArtifactCollection[]): Promise { + return await new DataBaseHelper(ArtifactCollection).saveMany(artifacts); } /** - * Get Vc Documents - * @param filters - * @param options - * @param countResult + * Save Block State + * @param {string} policyId - policy ID + * @param {string} blockId - block UUID + * @param {string} did - user DID + * @param {string} name - variable name + * @param {unknown} value - variable value + * @param {boolean} isLongValue - if long value * @virtual */ - public async getVcDocuments( - filters: FilterObject, - options?: FindOptions, - countResult?: boolean - ): Promise { - if (countResult) { - return await this.count(VcDocumentCollection, filters, options); + public async saveBlockCache( + policyId: string, + blockId: string, + did: string, + name: string, + value: unknown, + isLongValue: boolean + ): Promise { + let stateEntity = await this.findOne(BlockCache, { + policyId, + blockId, + did, + name + }); + if (stateEntity) { + stateEntity.value = value; + stateEntity.isLongValue = isLongValue; + } else { + stateEntity = this.create(BlockCache, { + policyId, + blockId, + did, + name, + value, + isLongValue + }); } - return await this.find(VcDocumentCollection, filters, options) as T[]; + await this.save(BlockCache, stateEntity); } /** - * Get Vc Documents - * @param aggregation + * Save Block State + * @param policyId + * @param uuid + * @param state + * * @virtual */ - public async getVcDocumentsByAggregation(aggregation: FilterObject[]): Promise { - return await this.aggregate(VcDocumentCollection, aggregation) as VcDocumentCollection[]; + public async saveBlockState(policyId: string, uuid: string, state: unknown): Promise { + let stateEntity = await this.findOne(BlockState, { + policyId, + blockId: uuid + }); + if (!stateEntity) { + stateEntity = this.create(BlockState, { + policyId, + blockId: uuid + }); + } + stateEntity.blockState = JSON.stringify(state); + await this.save(BlockState, stateEntity); } /** - * Get Virtual Documents - * @param policyId - * @param type - * @param pageIndex - * @param pageSize + * Save Did + * @param row * * @virtual */ - public static async getVirtualDocuments( - policyId: string, - type: string, - pageIndex?: string, - pageSize?: string - ): Promise<[DryRun[], number]> { - const filters = { - dryRunId: policyId, - dryRunClass: null - } - const otherOptions: { orderBy?: unknown, limit?: number, offset?: number, fields?: string[] } = {}; - const _pageSize = parseInt(pageSize, 10); - const _pageIndex = parseInt(pageIndex, 10); - if (Number.isInteger(_pageSize) && Number.isInteger(_pageIndex)) { - otherOptions.orderBy = { createDate: 'DESC' }; - otherOptions.limit = _pageSize; - otherOptions.offset = _pageIndex * _pageSize; - } - if (type === 'artifacts') { - filters.dryRunClass = { - $in: [ - 'VcDocumentCollection', - 'VpDocumentCollection', - 'DidDocumentCollection', - 'ApprovalDocumentCollection' - ] - }; - } else if (type === 'transactions') { - filters.dryRunClass = { $eq: 'Transactions' }; - otherOptions.fields = [ - 'id', - 'createDate', - 'type', - 'hederaAccountId' - ]; - } else if (type === 'ipfs') { - filters.dryRunClass = { $eq: 'Files' }; - otherOptions.fields = [ - 'id', - 'createDate', - 'document', - 'documentURL' - ]; - } - return await new DataBaseHelper(DryRun).findAndCount(filters, otherOptions); + public async saveDid(row: Partial): Promise { + const doc = this.create(DidDocumentCollection, row); + return await this.save(DidDocumentCollection, doc); } /** - * Get Virtual Hedera Account - * @param hederaAccountId + * Save Document State + * @param row * * @virtual */ - public async getVirtualHederaAccountInfo(hederaAccountId: string): Promise { - const item = (await new DataBaseHelper(DryRun).findOne({ - dryRunId: this.dryRun, - dryRunClass: 'HederaAccountInfo', - hederaAccountId - })); - return item?.tokenMap || {}; + public async saveDocumentState(row: Partial): Promise { + const item = this.create(DocumentState, row); + return await this.save(DocumentState, item); } /** - * Get Key from Virtual User - * @param did - * @param keyName + * Save file + * @param uuid + * @param buffer * - * @virtual + * @returns file ID */ - public async getVirtualKey(did: string, keyName: string): Promise { - const item = (await new DataBaseHelper(DryRun).findOne({ - dryRunId: this.dryRun, - dryRunClass: 'VirtualKey', - did, - type: keyName - })); - return item?.hederaAccountKey; + public static async saveFile(uuid: string, buffer: Buffer): Promise { + return DataBaseHelper.saveFile(uuid, buffer); } /** - * Get virtual keys - * @param filters Filters - * @returns Virtual keys + * Save many + * @param entityClass + * @param item + * @param filter */ - public async getVirtualKeys(filters: FilterQuery): Promise { - const extendedFilters = filters as FilterQuery & { - dryRunId?: string; - dryRunClass?: string; - }; + async saveMany(entityClass: new () => T, item: unknown[], filter?: FilterObject): Promise { + if (this.dryRun) { + this.addDryRunId(entityClass, item); + return await new DataBaseHelper(DryRun).saveMany(item, filter) as unknown as T[]; + } + return await new DataBaseHelper(entityClass).saveMany(item as Partial[], filter); + } - extendedFilters.dryRunId = this.dryRun; - extendedFilters.dryRunClass = 'VirtualKey'; + /** + * Save mint request + * @param data Mint request + * @returns Saved mint request + */ + public async saveMintRequest(data: Partial): Promise { + return await this.save(MintRequest, data); + } - return await new DataBaseHelper(DryRun).find(filters); + /** + * Save mint transaction + * @param transaction Transaction + * @returns Saved transaction + */ + public async saveMintTransaction(transaction: Partial): Promise { + return this.save(MintTransaction, transaction); + } + + /** + * Save Multi Policy object + * @param multiPolicy + * @returns multiPolicy + */ + public static async saveMultiPolicy(multiPolicy: MultiPolicy): Promise { + return await new DataBaseHelper(MultiPolicy).save(multiPolicy); + } + + /** + * Update policies + * @param models + */ + public static async savePolicies(models: Policy[]): Promise { + return await new DataBaseHelper(Policy).saveMany(models); + } + + /** + * Save policy cache + * @param entity Entity + * @returns Policy cache + */ + public static async savePolicyCache(entity: Partial): Promise { + return await new DataBaseHelper(PolicyCache).save(entity); + } + + /** + * Save policy cache data + * @param entity Policy cache data + * @returns Policy cache data + */ + public static async savePolicyCacheData( + entity: Partial + ): Promise { + return await new DataBaseHelper(PolicyCacheData).save(entity); + } + + /** + * Save schema + * @param item + */ + public static async saveSchema(item: SchemaCollection): Promise { + return await new DataBaseHelper(SchemaCollection).save(item); } /** - * Get Virtual Message - * @param dryRun - * @param messageId - * - * @virtual + * Save schemas + * @param items */ - public static async getVirtualMessage(dryRun: string, messageId: string): Promise { - return (await new DataBaseHelper(DryRun).findOne({ - dryRunId: dryRun, - dryRunClass: 'Message', - messageId - })); + public static async saveSchemas(items: SchemaCollection[]): Promise { + return await new DataBaseHelper(SchemaCollection).saveMany(items); } /** - * Get Virtual Messages - * @param dryRun - * @param topicId + * Save Topic + * @param topic * * @virtual */ - public static async getVirtualMessages(dryRun: string, topicId: string | TopicId): Promise { - return (await new DataBaseHelper(DryRun).find({ - dryRunId: dryRun, - dryRunClass: 'Message', - topicId - })); + public async saveTopic(topic: TopicCollection): Promise { + const topicObject = this.create(TopicCollection, topic); + return await this.save(TopicCollection, topicObject); } /** - * Get Virtual User - * @param did - * - * @virtual + * Save topic + * @param row */ - public async getVirtualUser(did: string): Promise { - return (await new DataBaseHelper(DryRun).findOne({ - dryRunId: this.dryRun, - dryRunClass: 'VirtualUsers', - did - })) as unknown as IAuthUser; + public static async saveTopic(row: Partial): Promise { + return await new DataBaseHelper(TopicCollection).save(row); } /** - * Get Current Virtual User - * @param policyId + * Save VC + * @param row * * @virtual */ - public static async getVirtualUser(policyId: string): Promise { - return await new DataBaseHelper(DryRun).findOne({ - dryRunId: policyId, - dryRunClass: 'VirtualUsers', - active: true - }, { - fields: [ - 'id', - 'did', - 'username', - 'hederaAccountId', - 'active' - ] - } as unknown as FindOptions); + public async saveVC(row: Partial): Promise { + const doc = this.create(VcDocumentCollection, row); + return await this.save(VcDocumentCollection, doc); } /** - * Get All Virtual Users - * @param policyId - * - * @virtual + * Save VC + * @param row */ - public static async getVirtualUsers(policyId: string): Promise { - return (await new DataBaseHelper(DryRun).find({ - dryRunId: policyId, - dryRunClass: 'VirtualUsers' - }, { - fields: [ - 'id', - 'did', - 'username', - 'hederaAccountId', - 'active' - ] as unknown as PopulatePath.ALL[], - orderBy: { - createDate: 1 - } - })); + public static async saveVC(row: Partial): Promise { + return await new DataBaseHelper(VcDocumentCollection).save(row); } /** - * Get Vp Document - * @param filters + * Save VCs + * @param data * - * @virtual + * @returns VCs */ - public async getVpDocument(filters: FilterQuery): Promise { - return await this.findOne(VpDocumentCollection, filters); + // tslint:disable-next-line:adjacent-overload-signatures + public static async saveVCs(data: Partial): Promise { + return (await new DataBaseHelper(VcDocumentCollection).save(data)); } /** - * Get Vp Documents - * @param filters + * Save VP + * @param row * - * @param options - * @param countResult * @virtual */ - public async getVpDocuments( - filters: FilterObject, - options?: FindOptions, - countResult?: boolean - ): Promise { - if (countResult) { - return await this.count(VpDocumentCollection, filters, options); - } - return await this.find(VpDocumentCollection, filters, options) as T[]; + public async saveVP(row: Partial): Promise { + const doc = this.create(VpDocumentCollection, row); + return await this.save(VpDocumentCollection, doc); } /** - * Get Vp Documents - * @param aggregation - * @virtual + * Save VPs + * @param data + * + * @returns VPs */ - public async getVpDocumentsByAggregation(aggregation: FilterObject[]): Promise { - return await this.aggregate(VpDocumentCollection, aggregation) as VpDocumentCollection[]; + public static async saveVPs(data: Partial): Promise { + return (await new DataBaseHelper(VpDocumentCollection).save(data)); } /** - * Load file - * @param id + * Save Virtual Message + * @param dryRun + * @param message * - * @returns file ID + * @virtual */ - public static async loadFile(id: ObjectId): Promise { - return DataBaseHelper.loadFile(id); + public static async saveVirtualMessage(dryRun: string, message: Message): Promise { + const document = message.toMessage(); + const messageId = message.getId(); + const topicId = message.getTopicId(); + + await new DataBaseHelper(DryRun).save(DatabaseServer.addDryRunId({ + document, + topicId, + messageId + }, dryRun, 'Message', false)); } /** - * Parse invite token + * Set Active Group + * * @param policyId - * @param invitationId + * @param did + * @param uuid * * @virtual */ - public async parseInviteToken(policyId: string, invitationId: string): Promise { - const invitation = await this.findOne(PolicyInvitations, invitationId); - if (invitation && invitation.policyId === policyId && invitation.active === true) { - invitation.active = false; - await this.save(PolicyInvitations, invitation); - return invitation; - } else { - return null; + public async setActiveGroup(policyId: string, did: string, uuid: string): Promise { + const groups = await this.find(PolicyRolesCollection, { policyId, did }); + for (const group of groups) { + group.active = group.uuid === uuid; } + await this.saveMany(PolicyRolesCollection, groups); } /** - * Overriding the remove method - * @param entityClass - * @param entities + * Set Dry Run id + * @param id */ - public async remove(entityClass: new () => T, entities: T | T[]): Promise { - if (this.dryRun) { - await new DataBaseHelper(DryRun).remove(entities as unknown as DryRun | DryRun[]); - } else { - await new DataBaseHelper(entityClass).remove(entities); - } + public setDryRun(id: string): void { + this.dryRun = id; } /** - * Remove Aggregate Document - * @param hash - * @param blockId + * Set MultiSign Status by user + * @param uuid + * @param documentId + * @param user + * @param status + * @param document * * @virtual */ - public async removeAggregateDocument(hash: string, blockId: string): Promise { - const item = await this.find(AggregateVC, {blockId, hash}); - await this.remove(AggregateVC, item); + public async setMultiSigDocument( + uuid: string, + documentId: string, + user: { id: string, did: string, group: string, username: string }, + status: string, + document: IVC + ): Promise { + const doc = this.create(MultiDocuments, { + uuid, + documentId, + status, + document, + userId: user.id, + did: user.did, + group: user.group, + username: user.username + }); + await this.save(MultiDocuments, doc); + return doc; } /** - * Remove Aggregate Documents - * @param removeMsp + * Set MultiSign Status by document + * @param uuid + * @param documentId + * @param group + * @param status * * @virtual */ - public async removeAggregateDocuments(removeMsp: AggregateVC[]): Promise { - await this.remove(AggregateVC, removeMsp); - } - - /** - * Remove Artifact - * @param artifact Artifact - */ - public static async removeArtifact(artifact?: ArtifactCollection): Promise { - await new DataBaseHelper(ArtifactCollection).remove(artifact) - await new DataBaseHelper(ArtifactChunkCollection).delete({ - uuid: artifact.uuid + public async setMultiSigStatus( + uuid: string, + documentId: string, + group: string, + status: string + ): Promise { + let item = await this.findOne(MultiDocuments, { + uuid: { $eq: uuid }, + documentId: { $eq: documentId }, + group: { $eq: group }, + userId: { $eq: 'Group' } }); + if (item) { + item.status = status; + await this.update(MultiDocuments, item.id, item); + } else { + item = this.create(MultiDocuments, { + uuid, + documentId, + status, + document: null, + userId: 'Group', + did: null, + group, + username: null + }); + await this.save(MultiDocuments, item); + } + return item; } /** - * Remove assign entity - * @param type - * @param entityId - * @param did - * @param owner + * Set Residue objects + * @param residue */ - public static async removeAssignEntity( - type: AssignedEntityType, - entityId: string, - did: string, - owner?: string - ): Promise { - const filters: {type: AssignedEntityType, entityId: string, did: string, owner?: string} = {type, entityId, did}; - - if (owner) { - filters.owner = owner; - } - const item = await (new DataBaseHelper(AssignEntity)).findOne(filters); - if (item) { - await (new DataBaseHelper(AssignEntity)).remove(item); - } - return true; + public async setResidue(residue: SplitDocuments[]): Promise { + await this.saveMany(SplitDocuments, residue); } /** - * Delete Module - * @param module + * Save suggestions config + * @param config + * @returns config */ - public static async removeModule(module: PolicyModule): Promise { - return await new DataBaseHelper(PolicyModule).remove(module); + public static async setSuggestionsConfig( + config: Partial + ): Promise { + const existingConfig = await DatabaseServer.getSuggestionsConfig( + config.user + ); + if (existingConfig) { + existingConfig.items = config.items; + } + return await new DataBaseHelper(SuggestionsConfig).save( + existingConfig || config + ); } /** - * Get policy tests - * @returns tests + * Set Dry Run id + * @param id */ - public static async removePolicyTests(tests: PolicyTest[]): Promise { - await new DataBaseHelper(PolicyTest).remove(tests); + public setSystemMode(systemMode: boolean): void { + this.systemMode = systemMode; } /** - * Remove Residue objects - * @param residue + * Set user in group + * + * @param group + * + * @virtual */ - public async removeResidue(residue: SplitDocuments[]): Promise { - await this.remove(SplitDocuments, residue); + public async setUserInGroup(group: unknown): Promise { + const doc = this.create(PolicyRolesCollection, group); + await this.save(PolicyRolesCollection, doc); + return doc; } /** - * Delete tag - * @param tag + * Save Virtual File + * @param policyId + * @param file + * @param url + * + * @virtual */ - public async removeTag(tag: Tag): Promise { - return await this.remove(Tag, tag); + public static async setVirtualFile( + policyId: string, + file: ArrayBuffer, + url: { url: string } + ): Promise { + await new DataBaseHelper(DryRun).save(DatabaseServer.addDryRunId({ + document: { + size: file?.byteLength + }, + documentURL: url?.url + }, policyId, 'Files', false)); } /** - * Delete tag - * @param tag + * Set Key from Virtual User + * @param did + * @param keyName + * @param key + * + * @virtual */ - public static async removeTag(tag: Tag): Promise { - return await new DataBaseHelper(Tag).remove(tag); + public async setVirtualKey(did: string, keyName: string, key: string): Promise { + await new DataBaseHelper(DryRun).save({ + dryRunId: this.dryRun, + dryRunClass: 'VirtualKey', + did, + type: keyName, + hederaAccountKey: key + } as Partial); } /** - * Delete Theme - * @param theme + * Save Virtual Transaction + * @param policyId + * @param type + * @param operatorId + * + * @virtual */ - public static async removeTheme(theme: Theme): Promise { - return await new DataBaseHelper(Theme).remove(theme); + public static async setVirtualTransaction( + policyId: string, + type: string, + operatorId?: string + ): Promise { + await new DataBaseHelper(DryRun).save(DatabaseServer.addDryRunId({ + type, + hederaAccountId: operatorId + }, policyId, 'Transactions', false)); } /** - * Delete Tool - * @param tool + * Set Current Virtual User + * @param policyId + * @param did + * + * @virtual */ - public static async removeTool(tool: PolicyTool): Promise { - return await new DataBaseHelper(PolicyTool).remove(tool); + public static async setVirtualUser(policyId: string, did: string): Promise { + const items = (await new DataBaseHelper(DryRun).find({ + dryRunId: policyId, + dryRunClass: 'VirtualUsers' + })); + for (const item of items) { + item.active = item.did === did; + await new DataBaseHelper(DryRun).save(item); + } } /** - * Overriding the save method + * Overriding the update method * @param entityClass - * @param item - * @param filter + * @param criteria + * @param row */ - async save(entityClass: new () => T, item: unknown | unknown[], filter?: FilterObject): Promise { - if (Array.isArray(item)) { - return await this.saveMany(entityClass, item, filter) as any; + async update( + entityClass: new () => T, + criteria: FilterQuery, + row: unknown | unknown[] + ): Promise { + if (Array.isArray(criteria)) { + return await this.updateMany(entityClass, row as unknown as T[], criteria) as any; } if (this.dryRun) { - this.addDryRunId(entityClass, item); - return await new DataBaseHelper(DryRun).save(item, filter) as unknown as T; + this.addDryRunId(entityClass, row); + return (await new DataBaseHelper(DryRun).update(row as DryRun, criteria as FilterQuery)) as unknown as T; + } else { + return await new DataBaseHelper(entityClass).update(row as T, criteria); } - - return await new DataBaseHelper(entityClass).save(item as Partial, filter); } /** - * Save Approval VC + * Update Approval VC * @param row * * @virtual */ - public async saveApproval(row: Partial): Promise { - const doc = this.create(ApprovalDocumentCollection, row); - return await this.save(ApprovalDocumentCollection, doc); + public async updateApproval(row: ApprovalDocumentCollection): Promise { + await this.update(ApprovalDocumentCollection, row.id, row); + return row; } /** - * Save Artifact - * @param artifact Artifact - * @returns Saved Artifact + * Update Did + * @param row + * + * @virtual */ - public static async saveArtifact(artifact: ArtifactCollection): Promise { - return await new DataBaseHelper(ArtifactCollection).save(artifact); + public async updateDid(row: DidDocumentCollection): Promise { + await this.update(DidDocumentCollection, row.id, row); + return row; } /** - * Save Artifact File - * @param uuid File UUID - * @param data Data + * Update External Topic + * @param item + * + * @virtual */ - public static async saveArtifactFile(uuid: string, data: Buffer): Promise { - let offset = 0; - let fileNumber = 1; - while (offset < data.length) { - await new DataBaseHelper(ArtifactChunkCollection).save({ - uuid, - number: fileNumber, - data: new Binary(data.subarray(offset, offset + DatabaseServer.MAX_DOCUMENT_SIZE > data.length ? data.length : offset + DatabaseServer.MAX_DOCUMENT_SIZE)) - }); - offset = offset + DatabaseServer.MAX_DOCUMENT_SIZE; - fileNumber++; - } + public async updateExternalTopic(item: ExternalDocument): Promise { + return await this.save(ExternalDocument, item); } /** - * Save Artifacts - * @param artifacts Artifacts - * @returns Saved Artifacts + * Update many method + * @param entityClass + * @param entities + * @param filter */ - public static async saveArtifacts(artifacts: ArtifactCollection[]): Promise { - return await new DataBaseHelper(ArtifactCollection).saveMany(artifacts); + async updateMany( + entityClass: new () => T, + entities: T[], + filter?: FilterQuery + ): Promise { + if (this.dryRun) { + this.addDryRunId(entityClass, entities); + return (await new DataBaseHelper(DryRun).updateMany(entities as unknown as DryRun[], filter as FilterQuery)); + } else { + return await new DataBaseHelper(entityClass).updateMany(entities as T[], filter); + } } /** - * Save Block State - * @param {string} policyId - policy ID - * @param {string} blockId - block UUID - * @param {string} did - user DID - * @param {string} name - variable name - * @param {unknown} value - variable value - * @param {boolean} isLongValue - if long value - * @virtual + * Update Module + * @param row */ - public async saveBlockCache( - policyId: string, - blockId: string, - did: string, - name: string, - value: unknown, - isLongValue: boolean - ): Promise { - let stateEntity = await this.findOne(BlockCache, { - policyId, - blockId, - did, - name - }); - if (stateEntity) { - stateEntity.value = value; - stateEntity.isLongValue = isLongValue; - } else { - stateEntity = this.create(BlockCache, { - policyId, - blockId, - did, - name, - value, - isLongValue - }); + public static async updateModule(row: PolicyModule): Promise { + 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`); } - await this.save(BlockCache, stateEntity); + return await dbHelper.update(row); } /** - * Save Block State - * @param policyId - * @param uuid - * @param state - * - * @virtual + * Update MultiPolicyTransaction + * @param item */ - public async saveBlockState(policyId: string, uuid: string, state: unknown): Promise { - let stateEntity = await this.findOne(BlockState, { - policyId, - blockId: uuid - }); - if (!stateEntity) { - stateEntity = this.create(BlockState, { - policyId, - blockId: uuid - }); - } - stateEntity.blockState = JSON.stringify(state); - await this.save(BlockState, stateEntity); + public static async updateMultiPolicyTransactions(item: MultiPolicyTransaction): Promise { + await new DataBaseHelper(MultiPolicyTransaction).update(item); } /** - * Save Did - * @param row - * - * @virtual - */ - public async saveDid(row: Partial): Promise { - const doc = this.create(DidDocumentCollection, row); - return await this.save(DidDocumentCollection, doc); + * Update policy + * @param model + */ + public static async updatePolicy(model: Policy): Promise { + return await new DataBaseHelper(Policy).save(model); } /** - * Save Document State - * @param row - * - * @virtual + * Update policy + * @param policyId + * @param data */ - public async saveDocumentState(row: Partial): Promise { - const item = this.create(DocumentState, row); - return await this.save(DocumentState, item); + public static async updatePolicyConfig(policyId: string, data: Policy): Promise { + const model = await new DataBaseHelper(Policy).findOne(policyId); + model.config = data.config; + model.name = data.name; + model.version = data.version; + model.description = data.description; + model.topicDescription = data.topicDescription; + model.policyRoles = data.policyRoles; + model.policyNavigation = data.policyNavigation; + model.policyTopics = data.policyTopics; + model.policyTokens = data.policyTokens; + model.policyGroups = data.policyGroups; + model.categories = data.categories; + model.projectSchema = data.projectSchema; + + return await new DataBaseHelper(Policy).save(model); } /** - * Save file - * @param uuid - * @param buffer + * Get policy tests + * @param test * - * @returns file ID + * @returns tests */ - public static async saveFile(uuid: string, buffer: Buffer): Promise { - return DataBaseHelper.saveFile(uuid, buffer); + public static async updatePolicyTest(test: PolicyTest): Promise { + return await new DataBaseHelper(PolicyTest).save(test); } /** - * Save many - * @param entityClass + * Update schema + * @param id * @param item - * @param filter */ - async saveMany(entityClass: new () => T, item: unknown[], filter?: FilterObject): Promise { - if (this.dryRun) { - this.addDryRunId(entityClass, item); - return await new DataBaseHelper(DryRun).saveMany(item, filter) as unknown as T[]; - } - return await new DataBaseHelper(entityClass).saveMany(item as Partial[], filter); + public static async updateSchema(id: string, item: SchemaCollection): Promise { + await new DataBaseHelper(SchemaCollection).update(item, { id }); } /** - * Save mint request - * @param data Mint request - * @returns Saved mint request + * Update schemas + * @param items Schemas */ - public async saveMintRequest(data: Partial): Promise { - return await this.save(MintRequest, data); + public static async updateSchemas(items: SchemaCollection[]): Promise { + await new DataBaseHelper(SchemaCollection).update(items); } /** - * Save mint transaction - * @param transaction Transaction - * @returns Saved transaction + * Update tag + * @param tag */ - public async saveMintTransaction(transaction: Partial): Promise { - return this.save(MintTransaction, transaction); + public async updateTag(tag: Tag): Promise { + return await this.update(Tag, tag.id, tag); } /** - * Save Multi Policy object - * @param multiPolicy - * @returns multiPolicy + * Update tag + * @param tag */ - public static async saveMultiPolicy(multiPolicy: MultiPolicy): Promise { - return await new DataBaseHelper(MultiPolicy).save(multiPolicy); + public static async updateTag(tag: Tag): Promise { + return await new DataBaseHelper(Tag).update(tag); } /** - * Update policies - * @param models + * Update tag cache + * @param row */ - public static async savePolicies(models: Policy[]): Promise { - return await new DataBaseHelper(Policy).saveMany(models); + public async updateTagCache(row: TagCache): Promise { + return await this.update(TagCache, row.id, row); } /** - * Save policy cache - * @param entity Entity - * @returns Policy cache + * Update tag cache + * @param row */ - public static async savePolicyCache(entity: Partial): Promise { - return await new DataBaseHelper(PolicyCache).save(entity); + public static async updateTagCache(row: TagCache): Promise { + return await new DataBaseHelper(TagCache).update(row); } /** - * Save policy cache data - * @param entity Policy cache data - * @returns Policy cache data + * Update tags + * @param tags */ - public static async savePolicyCacheData( - entity: Partial - ): Promise { - return await new DataBaseHelper(PolicyCacheData).save(entity); + public async updateTags(tags: Tag[]): Promise { + return await this.updateMany(Tag, tags); } /** - * Save schema - * @param item + * Update tags + * @param tags */ - public static async saveSchema(item: SchemaCollection): Promise { - return await new DataBaseHelper(SchemaCollection).save(item); + public static async updateTags(tags: Tag[]): Promise { + return await new DataBaseHelper(Tag).updateMany(tags); } /** - * Save schemas - * @param items + * Update tags cache + * @param rows */ - public static async saveSchemas(items: SchemaCollection[]): Promise { - return await new DataBaseHelper(SchemaCollection).saveMany(items); + public static async updateTagsCache(rows: TagCache[]): Promise { + return await new DataBaseHelper(TagCache).updateMany(rows); } /** - * Save Topic - * @param topic - * - * @virtual + * Update Theme + * @param row */ - public async saveTopic(topic: TopicCollection): Promise { - const topicObject = this.create(TopicCollection, topic); - return await this.save(TopicCollection, topicObject); + public static async updateTheme(row: Theme): Promise { + return await new DataBaseHelper(Theme).update(row); } /** - * Save topic + * Update Tool * @param row */ - public static async saveTopic(row: Partial): Promise { - return await new DataBaseHelper(TopicCollection).save(row); + public static async updateTool(row: PolicyTool): Promise { + return await new DataBaseHelper(PolicyTool).update(row); } /** - * Save VC + * Update topic * @param row - * - * @virtual */ - public async saveVC(row: Partial): Promise { - const doc = this.create(VcDocumentCollection, row); - return await this.save(VcDocumentCollection, doc); + public static async updateTopic(row: TopicCollection): Promise { + await new DataBaseHelper(TopicCollection).update(row); } /** - * Save VC + * Update VC * @param row + * + * @virtual */ - public static async saveVC(row: Partial): Promise { - return await new DataBaseHelper(VcDocumentCollection).save(row); + public async updateVC(row: VcDocumentCollection): Promise { + await this.update(VcDocumentCollection, row.id, row); + return row; } /** - * Save VCs - * @param data + * Update VP + * @param row * - * @returns VCs + * @virtual */ - // tslint:disable-next-line:adjacent-overload-signatures - public static async saveVCs(data: Partial): Promise { - return (await new DataBaseHelper(VcDocumentCollection).save(data)); + public async updateVP(row: VpDocumentCollection): Promise { + await this.update(VpDocumentCollection, row.id, row); + return row; } /** - * Save VP - * @param row - * - * @virtual + * Update VP DOcuments + * @param value + * @param filters + * @param dryRun */ - public async saveVP(row: Partial): Promise { - const doc = this.create(VpDocumentCollection, row); - return await this.save(VpDocumentCollection, doc); + public static async updateVpDocuments(value: unknown, filters: FilterQuery, dryRun?: string): Promise { + if (dryRun) { + const extendedFilters = filters as FilterQuery & { + dryRunId?: string; + dryRunClass?: string; + }; + + extendedFilters.dryRunId = dryRun; + extendedFilters.dryRunClass = 'VpDocumentCollection'; + + const items = await new DataBaseHelper(DryRun).find(extendedFilters); + + for (const item of items) { + Object.assign(item, value); + } + await new DataBaseHelper(DryRun).update(items); + } else { + const items = await new DataBaseHelper(VpDocumentCollection).find(filters); + for (const item of items) { + Object.assign(item, value); + } + await new DataBaseHelper(VpDocumentCollection).update(items); + } } /** - * Save VPs - * @param data + * Virtual Associate Token + * @param hederaAccountId + * @param token * - * @returns VPs + * @virtual */ - public static async saveVPs(data: Partial): Promise { - return (await new DataBaseHelper(VpDocumentCollection).save(data)); + public async virtualAssociate(hederaAccountId: string, token: TokenCollection): Promise { + const item = await new DataBaseHelper(DryRun).findOne({ + dryRunId: this.dryRun, + dryRunClass: 'HederaAccountInfo', + hederaAccountId + }); + if (item) { + if (item.tokenMap[token.tokenId]) { + throw new Error('Token already associated'); + } else { + item.tokenMap[token.tokenId] = { + frozen: token.enableFreeze ? false : null, + kyc: token.enableKYC ? false : null + }; + await new DataBaseHelper(DryRun).update(item); + } + } else { + const tokenMap = {}; + tokenMap[token.tokenId] = { + frozen: token.enableFreeze ? false : null, + kyc: token.enableKYC ? false : null + }; + await new DataBaseHelper(DryRun).save({ + dryRunId: this.dryRun, + dryRunClass: 'HederaAccountInfo', + hederaAccountId, + tokenMap + }); + } + return true; } /** - * Save Virtual Message - * @param dryRun - * @param message + * Virtual Dissociate Token + * @param hederaAccountId + * @param tokenId * * @virtual */ - public static async saveVirtualMessage(dryRun: string, message: Message): Promise { - const document = message.toMessage(); - const messageId = message.getId(); - const topicId = message.getTopicId(); - - await new DataBaseHelper(DryRun).save(DatabaseServer.addDryRunId({ - document, - topicId, - messageId - }, dryRun, 'Message', false)); + public async virtualDissociate(hederaAccountId: string, tokenId: string): Promise { + const item = await new DataBaseHelper(DryRun).findOne({ + dryRunId: this.dryRun, + dryRunClass: 'HederaAccountInfo', + hederaAccountId + }); + if (!item || !item.tokenMap[tokenId]) { + throw new Error('Token is not associated'); + } + delete item.tokenMap[tokenId]; + await new DataBaseHelper(DryRun).update(item); + return true; } /** - * Set Active Group - * - * @param policyId - * @param did - * @param uuid + * Virtual Freeze Token + * @param hederaAccountId + * @param tokenId * * @virtual */ - public async setActiveGroup(policyId: string, did: string, uuid: string): Promise { - const groups = await this.find(PolicyRolesCollection, {policyId, did}); - for (const group of groups) { - group.active = group.uuid === uuid; + public async virtualFreeze(hederaAccountId: string, tokenId: string): Promise { + const item = await new DataBaseHelper(DryRun).findOne({ + dryRunId: this.dryRun, + dryRunClass: 'HederaAccountInfo', + hederaAccountId + }); + if (!item || !item.tokenMap[tokenId]) { + throw new Error('Token is not associated'); } - await this.saveMany(PolicyRolesCollection, groups); + if (item.tokenMap[tokenId].frozen === null) { + throw new Error('Can not be frozen'); + } + if (item.tokenMap[tokenId].frozen === true) { + throw new Error('Token already frozen'); + } + item.tokenMap[tokenId].frozen = true; + await new DataBaseHelper(DryRun).update(item); + return true; } /** - * Set Dry Run id - * @param id + * Virtual GrantKyc Token + * @param hederaAccountId + * @param tokenId + * + * @virtual */ - public setDryRun(id: string): void { - this.dryRun = id; + public async virtualGrantKyc(hederaAccountId: string, tokenId: string): Promise { + const item = await new DataBaseHelper(DryRun).findOne({ + dryRunId: this.dryRun, + dryRunClass: 'HederaAccountInfo', + hederaAccountId + }); + if (!item || !item.tokenMap[tokenId]) { + throw new Error('Token is not associated'); + } + if (item.tokenMap[tokenId].kyc === null) { + throw new Error('Can not be granted kyc'); + } + if (item.tokenMap[tokenId].kyc === true) { + throw new Error('Token already granted kyc'); + } + item.tokenMap[tokenId].kyc = true; + await new DataBaseHelper(DryRun).update(item); + return true; } /** - * Set MultiSign Status by user - * @param uuid - * @param documentId - * @param user - * @param status - * @param document + * Virtual RevokeKyc Token + * @param hederaAccountId + * @param tokenId * * @virtual */ - public async setMultiSigDocument( - uuid: string, - documentId: string, - user: {id: string, did: string, group: string, username: string}, - status: string, - document: IVC - ): Promise { - const doc = this.create(MultiDocuments, { - uuid, - documentId, - status, - document, - userId: user.id, - did: user.did, - group: user.group, - username: user.username + public async virtualRevokeKyc(hederaAccountId: string, tokenId: string): Promise { + const item = await new DataBaseHelper(DryRun).findOne({ + dryRunId: this.dryRun, + dryRunClass: 'HederaAccountInfo', + hederaAccountId }); - await this.save(MultiDocuments, doc); - return doc; + if (!item || !item.tokenMap[tokenId]) { + throw new Error('Token is not associated'); + } + if (item.tokenMap[tokenId].kyc === null) { + throw new Error('Can not be revoked kyc'); + } + if (item.tokenMap[tokenId].kyc === false) { + throw new Error('Token already revoked kyc'); + } + item.tokenMap[tokenId].kyc = false; + await new DataBaseHelper(DryRun).update(item); + return true; } /** - * Set MultiSign Status by document - * @param uuid - * @param documentId - * @param group - * @param status + * Virtual Unfreeze Token + * @param hederaAccountId + * @param tokenId * * @virtual */ - public async setMultiSigStatus( - uuid: string, - documentId: string, - group: string, - status: string - ): Promise { - let item = await this.findOne(MultiDocuments, { - uuid: {$eq: uuid}, - documentId: {$eq: documentId}, - group: {$eq: group}, - userId: {$eq: 'Group'} + public async virtualUnfreeze(hederaAccountId: string, tokenId: string): Promise { + const item = await new DataBaseHelper(DryRun).findOne({ + dryRunId: this.dryRun, + dryRunClass: 'HederaAccountInfo', + hederaAccountId }); - if (item) { - item.status = status; - await this.update(MultiDocuments, item.id, item); - } else { - item = this.create(MultiDocuments, { - uuid, - documentId, - status, - document: null, - userId: 'Group', - did: null, - group, - username: null - }); - await this.save(MultiDocuments, item); + if (!item || !item.tokenMap[tokenId]) { + throw new Error('Token is not associated'); } - return item; + if (item.tokenMap[tokenId].frozen === null) { + throw new Error('Can not be unfrozen'); + } + if (item.tokenMap[tokenId].frozen === false) { + throw new Error('Token already unfrozen'); + } + item.tokenMap[tokenId].frozen = false; + await new DataBaseHelper(DryRun).update(item); + return true; } /** - * Set Residue objects - * @param residue + * Get mint request + * @param filters Filters + * @returns Mint request */ - public async setResidue(residue: SplitDocuments[]): Promise { - await this.saveMany(SplitDocuments, residue); + public async getMintRequests(filters: FilterObject): Promise { + return await this.find(MintRequest, filters); } /** - * Save suggestions config - * @param config - * @returns config + * Grid fs connect */ - public static async setSuggestionsConfig( - config: Partial - ): Promise { - const existingConfig = await DatabaseServer.getSuggestionsConfig( - config.user - ); - if (existingConfig) { - existingConfig.items = config.items; - } - return await new DataBaseHelper(SuggestionsConfig).save( - existingConfig || config - ); + public static connectGridFS() { + DataBaseHelper.connectGridFS(); } /** * Set Dry Run id * @param id */ - public setSystemMode(systemMode: boolean): void { - this.systemMode = systemMode; + public static async setSystemMode(dryRunId: string, systemMode: boolean): Promise { + const items = await new DataBaseHelper(DryRun).find({ dryRunId }); + for (const item of items) { + item.systemMode = systemMode; + } + await new DataBaseHelper(DryRun).update(items); } /** - * Set user in group - * - * @param group - * - * @virtual + * Create savepoint + * @param dryRunId + * @param systemMode */ - public async setUserInGroup(group: unknown): Promise { - const doc = this.create(PolicyRolesCollection, group); - await this.save(PolicyRolesCollection, doc); - return doc; + public static async createSavepoint(dryRunId: string): Promise { + const limit = { limit: DatabaseServer.DOCUMENTS_HANDLING_CHUNK_SIZE }; + const amount = await new DataBaseHelper(DryRun).count({ dryRunId }); + const naturalCount = Math.floor(amount / DatabaseServer.DOCUMENTS_HANDLING_CHUNK_SIZE); + for (let i = 0; i < naturalCount; i++) { + const items = await new DataBaseHelper(DryRun).find({ dryRunId }, limit); + for (const item of items) { + item.savepoint = true; + } + await new DataBaseHelper(DryRun).update(items); + } + const restItems = await new DataBaseHelper(DryRun).find({ dryRunId }); + for (const item of restItems) { + item.savepoint = true; + } + await new DataBaseHelper(DryRun).update(restItems); + + // const files = await new DataBaseHelper(DryRunFiles).find({ policyId: dryRunId }); + // await new DataBaseHelper(DryRunFiles).remove(files); } /** - * Save Virtual File - * @param policyId - * @param file - * @param url - * - * @virtual + * Restore savepoint + * @param dryRunId + * @param systemMode */ - public static async setVirtualFile( - policyId: string, - file: ArrayBuffer, - url: {url: string} - ): Promise { - await new DataBaseHelper(DryRun).save(DatabaseServer.addDryRunId({ - document: { - size: file?.byteLength - }, - documentURL: url?.url - }, policyId, 'Files', false)); + public static async restoreSavepoint(dryRunId: string): Promise { + const limit = { limit: DatabaseServer.DOCUMENTS_HANDLING_CHUNK_SIZE }; + const amount = await new DataBaseHelper(DryRun).count({ dryRunId, savepoint: { $exists: false } }); + const naturalCount = Math.floor(amount / DatabaseServer.DOCUMENTS_HANDLING_CHUNK_SIZE); + for (let i = 0; i < naturalCount; i++) { + const items = await new DataBaseHelper(DryRun).find({ dryRunId, savepoint: { $exists: false } }, limit); + await new DataBaseHelper(DryRun).remove(items); + } + const restItems = await new DataBaseHelper(DryRun).find({ dryRunId, savepoint: { $exists: false } }); + await new DataBaseHelper(DryRun).remove(restItems); + + // const files = await new DataBaseHelper(DryRunFiles).find({ policyId: dryRunId }); + // await new DataBaseHelper(DryRunFiles).remove(files); } /** - * Set Key from Virtual User - * @param did - * @param keyName - * @param key - * - * @virtual + * Get schemas + * @param filters */ - public async setVirtualKey(did: string, keyName: string, key: string): Promise { - await new DataBaseHelper(DryRun).save({ - dryRunId: this.dryRun, - dryRunClass: 'VirtualKey', - did, - type: keyName, - hederaAccountKey: key - } as Partial); + public static async getSchema(filters?: FilterObject | string): Promise { + return await new DataBaseHelper(SchemaCollection).findOne(filters); } /** - * Save Virtual Transaction - * @param policyId - * @param type - * @param operatorId - * - * @virtual + * Create Statistic + * @param statistic */ - public static async setVirtualTransaction( - policyId: string, - type: string, - operatorId?: string - ): Promise { - await new DataBaseHelper(DryRun).save(DatabaseServer.addDryRunId({ - type, - hederaAccountId: operatorId - }, policyId, 'Transactions', false)); + public static async createStatistic( + statistic: FilterObject + ): Promise { + const item = new DataBaseHelper(PolicyStatistic).create(statistic); + return await new DataBaseHelper(PolicyStatistic).save(item); + } + + /** + * Get Statistics + * @param filters + * @param options + */ + public static async getStatisticsAndCount( + filters?: FilterObject, + options?: FindOptions + ): Promise<[PolicyStatistic[], number]> { + return await new DataBaseHelper(PolicyStatistic).findAndCount(filters, options); } /** - * Set Current Virtual User - * @param policyId - * @param did - * - * @virtual + * Get Statistic By ID + * @param id */ - public static async setVirtualUser(policyId: string, did: string): Promise { - const items = (await new DataBaseHelper(DryRun).find({ - dryRunId: policyId, - dryRunClass: 'VirtualUsers' - })); - for (const item of items) { - item.active = item.did === did; - await new DataBaseHelper(DryRun).save(item); - } + public static async getStatisticById(id: string): Promise { + return await new DataBaseHelper(PolicyStatistic).findOne(id); } /** - * Overriding the update method - * @param entityClass - * @param criteria - * @param row + * Get Statistic + * @param filters */ - async update( - entityClass: new () => T, - criteria: FilterQuery, - row: unknown | unknown[] - ): Promise { - if (Array.isArray(criteria)) { - return await this.updateMany(entityClass, row as unknown as T[], criteria) as any; - } + public static async getStatistic(filters: FilterQuery): Promise { + return await new DataBaseHelper(PolicyStatistic).findOne(filters); + } - if (this.dryRun) { - this.addDryRunId(entityClass, row); - return (await new DataBaseHelper(DryRun).update(row as DryRun, criteria as FilterQuery)) as unknown as T; - } else { - return await new DataBaseHelper(entityClass).update(row as T, criteria); - } + /** + * Delete Statistic + * @param statistic + */ + public static async removeStatistic(statistic: PolicyStatistic): Promise { + return await new DataBaseHelper(PolicyStatistic).remove(statistic); } /** - * Update Approval VC - * @param row - * - * @virtual + * Get Statistics + * @param filters + * @param options */ - public async updateApproval(row: ApprovalDocumentCollection): Promise { - await this.update(ApprovalDocumentCollection, row.id, row); - return row; + public static async getStatistics( + filters?: FilterQuery, + options?: unknown + ): Promise { + return await new DataBaseHelper(PolicyStatistic).find(filters, options); } /** - * Update Did + * Update Statistic * @param row - * - * @virtual */ - public async updateDid(row: DidDocumentCollection): Promise { - await this.update(DidDocumentCollection, row.id, row); - return row; + public static async updateStatistic(row: PolicyStatistic): Promise { + return await new DataBaseHelper(PolicyStatistic).update(row); } /** - * Update External Topic - * @param item - * - * @virtual + * Get documents + * @param filters + * @param options */ - public async updateExternalTopic(item: ExternalDocument): Promise { - return await this.save(ExternalDocument, item); + public static async getStatisticDocumentsAndCount( + filters?: FilterObject, + options?: FindOptions + ): Promise<[VcDocumentCollection[], number]> { + return await new DataBaseHelper(VcDocumentCollection).findAndCount(filters, options); } /** - * Update many method - * @param entityClass - * @param entities - * @param filter + * Get documents + * @param filters + * @param options */ - async updateMany( - entityClass: new () => T, - entities: T[], - filter?: FilterQuery - ): Promise { - if (this.dryRun) { - this.addDryRunId(entityClass, entities); - return (await new DataBaseHelper(DryRun).updateMany(entities as unknown as DryRun[], filter as FilterQuery)); - } else { - return await new DataBaseHelper(entityClass).updateMany(entities as T[], filter); - } + public static async getStatisticDocuments( + filters?: FilterQuery, + options?: unknown + ): Promise { + return await new DataBaseHelper(VcDocumentCollection).find(filters, options); } /** - * Update Module - * @param row + * Get document + * @param filters + * @param options */ - public static async updateModule(row: PolicyModule): Promise { - 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); + public static async getStatisticDocument( + filters?: FilterQuery, + options?: unknown + ): Promise { + return await new DataBaseHelper(VcDocumentCollection).findOne(filters, options); } /** - * Update MultiPolicyTransaction - * @param item + * Create Statistic + * @param assessment */ - public static async updateMultiPolicyTransactions(item: MultiPolicyTransaction): Promise { - await new DataBaseHelper(MultiPolicyTransaction).update(item); + public static async createStatisticAssessment( + assessment: FilterObject + ): Promise { + const item = new DataBaseHelper(PolicyStatisticDocument).create(assessment); + return await new DataBaseHelper(PolicyStatisticDocument).save(item); } /** - * Update policy - * @param model + * Get statistic assessment + * @param filters */ - public static async updatePolicy(model: Policy): Promise { - return await new DataBaseHelper(Policy).save(model); + public static async getStatisticAssessment( + filters: FilterQuery + ): Promise { + return await new DataBaseHelper(PolicyStatisticDocument).findOne(filters); } /** - * Update policy - * @param policyId - * @param data + * Get statistic assessments + * @param filters + * @param options */ - public static async updatePolicyConfig(policyId: string, data: Policy): Promise { - const model = await new DataBaseHelper(Policy).findOne(policyId); - model.config = data.config; - model.name = data.name; - model.version = data.version; - model.description = data.description; - model.topicDescription = data.topicDescription; - model.policyRoles = data.policyRoles; - model.policyNavigation = data.policyNavigation; - model.policyTopics = data.policyTopics; - model.policyTokens = data.policyTokens; - model.policyGroups = data.policyGroups; - model.categories = data.categories; - model.projectSchema = data.projectSchema; - - return await new DataBaseHelper(Policy).save(model); + public static async getStatisticAssessmentsAndCount( + filters?: FilterObject, + options?: FindOptions + ): Promise<[PolicyStatisticDocument[], number]> { + return await new DataBaseHelper(PolicyStatisticDocument).findAndCount(filters, options); } /** - * Get policy tests - * @param test - * - * @returns tests + * Get statistic assessment count + * @param filters */ - public static async updatePolicyTest(test: PolicyTest): Promise { - return await new DataBaseHelper(PolicyTest).save(test); + public static async getStatisticAssessmentCount( + filters?: FilterObject + ): Promise { + return await new DataBaseHelper(PolicyStatisticDocument).count(filters); } /** - * Update schema - * @param id - * @param item + * Create Schema Rule + * @param rule */ - public static async updateSchema(id: string, item: SchemaCollection): Promise { - await new DataBaseHelper(SchemaCollection).update(item, {id}); + public static async createSchemaRule( + rule: FilterObject + ): Promise { + const item = new DataBaseHelper(SchemaRule).create(rule); + return await new DataBaseHelper(SchemaRule).save(item); } /** - * Update schemas - * @param items Schemas + * Get Schema Rule + * @param filters + * @param options */ - public static async updateSchemas(items: SchemaCollection[]): Promise { - await new DataBaseHelper(SchemaCollection).update(items); + public static async getSchemaRulesAndCount( + filters?: FilterObject, + options?: FindOptions + ): Promise<[SchemaRule[], number]> { + return await new DataBaseHelper(SchemaRule).findAndCount(filters, options); } /** - * Update tag - * @param tag + * Get Schema Rule + * @param filters + * @param options */ - public async updateTag(tag: Tag): Promise { - return await this.update(Tag, tag.id, tag); + public static async getSchemaRules( + filters?: FilterObject, + options?: unknown + ): Promise { + return await new DataBaseHelper(SchemaRule).find(filters, options); } /** - * Update tag - * @param tag + * Get Schema Rule By ID + * @param id */ - public static async updateTag(tag: Tag): Promise { - return await new DataBaseHelper(Tag).update(tag); + public static async getSchemaRuleById(id: string): Promise { + return await new DataBaseHelper(SchemaRule).findOne(id); } /** - * Update tag cache - * @param row + * Update Schema Rule + * @param rule */ - public async updateTagCache(row: TagCache): Promise { - return await this.update(TagCache, row.id, row); + public static async updateSchemaRule(rule: SchemaRule): Promise { + return await new DataBaseHelper(SchemaRule).update(rule); } /** - * Update tag cache - * @param row + * Delete Schema Rule + * @param rule */ - public static async updateTagCache(row: TagCache): Promise { - return await new DataBaseHelper(TagCache).update(row); + public static async removeSchemaRule(rule: SchemaRule): Promise { + return await new DataBaseHelper(SchemaRule).remove(rule); } /** - * Update tags - * @param tags + * Create Policy Label + * @param label */ - public async updateTags(tags: Tag[]): Promise { - return await this.updateMany(Tag, tags); + public static async createPolicyLabel( + label: FilterObject + ): Promise { + const item = new DataBaseHelper(PolicyLabel).create(label); + return await new DataBaseHelper(PolicyLabel).save(item); } /** - * Update tags - * @param tags + * Get Policy Label + * @param filters + * @param options */ - public static async updateTags(tags: Tag[]): Promise { - return await new DataBaseHelper(Tag).updateMany(tags); + public static async getPolicyLabelsAndCount( + filters?: FilterObject, + options?: FindOptions + ): Promise<[PolicyLabel[], number]> { + return await new DataBaseHelper(PolicyLabel).findAndCount(filters, options); } /** - * Update tags cache - * @param rows + * Get Policy Label + * @param filters + * @param options */ - public static async updateTagsCache(rows: TagCache[]): Promise { - return await new DataBaseHelper(TagCache).updateMany(rows); + public static async getPolicyLabels( + filters?: FilterObject, + options?: unknown + ): Promise { + return await new DataBaseHelper(PolicyLabel).find(filters, options); } /** - * Update Theme - * @param row + * Get Policy Label By ID + * @param id */ - public static async updateTheme(row: Theme): Promise { - return await new DataBaseHelper(Theme).update(row); + public static async getPolicyLabelById(id: string): Promise { + return await new DataBaseHelper(PolicyLabel).findOne(id); } /** - * Update Tool - * @param row + * Update Policy Label + * @param label */ - public static async updateTool(row: PolicyTool): Promise { - return await new DataBaseHelper(PolicyTool).update(row); + public static async updatePolicyLabel(label: PolicyLabel): Promise { + return await new DataBaseHelper(PolicyLabel).update(label); } /** - * Update topic - * @param row + * Delete Policy Label + * @param label */ - public static async updateTopic(row: TopicCollection): Promise { - await new DataBaseHelper(TopicCollection).update(row); + public static async removePolicyLabel(label: PolicyLabel): Promise { + return await new DataBaseHelper(PolicyLabel).remove(label); } /** - * Update VC - * @param row - * - * @virtual + * Create Label Document + * @param document */ - public async updateVC(row: VcDocumentCollection): Promise { - await this.update(VcDocumentCollection, row.id, row); - return row; + public static async createLabelDocument( + document: FilterObject + ): Promise { + const item = new DataBaseHelper(PolicyLabelDocument).create(document); + return await new DataBaseHelper(PolicyLabelDocument).save(item); } /** - * Update VP - * @param row - * - * @virtual + * Get statistic assessments + * @param filters + * @param options */ - public async updateVP(row: VpDocumentCollection): Promise { - await this.update(VpDocumentCollection, row.id, row); - return row; + public static async getLabelDocumentsAndCount( + filters?: FilterObject, + options?: FindOptions + ): Promise<[PolicyLabelDocument[], number]> { + return await new DataBaseHelper(PolicyLabelDocument).findAndCount(filters, options); } /** - * Update VP DOcuments - * @param value + * Get statistic assessment * @param filters - * @param dryRun */ - public static async updateVpDocuments(value: unknown, filters: FilterQuery, dryRun?: string): Promise { - if (dryRun) { - const extendedFilters = filters as FilterQuery & { - dryRunId?: string; - dryRunClass?: string; - }; - - extendedFilters.dryRunId = dryRun; - extendedFilters.dryRunClass = 'VpDocumentCollection'; - - const items = await new DataBaseHelper(DryRun).find(extendedFilters); + public static async getLabelDocument( + filters: FilterQuery + ): Promise { + return await new DataBaseHelper(PolicyLabelDocument).findOne(filters); + } - for (const item of items) { - Object.assign(item, value); - } - await new DataBaseHelper(DryRun).update(items); - } else { - const items = await new DataBaseHelper(VpDocumentCollection).find(filters); - for (const item of items) { - Object.assign(item, value); - } - await new DataBaseHelper(VpDocumentCollection).update(items); + /** + * Restore States + */ + public static async restoreStates(policyId: string): Promise { + const states = await new DataBaseHelper(BlockState).find({ policyId }); + for (const state of states) { + state.blockState = state.savedState; + await new DataBaseHelper(BlockState).save(state); } } /** - * Virtual Associate Token - * @param hederaAccountId - * @param token - * - * @virtual + * Copy States */ - public async virtualAssociate(hederaAccountId: string, token: TokenCollection): Promise { - const item = await new DataBaseHelper(DryRun).findOne({ - dryRunId: this.dryRun, - dryRunClass: 'HederaAccountInfo', - hederaAccountId - }); - if (item) { - if (item.tokenMap[token.tokenId]) { - throw new Error('Token already associated'); - } else { - item.tokenMap[token.tokenId] = { - frozen: token.enableFreeze ? false : null, - kyc: token.enableKYC ? false : null - }; - await new DataBaseHelper(DryRun).update(item); - } - } else { - const tokenMap = {}; - tokenMap[token.tokenId] = { - frozen: token.enableFreeze ? false : null, - kyc: token.enableKYC ? false : null - }; - await new DataBaseHelper(DryRun).save({ - dryRunId: this.dryRun, - dryRunClass: 'HederaAccountInfo', - hederaAccountId, - tokenMap - }); + public static async copyStates(policyId: string): Promise { + const states = await new DataBaseHelper(BlockState).find({ policyId }); + for (const state of states) { + state.savedState = state.blockState; + await new DataBaseHelper(BlockState).save(state); } - return true; } /** - * Virtual Dissociate Token - * @param hederaAccountId - * @param tokenId - * - * @virtual + * Create Formula + * @param formula */ - public async virtualDissociate(hederaAccountId: string, tokenId: string): Promise { - const item = await new DataBaseHelper(DryRun).findOne({ - dryRunId: this.dryRun, - dryRunClass: 'HederaAccountInfo', - hederaAccountId - }); - if (!item || !item.tokenMap[tokenId]) { - throw new Error('Token is not associated'); - } - delete item.tokenMap[tokenId]; - await new DataBaseHelper(DryRun).update(item); - return true; + public static async createFormula( + formula: FilterObject + ): Promise { + const item = new DataBaseHelper(Formula).create(formula); + return await new DataBaseHelper(Formula).save(item); } /** - * Virtual Freeze Token - * @param hederaAccountId - * @param tokenId - * - * @virtual + * Get Formulas + * @param filters + * @param options */ - public async virtualFreeze(hederaAccountId: string, tokenId: string): Promise { - const item = await new DataBaseHelper(DryRun).findOne({ - dryRunId: this.dryRun, - dryRunClass: 'HederaAccountInfo', - hederaAccountId - }); - if (!item || !item.tokenMap[tokenId]) { - throw new Error('Token is not associated'); - } - if (item.tokenMap[tokenId].frozen === null) { - throw new Error('Can not be frozen'); - } - if (item.tokenMap[tokenId].frozen === true) { - throw new Error('Token already frozen'); - } - item.tokenMap[tokenId].frozen = true; - await new DataBaseHelper(DryRun).update(item); - return true; + public static async getFormulasAndCount( + filters?: FilterObject, + options?: FindOptions + ): Promise<[Formula[], number]> { + return await new DataBaseHelper(Formula).findAndCount(filters, options); } /** - * Virtual GrantKyc Token - * @param hederaAccountId - * @param tokenId - * - * @virtual + * Get Formulas + * @param filters + * @param options */ - public async virtualGrantKyc(hederaAccountId: string, tokenId: string): Promise { - const item = await new DataBaseHelper(DryRun).findOne({ - dryRunId: this.dryRun, - dryRunClass: 'HederaAccountInfo', - hederaAccountId - }); - if (!item || !item.tokenMap[tokenId]) { - throw new Error('Token is not associated'); - } - if (item.tokenMap[tokenId].kyc === null) { - throw new Error('Can not be granted kyc'); - } - if (item.tokenMap[tokenId].kyc === true) { - throw new Error('Token already granted kyc'); - } - item.tokenMap[tokenId].kyc = true; - await new DataBaseHelper(DryRun).update(item); - return true; + public static async getFormulas( + filters?: FilterObject, + options?: unknown + ): Promise { + return await new DataBaseHelper(Formula).find(filters, options); } /** - * Virtual RevokeKyc Token - * @param hederaAccountId - * @param tokenId - * - * @virtual + * Get Formula By ID + * @param id */ - public async virtualRevokeKyc(hederaAccountId: string, tokenId: string): Promise { - const item = await new DataBaseHelper(DryRun).findOne({ - dryRunId: this.dryRun, - dryRunClass: 'HederaAccountInfo', - hederaAccountId - }); - if (!item || !item.tokenMap[tokenId]) { - throw new Error('Token is not associated'); - } - if (item.tokenMap[tokenId].kyc === null) { - throw new Error('Can not be revoked kyc'); - } - if (item.tokenMap[tokenId].kyc === false) { - throw new Error('Token already revoked kyc'); - } - item.tokenMap[tokenId].kyc = false; - await new DataBaseHelper(DryRun).update(item); - return true; + public static async getFormulaById(id: string): Promise { + return await new DataBaseHelper(Formula).findOne(id); } /** - * Virtual Unfreeze Token - * @param hederaAccountId - * @param tokenId - * - * @virtual + * Update Formula + * @param formula */ - public async virtualUnfreeze(hederaAccountId: string, tokenId: string): Promise { - const item = await new DataBaseHelper(DryRun).findOne({ - dryRunId: this.dryRun, - dryRunClass: 'HederaAccountInfo', - hederaAccountId - }); - if (!item || !item.tokenMap[tokenId]) { - throw new Error('Token is not associated'); - } - if (item.tokenMap[tokenId].frozen === null) { - throw new Error('Can not be unfrozen'); - } - if (item.tokenMap[tokenId].frozen === false) { - throw new Error('Token already unfrozen'); - } - item.tokenMap[tokenId].frozen = false; - await new DataBaseHelper(DryRun).update(item); - return true; + public static async updateFormula(formula: Formula): Promise { + return await new DataBaseHelper(Formula).update(formula); } /** - * Get mint request - * @param filters Filters - * @returns Mint request + * Delete Formula + * @param formula */ - public async getMintRequests(filters: FilterObject): Promise { - return await this.find(MintRequest, filters); + public static async removeFormula(formula: Formula): Promise { + return await new DataBaseHelper(Formula).remove(formula); } } diff --git a/common/src/entity/formula.ts b/common/src/entity/formula.ts new file mode 100644 index 0000000000..99461dd883 --- /dev/null +++ b/common/src/entity/formula.ts @@ -0,0 +1,96 @@ +import { BeforeCreate, Entity, Property } from '@mikro-orm/core'; +import { BaseEntity } from '../models/index.js'; +import { EntityStatus, GenerateUUIDv4, IFormula, IFormulaConfig } from '@guardian/interfaces'; + +/** + * Formula collection + */ +@Entity() +export class Formula extends BaseEntity implements IFormula { + /** + * ID + */ + @Property({ nullable: true }) + uuid?: string; + + /** + * Label + */ + @Property({ nullable: true }) + name?: string; + + /** + * Description + */ + @Property({ nullable: true }) + description?: string; + + /** + * Owner + */ + @Property({ + nullable: true, + index: true + }) + owner?: string; + + /** + * Creator + */ + @Property({ nullable: true }) + creator?: string; + + /** + * Status + */ + @Property({ nullable: true }) + status?: EntityStatus; + + /** + * Message id + */ + @Property({ nullable: true }) + messageId?: string; + + /** + * Policy id + */ + @Property({ + nullable: true, + index: true + }) + policyId?: string; + + /** + * Policy id + */ + @Property({ + nullable: true, + index: true + }) + policyTopicId?: string; + + /** + * Policy Instance Topic id + */ + @Property({ + nullable: true, + index: true + }) + policyInstanceTopicId?: string; + + /** + * Config + */ + @Property({ nullable: true, type: 'unknown' }) + config?: IFormulaConfig; + + /** + * Set defaults + */ + @BeforeCreate() + setDefaults() { + this.uuid = this.uuid || GenerateUUIDv4(); + this.status = this.status || EntityStatus.DRAFT; + } +} \ No newline at end of file diff --git a/common/src/entity/index.ts b/common/src/entity/index.ts index 4665137b12..fc50a08d76 100644 --- a/common/src/entity/index.ts +++ b/common/src/entity/index.ts @@ -47,4 +47,5 @@ export * from './policy-statistic.js'; export * from './policy-statistic-document.js'; export * from './schema-rule.js'; export * from './policy-label.js'; -export * from './policy-label-document.js'; \ No newline at end of file +export * from './policy-label-document.js'; +export * from './formula.js'; \ No newline at end of file diff --git a/common/src/hedera-modules/message/formula-message.ts b/common/src/hedera-modules/message/formula-message.ts new file mode 100644 index 0000000000..0e011a4d99 --- /dev/null +++ b/common/src/hedera-modules/message/formula-message.ts @@ -0,0 +1,202 @@ +import { Message } from './message.js'; +import { IURL, UrlType } from './url.interface.js'; +import { MessageAction } from './message-action.js'; +import { MessageType } from './message-type.js'; +import { FormulaMessageBody } from './message-body.interface.js'; +import { Formula } from '../../entity/index.js'; +import { IPFS } from '../../helpers/index.js'; + +/** + * Formula message + */ +export class FormulaMessage extends Message { + /** + * Name + */ + public name: string; + /** + * Description + */ + public description: string; + /** + * Owner + */ + public owner: string; + /** + * UUID + */ + public uuid: string; + /** + * Policy topic id + */ + public policyTopicId: string; + /** + * Policy Instance topic id + */ + public policyInstanceTopicId: string; + + /** + * Document + */ + public config: ArrayBuffer; + + constructor(action: MessageAction) { + super(action, MessageType.Formula); + } + + /** + * Set document + * @param item + */ + public setDocument(item: Formula, zip: ArrayBuffer): void { + this.name = item.name; + this.description = item.description; + this.owner = item.owner; + this.uuid = item.uuid; + this.policyTopicId = item.policyTopicId; + this.policyInstanceTopicId = item.policyInstanceTopicId; + this.config = zip; + } + + /** + * Get document + */ + public getDocument(): ArrayBuffer { + return this.config; + } + + /** + * To message object + */ + public override toMessageObject(): FormulaMessageBody { + return { + id: null, + status: null, + type: this.type, + action: this.action, + lang: this.lang, + name: this.name, + description: this.description, + owner: this.owner, + uuid: this.uuid, + policyTopicId: this.policyTopicId, + policyInstanceTopicId: this.policyInstanceTopicId, + cid: this.getDocumentUrl(UrlType.cid), + uri: this.getDocumentUrl(UrlType.url), + }; + } + + /** + * To documents + */ + public async toDocuments(): Promise { + if (this.config) { + return [this.config]; + } + return []; + } + + /** + * Load documents + * @param documents + */ + public loadDocuments(documents: string[]): FormulaMessage { + if (documents && documents.length === 1) { + this.config = Buffer.from(documents[0]); + } + return this; + } + + /** + * From message + * @param message + */ + public static fromMessage(message: string): FormulaMessage { + if (!message) { + throw new Error('Message Object is empty'); + } + + const json = JSON.parse(message); + return FormulaMessage.fromMessageObject(json); + } + + /** + * From message object + * @param json + */ + public static fromMessageObject(json: FormulaMessageBody): FormulaMessage { + if (!json) { + throw new Error('JSON Object is empty'); + } + + let message = new FormulaMessage(json.action); + message = Message._fromMessageObject(message, json); + message._id = json.id; + message._status = json.status; + message.name = json.name; + message.description = json.description; + message.owner = json.owner; + message.uuid = json.uuid; + message.policyTopicId = json.policyTopicId; + message.policyInstanceTopicId = json.policyInstanceTopicId; + const urls = [ + { + cid: json.cid, + url: IPFS.IPFS_PROTOCOL + json.cid, + }, + ]; + message.setUrls(urls); + return message; + } + + /** + * Get URL + */ + public override getUrl(): IURL[] { + return this.getUrls(); + } + + /** + * Get document URL + * @param type + */ + public getDocumentUrl(type: UrlType): string | null { + return this.getUrlValue(0, type); + } + + /** + * Get context URL + * @param type + */ + public getContextUrl(type: UrlType): string | null { + return this.getUrlValue(1, type); + } + + /** + * Validate + */ + public override validate(): boolean { + return true; + } + + /** + * To JSON + */ + public override toJson(): any { + const result = super.toJson(); + result.name = this.name; + result.description = this.description; + result.owner = this.owner; + result.uuid = this.uuid; + result.policyTopicId = this.policyTopicId; + result.policyInstanceTopicId = this.policyInstanceTopicId; + result.config = this.config; + } + + /** + * Get User DID + */ + public override getOwner(): string { + return this.owner; + } +} diff --git a/common/src/hedera-modules/message/index.ts b/common/src/hedera-modules/message/index.ts index afbee0dacd..15226ac62c 100644 --- a/common/src/hedera-modules/message/index.ts +++ b/common/src/hedera-modules/message/index.ts @@ -22,4 +22,5 @@ export { UserPermissionsMessage } from './user-permissions-message.js'; export { StatisticMessage } from './statistic-message.js'; export { StatisticAssessmentMessage } from './statistic-assessment-message.js'; export { LabelMessage } from './label-message.js'; -export { LabelDocumentMessage } from './label-document-message.js'; \ No newline at end of file +export { LabelDocumentMessage } from './label-document-message.js'; +export { FormulaMessage } from './formula-message.js'; \ No newline at end of file diff --git a/common/src/hedera-modules/message/message-action.ts b/common/src/hedera-modules/message/message-action.ts index 302737bff9..daadbf8221 100644 --- a/common/src/hedera-modules/message/message-action.ts +++ b/common/src/hedera-modules/message/message-action.ts @@ -39,4 +39,5 @@ export enum MessageAction { CreateStatisticAssessment = 'create-assessment-document', PublishPolicyLabel = 'publish-policy-label', CreateLabelDocument = 'create-label-document', + PublishFormula = 'publish-formula', } \ No newline at end of file diff --git a/common/src/hedera-modules/message/message-body.interface.ts b/common/src/hedera-modules/message/message-body.interface.ts index e74123af45..a8a8e02584 100644 --- a/common/src/hedera-modules/message/message-body.interface.ts +++ b/common/src/hedera-modules/message/message-body.interface.ts @@ -642,6 +642,44 @@ export interface LabelMessageBody extends MessageBody { uri: string; } +/** + * Formula message body + */ +export interface FormulaMessageBody extends MessageBody { + /** + * UUID + */ + uuid: string; + /** + * Name + */ + name: string; + /** + * Description + */ + description: string; + /** + * Owner + */ + owner: string; + /** + * Policy topic ID + */ + policyTopicId: string; + /** + * Policy instance topic ID + */ + policyInstanceTopicId: string; + /** + * CID + */ + cid: string; + /** + * URI + */ + uri: string; +} + /** * Statistic Assessment message body */ diff --git a/common/src/hedera-modules/message/message-server.ts b/common/src/hedera-modules/message/message-server.ts index f615ca0c5d..769f9af904 100644 --- a/common/src/hedera-modules/message/message-server.ts +++ b/common/src/hedera-modules/message/message-server.ts @@ -25,6 +25,7 @@ import { GuardianRoleMessage } from './guardian-role-message.js'; import { UserPermissionsMessage } from './user-permissions-message.js'; import { StatisticMessage } from './statistic-message.js'; import { LabelMessage } from './label-message.js'; +import { FormulaMessage } from './formula-message.js'; /** * Message server @@ -315,6 +316,9 @@ export class MessageServer { case MessageType.PolicyLabel: message = LabelMessage.fromMessageObject(json); break; + case MessageType.Formula: + message = FormulaMessage.fromMessageObject(json); + break; // Default schemas case 'schema-document': message = SchemaMessage.fromMessageObject(json); diff --git a/common/src/hedera-modules/message/message-type.ts b/common/src/hedera-modules/message/message-type.ts index 53ea82402b..f6fb25210b 100644 --- a/common/src/hedera-modules/message/message-type.ts +++ b/common/src/hedera-modules/message/message-type.ts @@ -21,5 +21,6 @@ export enum MessageType { Contract = 'Contract', UserPermissions = 'User-Permissions', PolicyStatistic = 'Policy-Statistic', - PolicyLabel = 'Policy-Label' + PolicyLabel = 'Policy-Label', + Formula = 'Formula' } diff --git a/common/src/import-export/formula.ts b/common/src/import-export/formula.ts new file mode 100644 index 0000000000..0b8c8af7db --- /dev/null +++ b/common/src/import-export/formula.ts @@ -0,0 +1,107 @@ +import JSZip from 'jszip'; +import { Formula } from '../entity/index.js'; +import { IFormulaConfig } from '@guardian/interfaces'; + +/** + * Formula components + */ +export interface IFormulaComponents { + formula: Formula; +} + +/** + * Formula import export + */ +export class FormulaImportExport { + /** + * Formula filename + */ + public static readonly formulaFileName = 'formula.json'; + + /** + * Load Formula components + * @param formula Formula + * + * @returns components + */ + public static async loadFormulaComponents(formula: Formula): Promise { + return { formula }; + } + + /** + * Generate Zip File + * @param formula formula + * + * @returns Zip file + */ + public static async generate(formula: Formula): Promise { + const components = await FormulaImportExport.loadFormulaComponents(formula); + const file = await FormulaImportExport.generateZipFile(components); + return file; + } + + /** + * Generate Zip File + * @param components formula components + * + * @returns Zip file + */ + public static async generateZipFile(components: IFormulaComponents): Promise { + const object = { ...components.formula }; + delete object.id; + delete object._id; + delete object.owner; + delete object.createDate; + delete object.updateDate; + const zip = new JSZip(); + zip.file(FormulaImportExport.formulaFileName, JSON.stringify(object)); + return zip; + } + + /** + * Parse zip formula file + * @param zipFile Zip file + * @returns Parsed formula + */ + public static async parseZipFile(zipFile: any): Promise { + const zip = new JSZip(); + const content = await zip.loadAsync(zipFile); + if ( + !content.files[FormulaImportExport.formulaFileName] || + content.files[FormulaImportExport.formulaFileName].dir + ) { + throw new Error('Zip file is not a formula'); + } + const formulaString = await content.files[FormulaImportExport.formulaFileName].async('string'); + const formula = JSON.parse(formulaString); + return { formula }; + } + + /** + * Validate Config + * + * @param data config + */ + public static validateConfig(data?: IFormulaConfig): IFormulaConfig { + return data; + } + + /** + * Replace Ids + * + * @param data config + */ + public static replaceIds(data: IFormulaConfig, oldId: string, newId: string): IFormulaConfig { + if (data) { + const formulas = data.formulas; + if (Array.isArray(formulas)) { + for (const component of formulas) { + if (component.link && component.link.entityId === oldId) { + component.link.entityId = newId; + } + } + } + } + return data; + } +} diff --git a/common/src/import-export/index.ts b/common/src/import-export/index.ts index e20ed2504a..ed65502035 100644 --- a/common/src/import-export/index.ts +++ b/common/src/import-export/index.ts @@ -7,4 +7,5 @@ export * from './record.js'; export * from './utils.js'; export * from './schema-rule.js'; export * from './policy-statistic.js'; -export * from './policy-label.js'; \ No newline at end of file +export * from './policy-label.js'; +export * from './formula.js'; \ No newline at end of file diff --git a/common/src/import-export/policy.ts b/common/src/import-export/policy.ts index 1fa9506321..e0f16ea8a8 100644 --- a/common/src/import-export/policy.ts +++ b/common/src/import-export/policy.ts @@ -1,5 +1,5 @@ import JSZip from 'jszip'; -import { Artifact, Policy, PolicyCategory, PolicyTool, Schema, Tag, Token } from '../entity/index.js'; +import { Artifact, Formula, Policy, PolicyCategory, PolicyTool, Schema, Tag, Token } from '../entity/index.js'; import { DatabaseServer } from '../database-modules/index.js'; import { ImportExportUtils } from './utils.js'; import { PolicyCategoryExport } from '@guardian/interfaces'; @@ -18,6 +18,7 @@ export interface IPolicyComponents { policy: Policy; tokens: Token[]; schemas: Schema[]; + formulas: Formula[]; artifacts: IArtifact[]; tags: Tag[]; tools: PolicyTool[]; @@ -116,7 +117,9 @@ export class PolicyImportExport { const allCategories = await DatabaseServer.getPolicyCategories(); policy.categoriesExport = policy.categories?.length ? PolicyImportExport.getPolicyCategoriesExport(policy, allCategories) : []; - return { policy, tokens, schemas, tools, artifacts, tags, tests }; + const formulas = await dataBaseServer.find(Formula, { policyId: policy.id }); + + return { policy, tokens, schemas, tools, artifacts, tags, tests, formulas }; } /** @@ -234,6 +237,16 @@ export class PolicyImportExport { zip.file(`tests/${test.uuid}.record`, test.data); } + zip.folder('formulas'); + for (const formula of components.formulas) { + const item = { ...formula }; + delete item._id; + delete item.id; + delete item.status; + item.id = formula.id.toString(); + zip.file(`formulas/${item.uuid}.json`, JSON.stringify(item)); + } + zip.file(PolicyImportExport.policyFileName, JSON.stringify(policyObject)); return zip; } @@ -253,17 +266,19 @@ export class PolicyImportExport { const policy = JSON.parse(policyString); const fileEntries = Object.entries(content.files).filter(file => !file[1].dir); - const [tokensStringArray, schemasStringArray, toolsStringArray, tagsStringArray] = await Promise.all([ + const [tokensStringArray, schemasStringArray, toolsStringArray, tagsStringArray, formulasStringArray] = await Promise.all([ Promise.all(fileEntries.filter(file => /^tokens\/.+/.test(file[0])).map(file => file[1].async('string'))), Promise.all(fileEntries.filter(file => /^schem[a,e]s\/.+/.test(file[0])).map(file => file[1].async('string'))), Promise.all(fileEntries.filter(file => /^tools\/.+/.test(file[0])).map(file => file[1].async('string'))), - Promise.all(fileEntries.filter(file => /^tags\/.+/.test(file[0])).map(file => file[1].async('string'))) + Promise.all(fileEntries.filter(file => /^tags\/.+/.test(file[0])).map(file => file[1].async('string'))), + Promise.all(fileEntries.filter(file => /^formulas\/.+/.test(file[0])).map(file => file[1].async('string'))) ]); const tokens = tokensStringArray.map(item => JSON.parse(item)); const schemas = schemasStringArray.map(item => JSON.parse(item)); const tools = toolsStringArray.map(item => JSON.parse(item)); const tags = tagsStringArray.map(item => JSON.parse(item)); + const formulas = formulasStringArray.map(item => JSON.parse(item)); const metaDataFile = (Object.entries(content.files).find(file => file[0] === 'artifacts/metadata.json')); const metaDataString = metaDataFile && await metaDataFile[1].async('string') || '[]'; @@ -309,7 +324,7 @@ export class PolicyImportExport { policy.categoriesExport = []; } - return { policy, tokens, schemas, artifacts, tags, tools, tests }; + return { policy, tokens, schemas, artifacts, tags, tools, tests, formulas }; } /** @@ -359,4 +374,18 @@ export class PolicyImportExport { return policyCategoryIds; } -} + /** + * Load all schemas (deep find) + * @param policy policy + * + * @returns schemas + */ + public static async fastLoadSchemas(policy: Policy) { + const topicId = policy.topicId; + const tools: any[] = policy.tools || []; + const toolsTopicMap = tools.map((t) => t.topicId); + const schemas = await new DatabaseServer().find(Schema, { topicId, readonly: false }); + const toolSchemas = await DatabaseServer.getSchemas({ topicId: { $in: toolsTopicMap } }); + return { schemas, toolSchemas }; + } +} \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index 8afa39f1fe..bf811da72d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,6 +26,7 @@ "js-yaml": "^4.1.0", "leader-line": "^1.0.7", "mathjs": "^13.1.1", + "mathlive": "^0.103.0", "moment": "^2.29.2", "moment-timezone": "^0.5.43", "ngx-colors": "3.1.4", diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 8ec1f0ac2f..a451508075 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -57,6 +57,9 @@ import { SchemaRulesComponent } from './modules/statistics/schema-rules/schema-r import { PolicyLabelDocumentConfigurationComponent } from './modules/statistics/policy-labels/policy-label-document-configuration/policy-label-document-configuration.component'; import { PolicyLabelDocumentsComponent } from './modules/statistics/policy-labels/policy-label-documents/policy-label-documents.component'; import { PolicyLabelDocumentViewComponent } from './modules/statistics/policy-labels/policy-label-document-view/policy-label-document-view.component'; +import { FormulasComponent } from './modules/formulas/formulas/formulas.component'; +import { FormulaConfigurationComponent } from './modules/formulas/formula-configuration/formula-configuration.component'; + @Injectable({ providedIn: 'root' @@ -573,7 +576,6 @@ const routes: Routes = [ ] } }, - { path: 'schema-rules', component: SchemaRulesComponent, @@ -602,9 +604,6 @@ const routes: Routes = [ ] } }, - - - { path: 'policy-labels', component: PolicyLabelsComponent, @@ -675,9 +674,34 @@ const routes: Routes = [ ] } }, - - - + { + path: 'formulas', + component: FormulasComponent, + canActivate: [PermissionsGuard], + data: { + roles: [ + UserRole.STANDARD_REGISTRY, + UserRole.USER + ], + permissions: [ + Permissions.FORMULAS_FORMULA_READ + ] + } + }, + { + path: 'formulas/:formulaId', + component: FormulaConfigurationComponent, + canActivate: [PermissionsGuard], + data: { + roles: [ + UserRole.STANDARD_REGISTRY, + UserRole.USER + ], + permissions: [ + Permissions.FORMULAS_FORMULA_READ + ] + } + }, { path: '', component: HomeComponent }, { path: 'info', component: InfoComponent }, diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index 1a0797c106..14f205dea9 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -58,6 +58,7 @@ export class AppComponent implements OnInit { const progressFooter = document.getElementById('block-progress-footer'); switch (type) { case 'COLLAPSE': { + document.body.style.setProperty('--header-width', 'var(--header-width-collapse)'); document.getElementById('main-content')!.style.left = 'var(--header-width-collapse)'; document.getElementById('main-content')!.setAttribute('main-collapse-menu', 'true'); if (progressFooter) { @@ -72,6 +73,7 @@ export class AppComponent implements OnInit { break; } case 'EXPAND': { + document.body.style.setProperty('--header-width', 'var(--header-width-expand)'); document.getElementById('main-content')!.style.left = 'var(--header-width-expand)'; document.getElementById('main-content')!.setAttribute('main-collapse-menu', 'false'); if (progressFooter) { diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index b842a86463..831a0a91a7 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -38,6 +38,7 @@ import { PermissionsService } from './services/permissions.service'; import { WorkerTasksService } from './services/worker-tasks.service'; import { SchemaRulesService } from './services/schema-rules.service'; import { PolicyLabelsService } from './services/policy-labels.service'; +import { FormulasService } from './services/formulas.service'; //Views import { UserProfileComponent } from './views/user-profile/user-profile.component'; import { LoginComponent } from './views/login/login.component'; @@ -76,6 +77,7 @@ import { SchemaEngineModule } from './modules/schema-engine/schema-engine.module import { ThemeService } from './services/theme.service'; import { RecordService } from './services/record.service'; import { StatisticsModule } from './modules/statistics/statistics.module'; +import { FormulasModule } from './modules/formulas/formulas.module'; // Injectors import { GET_SCHEMA_NAME } from './injectors/get-schema-name.injector'; import { BLOCK_TYPE_TIPS, BLOCK_TYPE_TIPS_VALUE, } from './injectors/block-type-tips.injector'; @@ -187,6 +189,7 @@ import { WorkerTasksComponent } from './views/worker-tasks/worker-tasks.componen SchemaEngineModule, PolicyEngineModule, StatisticsModule, + FormulasModule, TagEngineModule, CompareModule, ToastrModule.forRoot(), @@ -250,6 +253,7 @@ import { WorkerTasksComponent } from './views/worker-tasks/worker-tasks.componen RecordService, CompareStorage, ProjectComparisonService, + FormulasService, PermissionsService, PermissionsGuard, { diff --git a/frontend/src/app/modules/common/checkbox-button/checkbox-button.component.html b/frontend/src/app/modules/common/checkbox-button/checkbox-button.component.html new file mode 100644 index 0000000000..6c9c7ce6ab --- /dev/null +++ b/frontend/src/app/modules/common/checkbox-button/checkbox-button.component.html @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/frontend/src/app/modules/common/checkbox-button/checkbox-button.component.scss b/frontend/src/app/modules/common/checkbox-button/checkbox-button.component.scss new file mode 100644 index 0000000000..a1ba4914f7 --- /dev/null +++ b/frontend/src/app/modules/common/checkbox-button/checkbox-button.component.scss @@ -0,0 +1,53 @@ +:host { + --checkbox-button-on-first-color: #4169E2; + --checkbox-button-on-second-color: #e1e7fa; + --checkbox-button-off-first-color: #848FA9; + --checkbox-button-off-second-color: #ffffff; + display: inline-flex; +} + +.guardian-button { + width: 100%; + min-height: 28px; + padding: 1px 12px 1px 8px; + + &[select="false"] { + color: var(--checkbox-button-off-first-color); + background-color: var(--checkbox-button-off-second-color); + border: 1px solid var(--checkbox-button-off-first-color); + + .guardian-button-icon { + width: 0px !important; + height: auto !important; + position: relative; + + svg { + display: none; + } + } + } + + &[select="true"] { + color: var(--checkbox-button-on-first-color); + background-color: var(--checkbox-button-on-second-color); + border: 1px solid var(--checkbox-button-on-first-color); + + .guardian-button-icon { + width: 24px !important; + height: auto !important; + position: relative; + + svg { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + fill: var(--checkbox-button-on-first-color); + } + } + } + + .guardian-button-label { + padding-left: 4px; + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/common/checkbox-button/checkbox-button.component.ts b/frontend/src/app/modules/common/checkbox-button/checkbox-button.component.ts new file mode 100644 index 0000000000..963b140f9d --- /dev/null +++ b/frontend/src/app/modules/common/checkbox-button/checkbox-button.component.ts @@ -0,0 +1,28 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +/** + * Checkbox button. + */ +@Component({ + selector: 'checkbox-button', + templateUrl: './checkbox-button.component.html', + styleUrls: ['./checkbox-button.component.scss'] +}) +export class CheckboxButton { + @Input('value') value!: boolean; + @Output('valueChange') valueChange = new EventEmitter(); + @Output('change') change = new EventEmitter(); + + constructor() { + } + + ngOnInit(): void { + this.value = !!this.value; + } + + public onChange() { + this.value = !this.value; + this.valueChange.emit(this.value); + this.change.emit(this.value); + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/common/common-components.module.ts b/frontend/src/app/modules/common/common-components.module.ts index 87af9348d8..bf104c67bb 100644 --- a/frontend/src/app/modules/common/common-components.module.ts +++ b/frontend/src/app/modules/common/common-components.module.ts @@ -1,44 +1,48 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {FormsModule} from '@angular/forms'; -import {MaterialModule} from './material.module'; -import {DatetimePicker} from './datetime-picker/datetime-picker.component'; -import {HederaExplorer} from './hedera-explorer/hedera-explorer.component'; -import {SelectMenuButton} from './select-menu/select-menu.component'; -import {AsyncProgressComponent} from './async-progress/async-progress.component'; -import {SwitchButton} from './switch-button/switch-button.component'; -import {ConfirmationDialogComponent} from './confirmation-dialog/confirmation-dialog.component'; -import {FileDragNDropComponent} from './file-drag-n-drop/file-drag-n-drop.component'; -import {IconPreviewDialog} from './icon-preview-dialog/icon-preview-dialog.component'; -import {TokenConfigurationComponent} from './token-configuration/token-configuration.component'; -import {NgxFileDropModule} from 'ngx-file-drop'; -import {FileExplorer} from './file-explorer/file-explorer.component'; -import {NgxColorsModule} from 'ngx-colors'; -import {ConfirmDialog} from './confirm-dialog/confirm-dialog.component'; -import {SelectorDialogComponent} from './selector-dialog/selector-dialog.component'; -import {StepTreeComponent} from './step-tree/step-tree.component'; -import {SeparateStepperComponent} from './separate-stepper/separate-stepper.component'; -import {NgxMaskDirective, provideNgxMask} from 'ngx-mask'; -import {NewVersionsComponent} from './new-versions/new-versions.component'; -import {DataInputDialogComponent} from './data-input-dialog/data-input-dialog.component'; -import {CompareBtnComponent} from './compare-btn/compare-btn.component'; -import {CompareViewerComponent} from './compare-viewer/compare-viewer.component'; -import {AlertComponent} from './alert/alert.component'; -import {DialogModule} from 'primeng/dialog'; -import {InputTextModule} from 'primeng/inputtext'; -import {DropdownModule} from 'primeng/dropdown'; -import {ProgressBarModule} from 'primeng/progressbar'; -import {ButtonModule} from 'primeng/button'; -import {PaginatorComponent} from './paginator/paginator.component'; -import {AngularSvgIconModule} from 'angular-svg-icon'; -import {StatusDropdown} from './status-dropdown/status-dropdown.component'; -import {CustomCustomDialogComponent} from './custom-confirm-dialog/custom-confirm-dialog.component'; -import {TreeGraphComponent} from './tree-graph/tree-graph.component'; -import {GuardianSwitchButton} from './guardian-switch-button/guardian-switch-button.component'; -import {ImportEntityDialog} from './import-entity-dialog/import-entity-dialog.component'; -import {DialogService, DynamicDialogModule} from 'primeng/dynamicdialog'; -import {TabViewModule} from 'primeng/tabview'; -import {TooltipModule} from 'primeng/tooltip'; +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { MaterialModule } from './material.module'; +import { DatetimePicker } from './datetime-picker/datetime-picker.component'; +import { HederaExplorer } from './hedera-explorer/hedera-explorer.component'; +import { SelectMenuButton } from './select-menu/select-menu.component'; +import { AsyncProgressComponent } from './async-progress/async-progress.component'; +import { SwitchButton } from './switch-button/switch-button.component'; +import { ConfirmationDialogComponent } from './confirmation-dialog/confirmation-dialog.component'; +import { FileDragNDropComponent } from './file-drag-n-drop/file-drag-n-drop.component'; +import { IconPreviewDialog } from './icon-preview-dialog/icon-preview-dialog.component'; +import { TokenConfigurationComponent } from './token-configuration/token-configuration.component'; +import { NgxFileDropModule } from 'ngx-file-drop'; +import { FileExplorer } from './file-explorer/file-explorer.component'; +import { NgxColorsModule } from 'ngx-colors'; +import { ConfirmDialog } from './confirm-dialog/confirm-dialog.component'; +import { SelectorDialogComponent } from './selector-dialog/selector-dialog.component'; +import { StepTreeComponent } from './step-tree/step-tree.component'; +import { SeparateStepperComponent } from './separate-stepper/separate-stepper.component'; +import { NgxMaskDirective, provideNgxMask } from 'ngx-mask'; +import { NewVersionsComponent } from './new-versions/new-versions.component'; +import { DataInputDialogComponent } from './data-input-dialog/data-input-dialog.component'; +import { CompareBtnComponent } from './compare-btn/compare-btn.component'; +import { CompareViewerComponent } from './compare-viewer/compare-viewer.component'; +import { AlertComponent } from './alert/alert.component'; +import { DialogModule } from 'primeng/dialog'; +import { InputTextModule } from 'primeng/inputtext'; +import { DropdownModule } from 'primeng/dropdown'; +import { ProgressBarModule } from 'primeng/progressbar'; +import { ButtonModule } from 'primeng/button'; +import { PaginatorComponent } from './paginator/paginator.component'; +import { AngularSvgIconModule } from 'angular-svg-icon'; +import { StatusDropdown } from './status-dropdown/status-dropdown.component'; +import { CustomConfirmDialogComponent } from './custom-confirm-dialog/custom-confirm-dialog.component'; +import { TreeGraphComponent } from './tree-graph/tree-graph.component'; +import { GuardianSwitchButton } from './guardian-switch-button/guardian-switch-button.component'; +import { ImportEntityDialog } from './import-entity-dialog/import-entity-dialog.component'; +import { DialogService, DynamicDialogModule } from 'primeng/dynamicdialog'; +import { TabViewModule } from 'primeng/tabview'; +import { TooltipModule } from 'primeng/tooltip'; +import { MathLiveComponent } from './mathlive/mathlive.component'; +import { MenuButton } from './menu-button/menu-button.component'; +import { CheckboxButton } from './checkbox-button/checkbox-button.component'; +import { IPFSLinkComponent } from './ipfs-link/ipfs-link.component'; @NgModule({ declarations: [ @@ -63,10 +67,14 @@ import {TooltipModule} from 'primeng/tooltip'; AlertComponent, PaginatorComponent, StatusDropdown, - CustomCustomDialogComponent, + CustomConfirmDialogComponent, TreeGraphComponent, GuardianSwitchButton, - ImportEntityDialog + ImportEntityDialog, + MathLiveComponent, + MenuButton, + CheckboxButton, + IPFSLinkComponent ], imports: [ CommonModule, @@ -111,10 +119,14 @@ import {TooltipModule} from 'primeng/tooltip'; PaginatorComponent, DataInputDialogComponent, StatusDropdown, - CustomCustomDialogComponent, + CustomConfirmDialogComponent, TreeGraphComponent, GuardianSwitchButton, - ImportEntityDialog + ImportEntityDialog, + MathLiveComponent, + MenuButton, + CheckboxButton, + IPFSLinkComponent ] }) export class CommonComponentsModule { diff --git a/frontend/src/app/modules/common/custom-confirm-dialog/custom-confirm-dialog.component.ts b/frontend/src/app/modules/common/custom-confirm-dialog/custom-confirm-dialog.component.ts index 47bbb34204..403897e4e1 100644 --- a/frontend/src/app/modules/common/custom-confirm-dialog/custom-confirm-dialog.component.ts +++ b/frontend/src/app/modules/common/custom-confirm-dialog/custom-confirm-dialog.component.ts @@ -6,7 +6,7 @@ import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; templateUrl: './custom-confirm-dialog.component.html', styleUrls: ['./custom-confirm-dialog.component.scss'], }) -export class CustomCustomDialogComponent implements OnInit { +export class CustomConfirmDialogComponent implements OnInit { public loading = true; public header: string; public text: string; diff --git a/frontend/src/app/modules/common/import-entity-dialog/import-entity-dialog.component.ts b/frontend/src/app/modules/common/import-entity-dialog/import-entity-dialog.component.ts index e0626b4440..e5565cf947 100644 --- a/frontend/src/app/modules/common/import-entity-dialog/import-entity-dialog.component.ts +++ b/frontend/src/app/modules/common/import-entity-dialog/import-entity-dialog.component.ts @@ -11,6 +11,7 @@ import { ToolsService } from 'src/app/services/tools.service'; import { SchemaRulesService } from 'src/app/services/schema-rules.service'; import { PolicyStatisticsService } from 'src/app/services/policy-statistics.service'; import { PolicyLabelsService } from 'src/app/services/policy-labels.service'; +import { FormulasService } from 'src/app/services/formulas.service'; export enum ImportEntityType { Policy = 'policy', @@ -21,7 +22,8 @@ export enum ImportEntityType { SchemaRule = 'schema-rule', Theme = 'theme', Statistic = 'statistic', - PolicyLabel = 'policy-label' + PolicyLabel = 'policy-label', + Formula = 'formula', } export interface IImportEntityArray { @@ -34,6 +36,7 @@ export interface IImportEntityArray { rule?: any, statistic?: any, label?: any, + formula?: any, } export interface IImportEntityMessage { @@ -46,6 +49,7 @@ export interface IImportEntityMessage { rule?: any, statistic?: any, label?: any, + formula?: any, } export type IImportEntityResult = IImportEntityArray | IImportEntityMessage; @@ -98,7 +102,8 @@ export class ImportEntityDialog { private taskService: TasksService, private schemaRulesService: SchemaRulesService, private policyStatisticsService: PolicyStatisticsService, - private policyLabelsService: PolicyLabelsService + private policyLabelsService: PolicyLabelsService, + private formulasService: FormulasService, ) { const _config = this.config.data || {}; @@ -175,6 +180,14 @@ export class ImportEntityDialog { this.fileExtension = 'label'; this.placeholder = 'Import Label .label file'; break; + case 'formula': + this.type = ImportEntityType.Formula; + this.canImportFile = true; + this.canImportMessage = false; + this.title = 'Import Formula'; + this.fileExtension = 'formula'; + this.placeholder = 'Import Formula .formula file'; + break; default: this.type = ImportEntityType.Policy; this.canImportFile = true; @@ -283,6 +296,10 @@ export class ImportEntityDialog { this.labelFromFile(arrayBuffer); break; } + case ImportEntityType.Formula: { + this.formulaFromFile(arrayBuffer); + break; + } default: { break; } @@ -327,6 +344,9 @@ export class ImportEntityDialog { case ImportEntityType.PolicyLabel: { return; } + case ImportEntityType.Formula: { + return; + } default: { return; } @@ -499,7 +519,6 @@ export class ImportEntityDialog { }); } - //Label private labelFromFile(arrayBuffer: any) { this.loading = true; @@ -516,4 +535,21 @@ export class ImportEntityDialog { this.loading = false; }); } + + //Label + private formulaFromFile(arrayBuffer: any) { + this.loading = true; + this.formulasService + .previewByFile(arrayBuffer) + .subscribe((result) => { + this.loading = false; + this.setResult({ + type: 'file', + data: arrayBuffer, + formula: result + }); + }, (e) => { + this.loading = false; + }); + } } \ No newline at end of file diff --git a/frontend/src/app/modules/common/ipfs-link/ipfs-link.component.html b/frontend/src/app/modules/common/ipfs-link/ipfs-link.component.html new file mode 100644 index 0000000000..4bd79b8753 --- /dev/null +++ b/frontend/src/app/modules/common/ipfs-link/ipfs-link.component.html @@ -0,0 +1 @@ +{{url}} \ No newline at end of file diff --git a/frontend/src/app/modules/common/ipfs-link/ipfs-link.component.scss b/frontend/src/app/modules/common/ipfs-link/ipfs-link.component.scss new file mode 100644 index 0000000000..e55039f711 --- /dev/null +++ b/frontend/src/app/modules/common/ipfs-link/ipfs-link.component.scss @@ -0,0 +1,10 @@ +.link { + font-family: Inter, sans-serif; + font-size: 14px !important; + font-style: normal; + font-weight: 500 !important; + color: var(--color-primary); + text-decoration: none; + border-bottom: 1px solid var(--color-primary); + text-transform: capitalize; +} \ No newline at end of file diff --git a/frontend/src/app/modules/common/ipfs-link/ipfs-link.component.ts b/frontend/src/app/modules/common/ipfs-link/ipfs-link.component.ts new file mode 100644 index 0000000000..c6247ea689 --- /dev/null +++ b/frontend/src/app/modules/common/ipfs-link/ipfs-link.component.ts @@ -0,0 +1,27 @@ +import { Component, Input, SimpleChanges } from '@angular/core'; +import { API_IPFS_GATEWAY_URL, IPFS_SCHEMA } from 'src/app/services/api'; + +@Component({ + selector: 'ipfs-link', + templateUrl: './ipfs-link.component.html', + styleUrls: ['./ipfs-link.component.scss'] +}) +export class IPFSLinkComponent { + @Input('url') url: string | undefined; + + public link:string; + + constructor() { + } + + ngOnChanges(changes: SimpleChanges): void { + this.link = ''; + if(this.url) { + if(this.url.startsWith(IPFS_SCHEMA)) { + this.link = API_IPFS_GATEWAY_URL + this.url.replace(IPFS_SCHEMA, ''); + } else { + this.link = this.url; + } + } + } +} diff --git a/frontend/src/app/modules/common/material.module.ts b/frontend/src/app/modules/common/material.module.ts index 75c34de15b..1e976ff457 100644 --- a/frontend/src/app/modules/common/material.module.ts +++ b/frontend/src/app/modules/common/material.module.ts @@ -1,27 +1,27 @@ -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {ReactiveFormsModule} from '@angular/forms'; -import {DragDropModule} from '@angular/cdk/drag-drop'; -import {CdkTableModule} from '@angular/cdk/table'; -import {TabViewModule} from 'primeng/tabview'; -import {AccordionModule} from 'primeng/accordion'; -import {TableModule} from 'primeng/table'; -import {ButtonModule} from 'primeng/button'; -import {SelectButtonModule} from 'primeng/selectbutton'; -import {CalendarModule} from 'primeng/calendar'; -import {RadioButtonModule} from 'primeng/radiobutton'; -import {ProgressSpinnerModule} from 'primeng/progressspinner'; -import {ToolbarModule} from 'primeng/toolbar'; -import {OverlayPanelModule} from 'primeng/overlaypanel'; -import {ProgressBarModule} from 'primeng/progressbar'; -import {CheckboxModule} from 'primeng/checkbox'; -import {ChipsModule} from 'primeng/chips'; -import {StepsModule} from 'primeng/steps'; -import {DropdownModule} from 'primeng/dropdown'; -import {MultiSelectModule} from 'primeng/multiselect'; -import {InputNumberModule} from 'primeng/inputnumber'; -import {TooltipModule} from 'primeng/tooltip'; -import {TreeModule} from 'primeng/tree'; +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ReactiveFormsModule } from '@angular/forms'; +import { DragDropModule } from '@angular/cdk/drag-drop'; +import { CdkTableModule } from '@angular/cdk/table'; +import { TabViewModule } from 'primeng/tabview'; +import { AccordionModule } from 'primeng/accordion'; +import { TableModule } from 'primeng/table'; +import { ButtonModule } from 'primeng/button'; +import { SelectButtonModule } from 'primeng/selectbutton'; +import { CalendarModule } from 'primeng/calendar'; +import { RadioButtonModule } from 'primeng/radiobutton'; +import { ProgressSpinnerModule } from 'primeng/progressspinner'; +import { ToolbarModule } from 'primeng/toolbar'; +import { OverlayPanelModule } from 'primeng/overlaypanel'; +import { ProgressBarModule } from 'primeng/progressbar'; +import { CheckboxModule } from 'primeng/checkbox'; +import { ChipsModule } from 'primeng/chips'; +import { StepsModule } from 'primeng/steps'; +import { DropdownModule } from 'primeng/dropdown'; +import { MultiSelectModule } from 'primeng/multiselect'; +import { InputNumberModule } from 'primeng/inputnumber'; +import { TooltipModule } from 'primeng/tooltip'; +import { TreeModule } from 'primeng/tree'; @NgModule({ declarations: [], diff --git a/frontend/src/app/modules/common/mathlive/keyboards/math-keyboard.ts b/frontend/src/app/modules/common/mathlive/keyboards/math-keyboard.ts new file mode 100644 index 0000000000..d3df58d23d --- /dev/null +++ b/frontend/src/app/modules/common/mathlive/keyboards/math-keyboard.ts @@ -0,0 +1,469 @@ +export const mathKeyboard = { + label: "math", + tooltip: "Math", + rows: [ + [ + { + latex: "+", + shift: "\\oplus", + variants: [ + { latex: "+" }, + { latex: "\\oplus" }, + ] + }, + { + latex: "-", + shift: "\\pm", + variants: [ + { latex: "-", }, + { latex: "\\pm" }, + { latex: "\\ominus" }, + ] + }, + { + latex: "\\times", + shift: "\\otimes", + variants: [ + { latex: "\\times" }, + { latex: "\\otimes" }, + { latex: "\\cdot" }, + ] + }, + { + latex: "\\frac{#@}{#?}", + shift: "\\%", + variants: [ + { latex: "\\frac{#@}{#?}" }, + { latex: "/" }, + { latex: "\\div" }, + { latex: "\\%" }, + { latex: "\\oslash" }, + ] + }, + { + latex: "=", + shift: "\\neq", + variants: [ + { latex: "=" }, + { latex: "\\neq" }, + { latex: "\\equiv" }, + { latex: "\\varpropto" }, + { latex: "\\thickapprox" }, + { latex: "\\lt" }, + { latex: "\\gt" }, + { latex: "\\le" }, + { latex: "\\ge" }, + ] + }, + { + latex: ".", + shift: ",", + variants: [ + { latex: "." }, + { latex: "," }, + { latex: ";" }, + { latex: "\\colon" }, + { latex: "\\Colon" }, + { latex: "?" }, + { latex: "\\cdotp" }, + { latex: "\\ldots" }, + { latex: "\\cdots" }, + { latex: "\\therefore" }, + { latex: "\\because" }, + { latex: "\\Colon:" }, + { latex: "\\vdots" }, + { latex: "\\ddots" }, + { latex: "\\ldotp" }, + ] + }, + { + latex: "(", + shift: "\\lbrack", + variants: [ + { latex: "(" }, + { latex: "\\lbrack" }, + { latex: "\\langle" }, + { latex: "\\lfloor" }, + { latex: "\\lceil" }, + { latex: "\\lbrace" }, + ] + }, + { + latex: ")", + shift: "\\rbrack", + variants: [ + { latex: ")" }, + { latex: "\\rbrack" }, + { latex: "\\rangle" }, + { latex: "\\rfloor" }, + { latex: "\\rceil" }, + { latex: "\\rbrace" }, + ] + }, + "\\sqrt{#0}", + "#0^2", + ], + [ + "#@^{#?}", + "#@_{#?}", + "|#0|", + "\\sqrt[#0]{#0}", + { + class: "small", + latex: "\\log_{#0}#0", + shift: "\\ln", + variants: [ + { class: "small", latex: "\\log_{#0}#0" }, + { class: "small", latex: "\\ln#0" }, + { class: "small", latex: "\\log_{10}#0" }, + ] + }, + { + latex: "\\exponentialE", + shift: "\\exp", + variants: [ + { class: "small", latex: "\\exponentialE" }, + { class: "small", latex: "\\exp\\left(#0\\right)" }, + { class: "small", latex: "\\times10^{#0}" }, + ] + }, + "\\lim_{#0}", + { + class: "small", + latex: "\\sum_{#0}^{#0}#0", + shift: "\\Sigma", + variants: [ + { class: "small", latex: "\\sum_{#0}^{#0}#0" }, + { class: "small", latex: "\\sum #0" }, + ] + }, + { + class: "small", + latex: "\\prod_{#0}^{#0}#0", + shift: "\\Pi", + variants: [ + { class: "small", latex: "\\prod_{#0}^{#0}#0", }, + { class: "small", latex: "\\prod#0", }, + ] + }, + { + class: "small", + latex: "\\int_{#0}^{#0}#0", + shift: "\\smallint", + variants: [ + { class: "small", latex: "\\int_{#0}^{#0}#0" }, + { class: "small", latex: "\\int#0" }, + { class: "small", latex: "\\iint #0" }, + { class: "small", latex: "\\iiint#0" }, + { class: "small", latex: "\\oint#0" }, + { class: "small", latex: "\\intclockwise#0" }, + { class: "small", latex: "\\varointclockwise#0" }, + { class: "small", latex: "\\ointctrclockwise#0" }, + { class: "small", latex: "\\intctrclockwise#0" }, + { class: "small", latex: "\\oiint#0" }, + { class: "small", latex: "\\oiiint#0" }, + ] + }, + ], + [ + { + latex: "\\overrightarrow{#@}", + shift: "\\overleftarrow{#@}", + variants: [ + { latex: "\\overrightarrow{#@}" }, + { latex: "\\overleftarrow{#@}" }, + { latex: "\\underleftarrow{#@}" }, + { latex: "\\underrightarrow{#@}" }, + { latex: "\\overleftrightarrow{#@}" }, + { latex: "\\underleftrightarrow{#@}" }, + ] + }, + { + latex: "\\overline{#@}", + shift: "\\underline{#@}", + variants: [ + { latex: "\\overline{#@}" }, + { latex: "\\underline{#@}" }, + { latex: "\\tilde{#@}" }, + { latex: "\\grave{#@}" }, + { latex: "\\dot{#@}" }, + { latex: "\\ddot{#@}" }, + { latex: "\\mathring{#@}" }, + { latex: "\\breve{#@}" }, + { latex: "\\acute{#@}" }, + { latex: "\\bar{#@}" }, + { latex: "\\vec{#@}" }, + { latex: "\\hat{#@}" }, + { latex: "\\check{#@}" }, + { latex: "\\undergroup{#@}" }, + { latex: "\\overgroup{#@}" }, + { latex: "\\underbrace{#@}" }, + { latex: "\\overbrace{#@}" }, + { latex: "\\overlinesegment{#@}" }, + { latex: "\\underlinesegment{#@}" }, + ] + }, + { + latex: "#@^{\\prime}", + shift: "#@^{\\doubleprime}", + variants: [ + { latex: "#@^{\\prime}" }, + { latex: "#@^{\\doubleprime}" }, + { latex: "#@\\degree" }, + ] + }, + { + class: "small", + latex: "\\mathrm{abs}\\left(#0\\right)", + }, + { + latex: "\\cup", + shift: "\\cap", + variants: [ + { latex: "\\cup" }, + { latex: "\\cap" }, + { latex: "\\subset" }, + { latex: "\\subseteq" }, + { latex: "\\subsetneq" }, + { latex: "\\varsubsetneq" }, + { latex: "\\subsetneqq" }, + { latex: "\\nsubset" }, + { latex: "\\nsubseteq" }, + { latex: "\\supset" }, + { latex: "\\supseteq" }, + { latex: "\\supsetneq" }, + { latex: "\\supsetneqq" }, + { latex: "\\nsupset" }, + { latex: "\\nsupseteq" }, + ] + }, + + { + latex: "\\exists", + shift: "\\forall", + variants: [ + { latex: "\\exists" }, + { latex: "\\nexists" }, + { latex: "\\forall" }, + { latex: "\\lnot" }, + { latex: "\\land" }, + { latex: "\\lor" }, + { latex: "\\oplus" }, + { latex: "\\downarrow" }, + { latex: "\\uparrow" }, + { latex: "\\curlywedge" }, + { latex: "\\bar{\\curlywedge}" }, + { latex: "\\in" }, + { latex: "\\owns" }, + { latex: "\\notin" }, + { latex: "\\ni" }, + { latex: "\\not\\owns" }, + ] + }, + { + latex: "\\rightarrow", + shift: "\\larr", + variants: [ + { latex: "\\rightarrow" }, + { latex: "\\implies" }, + { latex: "\\to" }, + { latex: "\\dashv" }, + { latex: "\\roundimplies" }, + { latex: "\\larr" }, + { latex: "\\impliedby" }, + { latex: "\\gets" }, + { latex: "\\lArr" }, + { latex: "\\vdash" }, + { latex: "\\models" }, + { latex: "\\in" }, + { latex: "\\lrArr" }, + { latex: "\\iff" }, + { latex: "\\leftrightarrow" }, + { latex: "\\leftrightarrows" }, + { latex: "\\Leftrightarrow" }, + { latex: "^{\\biconditional}" }, + ] + }, + { + latex: "\\infty", + shift: "\\omega", + variants: [ + { latex: "\\infty" }, + { latex: "\\aleph_0" }, + { latex: "\\aleph_1" }, + { latex: "\\omega" }, + { latex: "\\mathfrak{m}" }, + ] + }, + { + latex: "\\imaginaryI", + shift: "\\Re", + variants: [ + { latex: "\\Re" }, + { latex: "\\Im" }, + { latex: "\\imaginaryJ" }, + { latex: "\\imaginaryI" }, + { latex: "\\Vert#0\\Vert" }, + ] + }, + { + latex: "\\mathrm{d}#0", + shift: "\\partial", + variants: [ + { latex: "\\mathrm{d}#0" }, + { latex: "\\dfrac{\\mathrm{d}}{\\mathrm{d}#0}" }, + { latex: "\\frac{\\partial}{\\partial #0}" }, + { latex: "\\mathrm{d}" }, + { latex: "\\partial" }, + ] + }, + ], + [ + { + latex: "\\sin", + shift: "\\sin^{-1}", + variants: [ + { class: "small", latex: "\\sin" }, + { class: "small", latex: "\\sinh" }, + { class: "small", latex: "\\sin^{-1}" }, + { class: "small", latex: "\\arsinh" }, + ] + }, + { + latex: "\\cos", + shift: "\\cos^{-1}", + variants: [ + { class: "small", latex: "\\cos" }, + { class: "small", latex: "\\cosh" }, + { class: "small", latex: "\\cos^{-1}" }, + { class: "small", latex: "\\arcosh" }, + ] + }, + { + latex: "\\tan", + shift: "\\tan^{-1}", + variants: [ + { class: "small", latex: "\\tan" }, + { class: "small", latex: "\\tg" }, + { class: "small", latex: "\\tan^{-1}" }, + { class: "small", latex: "\\tanh" }, + { class: "small", latex: "\\artanh" }, + { class: "small", latex: "\\arctan" }, + { class: "small", latex: "\\arctg" }, + { class: "small", latex: "\\cot" }, + ] + }, + "\\Delta", + { + latex: "\\pi", + shift: "\\tau", + variants: [ + { latex: "\\pi" }, + { latex: "\\tau" }, + { latex: "\\rho" }, + { latex: "\\theta" }, + ] + }, + { + latex: "f(#0)", + shift: "x_{i}", + variants: [ + { class: "small", latex: "f(#0)" }, + { class: "small", latex: "g(#0)" }, + { latex: "x^{n}" }, + { latex: "x^{#0}" }, + { latex: "x_{n}" }, + { latex: "x_{i}" }, + { latex: "x_{#0}" }, + ] + }, + + { + latex: "#@_{i}", + shift: "#@^{n}", + variants: [ + { latex: "#@_{i}" }, + { latex: "#@_{n}" }, + { latex: "#@^{n}" }, + { latex: "#@_{ij}" }, + { latex: "#@_{t}" }, + ] + }, + { + latex: "\\text{\\_}", + shift: "\\circ", + variants: [ + { latex: "\\ast" }, + { latex: "\\circ" }, + { latex: "\\bigcirc" }, + { latex: "\\bullet" }, + { latex: "\\odot" }, + { latex: "\\oslash" }, + { latex: "\\circledcirc" }, + { latex: "\\star" }, + { latex: "\\times" }, + { latex: "\\doteq" }, + { latex: "\\doteqdot" }, + ] + }, + { + latex: "+", + aside: "New Line", + class: "action", + command: ["performWithFeedback", "addRowAfter"], + shift: { + latex: "-", + class: "action", + aside: "Delete Line", + command: ["performWithFeedback", "removeRow"], + }, + variants: [ + { + latex: "+", + aside: "New Line", + class: "small", + command: ["performWithFeedback", "addRowAfter"], + }, + { + latex: "-", + aside: "Delete Line", + class: "small", + command: ["performWithFeedback", "removeRow"], + }, + ] + }, + { + label: "T", + aside: "Text Mode", + class: "action", + command: ["switchMode", "text"], + shift: { + label: "M", + aside: "Math Mode", + class: "small action", + command: ["switchMode", "math"], + }, + variants: [ + { + label: "T", + class: "small", + aside: "Math Mode", + command: ["switchMode", "text"], + }, + { + label: "M", + class: "small", + aside: "Math Mode", + command: ["switchMode", "math"], + }, + { + label: "L", + class: "small", + aside: "LaTeX Mode", + command: ["switchMode", "latex"], + }, + ] + }, + ] + ] +}; \ No newline at end of file diff --git a/frontend/src/app/modules/common/mathlive/keyboards/matrix-keyboard.ts b/frontend/src/app/modules/common/mathlive/keyboards/matrix-keyboard.ts new file mode 100644 index 0000000000..8f1f5ea783 --- /dev/null +++ b/frontend/src/app/modules/common/mathlive/keyboards/matrix-keyboard.ts @@ -0,0 +1,252 @@ +export const matrixKeyboard = { + label: "matrix", + tooltip: "Matrix", + rows: [ + [ + { + label: "1x1", + latex: "\\begin{pmatrix}#0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0\\end{Bmatrix}" }, + ] + }, + { + label: "1x2", + latex: "\\begin{pmatrix}#0 & #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0 & #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0 & #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0 & #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0 & #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0 & #0\\end{Bmatrix}" }, + ] + }, + { + label: "1x3", + latex: "\\begin{pmatrix}#0 & #0 & #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0 & #0 & #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0 & #0 & #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0 & #0 & #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0 & #0 & #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0 & #0 & #0\\end{Bmatrix}" }, + ] + }, + { + label: "1x4", + latex: "\\begin{pmatrix}#0 & #0 & #0 & #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0 & #0 & #0 & #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0 & #0 & #0 & #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0 & #0 & #0 & #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0 & #0 & #0 & #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0 & #0 & #0 & #0\\end{Bmatrix}" }, + ] + }, + { + latex: "\\cdots", + command: ["performWithFeedback", "addRowAfter"], + aside: "Add Row After", + shift: "\\cdots", + variants: [ + { + latex: "\\cdots", + command: ["performWithFeedback", "addRowAfter"], + aside: "Add Row After", + }, + { + latex: "\\cdots", + command: ["performWithFeedback", "addRowBefore"], + aside: "Add Row Before", + } + ] + } + ], + [ + { + label: "2x1", + latex: "\\begin{pmatrix}#0\\\\ #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0\\\\ #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0\\\\ #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0\\\\ #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0\\\\ #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0\\\\ #0\\end{Bmatrix}" }, + ] + }, + { + label: "2x2", + latex: "\\begin{pmatrix}#0 & #0\\\\ #0 & #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0 & #0\\\\ #0 & #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0 & #0\\\\ #0 & #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0 & #0\\\\ #0 & #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0 & #0\\\\ #0 & #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0 & #0\\\\ #0 & #0\\end{Bmatrix}" }, + ] + }, + { + label: "2x3", + latex: "\\begin{pmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\end{Bmatrix}" }, + ] + }, + { + label: "2x4", + latex: "\\begin{pmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{Bmatrix}" }, + ] + }, + { + latex: "\\vdots", + command: ["performWithFeedback", "addColumnAfter"], + aside: "Add Column After", + shift: "\\vdots", + variants: [ + { + latex: "\\vdots", + command: ["performWithFeedback", "addColumnAfter"], + aside: "Add Column After", + }, + { + latex: "\\vdots", + command: ["performWithFeedback", "addColumnBefore"], + aside: "Add Column Before", + } + ] + } + ], + [ + { + label: "3x1", + latex: "\\begin{pmatrix}#0\\\\ #0\\\\ #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0\\\\ #0\\\\ #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0\\\\ #0\\\\ #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0\\\\ #0\\\\ #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0\\\\ #0\\\\ #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0\\\\ #0\\\\ #0\\end{Bmatrix}" }, + ] + }, + { + label: "3x2", + latex: "\\begin{pmatrix}#0 & #0\\\\ #0 & #0\\\\ #0 & #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0 & #0\\\\ #0 & #0\\\\ #0 & #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0 & #0\\\\ #0 & #0\\\\ #0 & #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0 & #0\\\\ #0 & #0\\\\ #0 & #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0 & #0\\\\ #0 & #0\\\\ #0 & #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0 & #0\\\\ #0 & #0\\\\ #0 & #0\\end{Bmatrix}" }, + ] + }, + { + label: "3x3", + latex: "\\begin{pmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\end{Bmatrix}" }, + ] + }, + { + label: "3x4", + latex: "\\begin{pmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{Bmatrix}" }, + ] + }, + { + latex: "\\cdots", + aside: "Delelte Row", + command: ["performWithFeedback", "removeRow"], + } + ], + [ + { + label: "4x1", + latex: "\\begin{pmatrix}#0\\\\ #0\\\\ #0\\\\ #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0\\\\ #0\\\\ #0\\\\ #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0\\\\ #0\\\\ #0\\\\ #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0\\\\ #0\\\\ #0\\\\ #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0\\\\ #0\\\\ #0\\\\ #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0\\\\ #0\\\\ #0\\\\ #0\\end{Bmatrix}" }, + ] + }, + { + label: "4x2", + latex: "\\begin{pmatrix}#0 & #0\\\\ #0 & #0\\\\ #0 & #0\\\\ #0 & #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0 & #0\\\\ #0 & #0\\\\ #0 & #0\\\\ #0 & #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0 & #0\\\\ #0 & #0\\\\ #0 & #0\\\\ #0 & #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0 & #0\\\\ #0 & #0\\\\ #0 & #0\\\\ #0 & #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0 & #0\\\\ #0 & #0\\\\ #0 & #0\\\\ #0 & #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0 & #0\\\\ #0 & #0\\\\ #0 & #0\\\\ #0 & #0\\end{Bmatrix}" }, + ] + }, + { + label: "4x3", + latex: "\\begin{pmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\end{Bmatrix}" }, + ] + }, + { + label: "4x4", + latex: "\\begin{pmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{Bmatrix}" }, + ] + }, + { + latex: "\\vdots", + aside: "Delete Column", + command: ["performWithFeedback", "removeColumn"], + } + ] + ] +}; \ No newline at end of file diff --git a/frontend/src/app/modules/common/mathlive/mathlive.component.html b/frontend/src/app/modules/common/mathlive/mathlive.component.html new file mode 100644 index 0000000000..ece16ca201 --- /dev/null +++ b/frontend/src/app/modules/common/mathlive/mathlive.component.html @@ -0,0 +1,3 @@ +
+ +
\ No newline at end of file diff --git a/frontend/src/app/modules/common/mathlive/mathlive.component.scss b/frontend/src/app/modules/common/mathlive/mathlive.component.scss new file mode 100644 index 0000000000..a98b7c0535 --- /dev/null +++ b/frontend/src/app/modules/common/mathlive/mathlive.component.scss @@ -0,0 +1,20 @@ +.mathlive-content { + display: flex; + flex-flow: column; +} + +.mathlive-content::ng-deep { + math-field::part(virtual-keyboard-toggle) { + display: none; + } + + math-field { + min-height: 40px; + border-radius: 8px; + outline: none; + border: 1px solid var(--guardian-grey-color-2, #E1E7EF); + padding-left: 12px; + font-size: 24px; + user-select: all; + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/common/mathlive/mathlive.component.ts b/frontend/src/app/modules/common/mathlive/mathlive.component.ts new file mode 100644 index 0000000000..c49313ea63 --- /dev/null +++ b/frontend/src/app/modules/common/mathlive/mathlive.component.ts @@ -0,0 +1,67 @@ +import { Component, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output, ViewChild } from '@angular/core'; +import { MathfieldElement } from 'mathlive'; +import { matrixKeyboard } from './keyboards/matrix-keyboard'; +import { mathKeyboard } from './keyboards/math-keyboard'; + +@Component({ + selector: 'math-live', + templateUrl: './mathlive.component.html', + styleUrls: ['./mathlive.component.scss'] +}) +export class MathLiveComponent implements OnInit, OnDestroy { + @ViewChild('mathLiveContent', { static: true }) mathLiveContent: ElementRef; + @Input('readonly') readonly: boolean = false; + @Input('value') value!: string; + @Output('valueChange') valueChange = new EventEmitter(); + @Output('keyboard') keyboard = new EventEmitter(); + @Output('focus') focus = new EventEmitter(); + + private readonly mfe: MathfieldElement; + + constructor() { + MathfieldElement.keypressSound = null; + MathfieldElement.plonkSound = null; + this.mfe = new MathfieldElement(); + } + + ngOnInit(): void { + const mathVirtualKeyboard: any = window.mathVirtualKeyboard; + mathVirtualKeyboard.layouts = [ + mathKeyboard, + matrixKeyboard, + "numeric", + "symbols", + "greek" + ]; + this.mfe.mathVirtualKeyboardPolicy = "manual"; + this.mfe.addEventListener("focusin", () => { + this.keyboard.emit(true); + this.focus.emit(this); + return mathVirtualKeyboard.show(); + }); + this.mfe.addEventListener("focusout", () => { + this.keyboard.emit(false); + this.focus.emit(this); + return mathVirtualKeyboard.hide(); + }); + this.mfe.addEventListener('input', (ev: any) => { + this.value = ev?.target?.value; + this.valueChange.emit(this.value); + }); + this.mfe.value = this.value || ''; + this.mfe.readonly = this.readonly; + } + + ngAfterViewInit() { + const container = this.mathLiveContent.nativeElement; + container.appendChild(this.mfe) + } + + ngOnDestroy() { + this.mfe.remove(); + } + + public getElement(): ElementRef { + return this.mathLiveContent; + } +} diff --git a/frontend/src/app/modules/common/menu-button/menu-button.component.html b/frontend/src/app/modules/common/menu-button/menu-button.component.html new file mode 100644 index 0000000000..29eb68cb9f --- /dev/null +++ b/frontend/src/app/modules/common/menu-button/menu-button.component.html @@ -0,0 +1,40 @@ + + + + + + + + \ No newline at end of file diff --git a/frontend/src/app/modules/common/menu-button/menu-button.component.scss b/frontend/src/app/modules/common/menu-button/menu-button.component.scss new file mode 100644 index 0000000000..ea7b2890e1 --- /dev/null +++ b/frontend/src/app/modules/common/menu-button/menu-button.component.scss @@ -0,0 +1,90 @@ +:host { + display: flex; + flex-direction: row; + position: relative; +} + +.menu-left-button { + width: 100%; + height: 100%; + border-top-right-radius: 0px; + border-bottom-right-radius: 0px; + justify-content: flex-start; + padding-left: 10px; +} + +.menu-right-button { + width: 40px; + min-width: 40px; + color: var(--guardian-secondary-color); + background-color: var(--guardian-primary-color); + border: 1px solid var(--guardian-primary-color); + border-radius: 6px; + border-top-left-radius: 0px; + border-bottom-left-radius: 0px; + position: relative; + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; + + &::before { + content: ""; + display: block; + position: absolute; + left: 0px; + top: 6px; + bottom: 6px; + width: 3px; + border-left: 1px solid #fff; + pointer-events: none; + } + + &:hover { + filter: brightness(0.95); + } +} + +.menu-dropdown { + height: 40px; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + pointer-events: none; + opacity: 0; + z-index: -1; +} + +.menu-option { + display: flex; + flex-direction: row; + align-items: center; + padding: 8px 10px; + + .menu-option-icon { + width: 24px; + height: 24px; + overflow: hidden; + margin-right: 12px; + } + + .menu-option-label { + font-family: var(--guardian-font-family); + user-select: none; + justify-content: center; + align-items: center; + margin: 0; + display: inline-flex; + font-size: 14px; + line-height: 16px; + text-align: left; + } +} + +::ng-deep .menu-dropdown-panel { + .p-dropdown-item { + padding: 0; + } +} diff --git a/frontend/src/app/modules/common/menu-button/menu-button.component.ts b/frontend/src/app/modules/common/menu-button/menu-button.component.ts new file mode 100644 index 0000000000..1f4f68ce0f --- /dev/null +++ b/frontend/src/app/modules/common/menu-button/menu-button.component.ts @@ -0,0 +1,61 @@ +import { Component, ElementRef, EventEmitter, Inject, Input, Output, SimpleChanges, ViewChild } from '@angular/core'; + +interface IOption { + id: string; + text: string; + icon: string; + color: string; +} + +/** + * menu button. + */ +@Component({ + selector: 'menu-button', + templateUrl: './menu-button.component.html', + styleUrls: ['./menu-button.component.scss'] +}) +export class MenuButton { + @Input('immediately') immediately: boolean = false; + @Input('options') options!: IOption[]; + @Output('action') action = new EventEmitter(); + + @ViewChild('dropdownMenu', { static: true }) dropdownMenu: any; + + public current: IOption; + public size: string = 'auto'; + + constructor() { + } + + ngOnChanges(changes: SimpleChanges): void { + this.current = this.options?.[0]; + } + + ngAfterViewInit(): void { + this.size = this.getSize(); + } + + onAction() { + this.action.emit(this.current); + } + + onChange($event: any) { + this.current = $event.value; + if(this.immediately) { + this.onAction(); + } + } + + private getSize(): string { + try { + const width = this.dropdownMenu.el.nativeElement.getBoundingClientRect().width; + if (width) { + return width + 'px'; + } + } catch (error) { + return 'auto'; + } + return 'auto'; + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/common/status-dropdown/status-dropdown.component.html b/frontend/src/app/modules/common/status-dropdown/status-dropdown.component.html index b6600385bd..25200d30bd 100644 --- a/frontend/src/app/modules/common/status-dropdown/status-dropdown.component.html +++ b/frontend/src/app/modules/common/status-dropdown/status-dropdown.component.html @@ -1,6 +1,6 @@ + {{label}}
diff --git a/frontend/src/app/modules/common/status-dropdown/status-dropdown.component.ts b/frontend/src/app/modules/common/status-dropdown/status-dropdown.component.ts index a8b6676523..b208f1e8e4 100644 --- a/frontend/src/app/modules/common/status-dropdown/status-dropdown.component.ts +++ b/frontend/src/app/modules/common/status-dropdown/status-dropdown.component.ts @@ -1,10 +1,10 @@ -import { Component, EventEmitter, Input, Output, SimpleChanges } from '@angular/core'; +import { ChangeDetectorRef, Component, EventEmitter, Input, Output, SimpleChanges } from '@angular/core'; interface IOptions { label: string; value: string; description: string; - disable?: ((value: string) => boolean) | boolean; + disable?: ((value: string, item?: any) => boolean) | boolean; } /** @@ -16,38 +16,41 @@ interface IOptions { styleUrls: ['./status-dropdown.component.scss'] }) export class StatusDropdown { + @Input('item') item!: any; @Input('options') options!: IOptions[]; - @Input('value') value!: string; @Input('status') status!: string; - @Output('valueChange') valueChange = new EventEmitter(); @Output('onChange') change = new EventEmitter(); public list: IOptions[] = []; public disabled: boolean = true; public label: string = ''; - constructor() { } + constructor( + private cdRef: ChangeDetectorRef, + ) { } ngOnChanges(changes: SimpleChanges): void { this.options = this.options || []; this.list = this.options.filter((item) => { if (typeof item.disable === 'function') { - return !item.disable(this.value); + return !item.disable(this.status, this.item); } else { return !item.disable; } }) this.disabled = this.list.length === 0; - const item = this.options.find((item) => item.value === this.value); + const item = this.options.find((item) => item.value === this.status); if (item) { this.label = item.label; } else { - this.label = this.value; + this.label = this.status; } } - onChange() { - this.valueChange.emit(this.value); - this.change.emit(this.value) + onChange($event: any, dropdown: any) { + if($event.value) { + this.change.emit($event.value); + dropdown.clear(); + } } } diff --git a/frontend/src/app/modules/formulas/dialogs/formulas-view-dialog/formulas-view-dialog.component.html b/frontend/src/app/modules/formulas/dialogs/formulas-view-dialog/formulas-view-dialog.component.html new file mode 100644 index 0000000000..c08d4c9b38 --- /dev/null +++ b/frontend/src/app/modules/formulas/dialogs/formulas-view-dialog/formulas-view-dialog.component.html @@ -0,0 +1,232 @@ +
+
+
View Formula
+
+
+ +
+
+
+ +
+ + + +
+ {{ tab.label }} +
+
+
+
+
+
+ + +
+ + +
Constant
+
+ +
+ + +
+
+ + +
+
+ + +
+
+
+ + + + +
Variable
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + + +
Formula
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ {{relationship.name}} +
+
+
+
+
+ + + + +
Text
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ {{relationship.name}} +
+
+
+
+
+ + +
Schema
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+
+
+
+
+ + +
+
+ + +
+
+
+
There were no files attached.
+
+
+
+
+ \ No newline at end of file diff --git a/frontend/src/app/modules/formulas/dialogs/formulas-view-dialog/formulas-view-dialog.component.scss b/frontend/src/app/modules/formulas/dialogs/formulas-view-dialog/formulas-view-dialog.component.scss new file mode 100644 index 0000000000..ac5ce7714f --- /dev/null +++ b/frontend/src/app/modules/formulas/dialogs/formulas-view-dialog/formulas-view-dialog.component.scss @@ -0,0 +1,301 @@ +.dialog-header { + .header-item { + padding: 6px 12px; + margin: 0px 16px; + border-radius: 6px; + font-size: var(--guardian-primary-font-size); + color: var(--guardian-dry-run-color); + background: var(--guardian-dry-run-background); + user-select: none; + cursor: default; + } +} + +.guardian-input-container { + margin-bottom: 24px; +} + +.guardian-textarea-container { + margin-bottom: 24px; +} + +.dialog-body { + display: flex; + flex-direction: column; + min-height: 600px; + padding-bottom: 24px; +} + +.view-bar { + margin-bottom: 24px; +} + +.view-body { + display: flex; + flex-direction: row; + min-height: 600px; + height: 600px; +} + +.action-buttons { + display: flex; + justify-content: flex-end; + user-select: none; + position: relative; + + &>div { + margin-left: 15px; + margin-right: 0px; + } +} + +.close-icon-color { + fill: #848FA9; + color: #848FA9; +} + +.context { + position: relative; + height: 100%; + display: flex; + flex-direction: column; + width: 100%; + overflow: hidden; + + .current-item-name { + font-family: Poppins; + font-size: 24px; + font-weight: 600; + line-height: 32px; + color: #181818; + margin-bottom: 24px; + } + + .current-item { + border: 1px solid #E1E7EF; + border-radius: 8px; + height: 100%; + overflow: auto; + padding: 24px; + } +} + +.formula-relationships { + border-radius: 8px; + outline: none; + border: 1px solid var(--guardian-grey-color-2, #E1E7EF); + font-family: Inter; + font-size: 14px; + font-weight: 400; + color: #000; + padding: 8px 16px 0px 16px; + display: flex; + flex-direction: row; + flex-wrap: wrap; + min-height: 40px; + + .formula-relationship { + width: auto; + height: 30px; + padding: 7px 12px 7px 12px; + border-radius: 6px; + background: var(--guardian-grey-color, #EFF3F7); + color: var(--guardian-disabled-color, #848FA9); + font-family: Inter; + font-size: 14px; + font-weight: 400; + line-height: 16px; + text-align: left; + margin-right: 8px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + display: inline-flex; + margin-bottom: 8px; + cursor: pointer; + + &:hover { + filter: brightness(0.95); + } + } +} + +.formula-link { + height: 40px; + border-radius: 8px; + outline: none; + border: 1px solid var(--guardian-grey-color-2, #E1E7EF); + font-family: Inter; + font-size: 14px; + font-weight: 400; + color: #000; + padding: 12px 16px; + cursor: pointer; + + &:hover { + background: var(--guardian-hover, #F0F3FC); + } + + .formula-link-value { + display: flex; + flex-direction: row; + pointer-events: none; + + &__entity { + font-family: Inter; + font-size: 14px; + font-weight: 500; + line-height: 16px; + color: var(--guardian-entity-color, #DA9B22); + margin-right: 8px; + } + + &__item { + font-family: Inter; + font-size: 14px; + font-weight: 400; + line-height: 16px; + } + } +} + +.nav-container { + display: flex; + width: 260px; + min-width: 260px; + height: 100%; + padding: 16px 0px 16px 0px; + border: 1px solid #E1E7EF; + border-radius: 8px; + margin-right: 24px; + position: relative; + overflow: auto; +} + +.field-tree { + width: auto; + min-width: 100%; + display: table; + + .field-item { + width: 100%; + padding: 8px 24px 8px 16px; + font-family: Inter; + font-size: 14px; + font-weight: 500; + line-height: 18px; + text-align: left; + display: flex; + justify-content: flex-start; + align-items: center; + + .field-offset { + width: 0px; + height: 24px; + overflow: hidden; + } + + .field-collapse { + width: 24px; + min-width: 24px; + height: 24px; + overflow: hidden; + } + + .field-select { + width: 24px; + min-width: 24px; + height: 24px; + overflow: hidden; + cursor: pointer; + + * { + pointer-events: none; + } + } + + .field-container { + padding-left: 8px; + cursor: pointer; + + * { + pointer-events: none; + } + + &[readonly="true"] { + cursor: default; + } + + &__view { + text-transform: capitalize; + font-size: 12px; + font-weight: bold; + margin-bottom: 4px; + } + + &__entity { + font-family: Inter; + font-size: 14px; + font-weight: 500; + line-height: 16px; + color: var(--guardian-entity-color, #DA9B22); + margin-bottom: 4px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 150px; + } + + &__header { + display: flex; + height: 18px; + + &-type { + font-family: Inter; + font-size: 12px; + font-weight: 400; + line-height: 16px; + color: #848FA9; + margin-right: 4px; + padding: 2px 0px; + text-transform: capitalize; + } + + &-name { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 150px; + } + } + } + + &[selected="true"] { + background: var(--guardian-primary-background, #e1e7fa); + } + } +} + +.files-container { + width: 100%; + + .file-item { + width: 100%; + border: 1px solid #E1E7EF; + border-radius: 8px; + padding: 24px; + margin-bottom: 24px; + } + + .no-files { + font-family: Inter; + font-size: 14px; + font-weight: 400; + color: var(--guardian-disabled-color, #848FA9); + height: 87px; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/formulas/dialogs/formulas-view-dialog/formulas-view-dialog.component.ts b/frontend/src/app/modules/formulas/dialogs/formulas-view-dialog/formulas-view-dialog.component.ts new file mode 100644 index 0000000000..2f0f372d05 --- /dev/null +++ b/frontend/src/app/modules/formulas/dialogs/formulas-view-dialog/formulas-view-dialog.component.ts @@ -0,0 +1,85 @@ +import { Component } from '@angular/core'; +import { DialogService, DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { FormulaFiles, FormulaItem, FormulasTree, SchemaItem } from '../../models/formula-tree'; +import { TreeListData, TreeListItem, TreeListView } from 'src/app/modules/common/tree-graph/tree-list'; + +@Component({ + selector: 'formulas-view-dialog', + templateUrl: './formulas-view-dialog.component.html', + styleUrls: ['./formulas-view-dialog.component.scss'], +}) +export class FormulasViewDialog { + public loading = true; + public tree: FormulasTree; + public schema: string; + public path: string; + public items: FormulaItem[]; + public current: FormulaItem | SchemaItem | null; + public nav: TreeListView; + public files: FormulaFiles[]; + + public viewDocumentOptions = [ + { label: 'Formulas', value: true, icon: 'function' }, + { label: 'Files', value: false, icon: 'file' } + ]; + public viewFormulas: boolean = true; + + constructor( + public ref: DynamicDialogRef, + public config: DynamicDialogConfig, + private dialogService: DialogService, + ) { + this.tree = this.config.data?.tree; + this.schema = this.config.data?.schema; + this.path = this.config.data?.path; + } + + ngOnInit() { + this.loading = false; + this.items = this.tree?.get(this.schema, this.path) || []; + this.files = this.tree?.getFiles(this.items) || []; + const navTree = FormulasTree.createNav(this.items) + const fields = TreeListData.fromObject(navTree, 'children'); + this.nav = TreeListView.createView(fields, (s) => { return !s.parent }); + this.nav.collapseAll(false); + this.nav.updateHidden(); + this.selectItem(this.items[0]); + } + + ngOnDestroy() { + } + + public onClose(): void { + this.ref.close(null); + } + + public onLink(current: FormulaItem) { + this.selectItem(current.linkItem); + } + + public onRelationship(relationship: FormulaItem) { + this.selectItem(relationship); + } + + public onCollapseNav(item: TreeListItem) { + if (this.nav) { + this.nav.collapse(item, !item.collapsed); + this.nav.updateHidden(); + } + } + + public onSelectNav(item: TreeListItem) { + this.selectItem(item.data.data); + } + + private selectItem(current: FormulaItem | SchemaItem | null) { + this.current = current; + this.nav.data.items.forEach((e) => e.selected = e.data.data === this.current); + this.nav.updateHidden(); + this.nav.updateSelected(); + } + + public setTab(event: any): void { + this.viewFormulas = event.index === 0; + } +} diff --git a/frontend/src/app/modules/formulas/dialogs/link-dialog/link-dialog.component.html b/frontend/src/app/modules/formulas/dialogs/link-dialog/link-dialog.component.html new file mode 100644 index 0000000000..339d4ae6bc --- /dev/null +++ b/frontend/src/app/modules/formulas/dialogs/link-dialog/link-dialog.component.html @@ -0,0 +1,159 @@ +
+
+
Set Link
+
+
+ +
+
+
+ +
+
Select Schema \ Formula
+
+
+
Schemas
+
Formulas
+
+
+
+
+ +
+
+ {{item.name}} +
+
+
+
+
+
+ +
+
+ {{item.name}} +
+
+
+
+
+
+
Select Field
+
Select Variable
+
+
+
+
+
+
+
+
+
+ +
+
+ {{item.data.description}} +
+
+
+
+
+
+
+
+
+
+
+ +
+
+ {{item.data.name}} +
+
+
+
+
+
+ \ No newline at end of file diff --git a/frontend/src/app/modules/formulas/dialogs/link-dialog/link-dialog.component.scss b/frontend/src/app/modules/formulas/dialogs/link-dialog/link-dialog.component.scss new file mode 100644 index 0000000000..bfca17a282 --- /dev/null +++ b/frontend/src/app/modules/formulas/dialogs/link-dialog/link-dialog.component.scss @@ -0,0 +1,254 @@ +.context { + position: relative; + height: 100%; + display: flex; + flex-direction: column; + width: 100%; + overflow: hidden; + + .context-header { + font-family: Poppins; + font-size: 24px; + font-weight: 600; + line-height: 32px; + color: #181818; + margin-bottom: 24px; + } + + .context-body { + border: 1px solid #E1E7EF; + padding: 24px; + border-radius: 8px; + overflow: hidden; + height: 100%; + display: flex; + flex-direction: column; + position: relative; + } +} + +.dialog-header { + .header-item { + padding: 6px 12px; + margin: 0px 16px; + border-radius: 6px; + font-size: var(--guardian-primary-font-size); + color: var(--guardian-dry-run-color); + background: var(--guardian-dry-run-background); + user-select: none; + cursor: default; + } +} + +.guardian-input-container { + margin-bottom: 24px; +} + +.dialog-body { + display: flex; + flex-direction: row; + min-height: 500px; + height: 500px; + padding-bottom: 24px; +} + +.action-buttons { + display: flex; + justify-content: flex-end; + user-select: none; + position: relative; + + .close-btn { + position: absolute; + top: 0px; + left: 0px; + margin-left: 0px; + } + + &>div { + margin-left: 15px; + margin-right: 0px; + } +} + +.close-icon-color { + fill: #848FA9; + color: #848FA9; +} + +.type-selector { + border-radius: 6px; + display: flex; + flex-direction: row; + margin-bottom: 20px; + border: 1px solid #C4D0E1; + background: #F9FAFC; + width: 250px; + height: 42px; + min-height: 42px; + + .selector-item { + padding: 5px; + border-radius: 6px; + margin: 3px; + cursor: pointer; + user-select: none; + flex: 1; + text-align: center; + color: #AAB7C4; + font-family: Inter; + font-size: 14px; + font-weight: 500; + line-height: 24px; + + &[selected="true"] { + background: var(--color-primary) !important; + color: var(--color-grey-white) !important; + } + + &:hover { + background: #E1E7EF; + } + } +} + +.entities { + overflow: auto; + height: 100%; + + .entity { + display: flex; + flex-direction: row; + min-height: 40px; + align-items: center; + width: 100%; + cursor: pointer; + padding: 0px 6px; + + .entity-select { + pointer-events: none; + } + + .entity-name { + padding-left: 16px; + width: 510px; + pointer-events: none; + } + + &:hover { + background: #e1e7fa; + } + } +} + +.field-tree { + position: absolute; + overflow: auto; + top: 16px; + bottom: 24px; + left: 24px; + right: 24px; + transform: translateZ(0); + transform-origin: 0% 0%; + + .field-item { + width: 100%; + padding: 8px 0px; + font-family: Inter; + font-size: 14px; + font-weight: 500; + line-height: 18px; + text-align: left; + display: flex; + justify-content: flex-start; + align-items: center; + + .field-offset { + width: 0px; + height: 24px; + overflow: hidden; + } + + .field-collapse { + width: 24px; + min-width: 24px; + height: 24px; + overflow: hidden; + } + + .field-select { + width: 24px; + min-width: 24px; + height: 24px; + overflow: hidden; + cursor: pointer; + + * { + pointer-events: none; + } + } + + .field-name { + padding-left: 8px; + min-width: 125px; + cursor: pointer; + + &[readonly="true"] { + cursor: default; + } + } + + &[property="false"] { + opacity: 0.4; + } + + &[highlighted="true"] .field-name { + color: var(--guardian-primary-color, #4169E2); + font-weight: 600; + } + + &[search-highlighted="hidden"] { + display: none; + } + + &[search-highlighted="sub"] .field-name { + opacity: 0.5; + } + + &[search-highlighted="highlighted"] .field-name { + color: var(--guardian-success-color, #19BE47); + font-weight: 600; + } + + &[search-highlighted="highlighted"][highlighted="true"] .field-name { + color: var(--guardian-primary-color, #4169E2); + font-weight: 600; + } + } +} + +.nav-container { + display: flex; + width: 260px; + height: 100%; + pointer-events: none; + padding: 0px 24px 0px 2px; + + .guardian-step-container { + width: 264px; + height: fit-content; + padding: 8px 0px 8px 0px; + border-radius: 8px; + box-shadow: none; + background: var(--guardian-background, #FFFFFF); + border: 1px solid #E1E7EF; + + .guardian-step { + padding-left: 24px; + } + + .guardian-step-separator { + margin-left: 16px; + } + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/formulas/dialogs/link-dialog/link-dialog.component.ts b/frontend/src/app/modules/formulas/dialogs/link-dialog/link-dialog.component.ts new file mode 100644 index 0000000000..7c0594fd47 --- /dev/null +++ b/frontend/src/app/modules/formulas/dialogs/link-dialog/link-dialog.component.ts @@ -0,0 +1,120 @@ +import { Component } from '@angular/core'; +import { DialogService, DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { TreeListData, TreeListItem, TreeListView } from 'src/app/modules/common/tree-graph/tree-list'; +import { FieldData } from 'src/app/modules/common/models/schema-node'; +import { IFormulaLink } from '@guardian/interfaces'; + +@Component({ + selector: 'link-dialog', + templateUrl: './link-dialog.component.html', + styleUrls: ['./link-dialog.component.scss'], +}) +export class LinkDialog { + public loading = true; + public schemas: any[]; + public formulas: any[]; + public type: 'schema' | 'formula'; + public step: number = 1; + public entityId: string; + public itemId: string | null; + public items: TreeListView | null; + + constructor( + public ref: DynamicDialogRef, + public config: DynamicDialogConfig, + private dialogService: DialogService, + ) { + this.schemas = this.config.data?.schemas || []; + this.formulas = this.config.data?.formulas || []; + if (this.schemas.length) { + this.type = 'schema'; + } else if (this.formulas.length) { + this.type = 'formula'; + } else { + this.type = 'schema'; + } + const link: IFormulaLink = this.config.data?.link; + if (link) { + this.type = link.type; + this.entityId = link.entityId; + this.itemId = link.item; + } + } + + ngOnInit() { + this.loading = false; + } + + ngOnDestroy(): void { + } + + public onChangeType(type: 'schema' | 'formula') { + this.type = type; + } + + public onSelectEntity(item: any) { + if (this.type === 'schema') { + this.entityId = item.iri; + } + if (this.type === 'formula') { + this.entityId = item.uuid; + } + } + + public onClose(): void { + this.ref.close(null); + } + + public onNext() { + this.step = 2; + this.items = null; + if (this.type === 'schema') { + const schema = this.schemas.find((s) => s.iri === this.entityId); + const fields = TreeListData.fromObject(schema, 'fields'); + this.items = TreeListView.createView(fields, (s) => { return !s.parent }); + } + if (this.type === 'formula') { + const formula = this.formulas.find((s) => s.uuid === this.entityId); + const fields = TreeListData.fromObject(formula.config || {}, 'formulas'); + this.items = TreeListView.createView(fields, (s) => { return !s.parent }); + } + } + + public onPrev() { + this.step = 1; + } + + public onCollapseField(item: TreeListItem) { + if (this.items) { + this.items.collapse(item, !item.collapsed); + this.items.updateHidden(); + } + } + + public onSelectField(item: TreeListItem) { + if (item.expandable) { + this.onCollapseField(item); + return; + } + this.itemId = item.data.path; + } + + public onSelectVariable(item: TreeListItem) { + if (item.expandable) { + this.onCollapseField(item); + return; + } + this.itemId = item.data.uuid; + } + + public onSubmit(): void { + if (this.itemId) { + const result: IFormulaLink = { + type: this.type, + entityId: this.entityId, + item: this.itemId + } + this.ref.close(result); + } + } +} diff --git a/frontend/src/app/modules/formulas/dialogs/new-formula-dialog/new-formula-dialog.component.html b/frontend/src/app/modules/formulas/dialogs/new-formula-dialog/new-formula-dialog.component.html new file mode 100644 index 0000000000..08a6e5bf22 --- /dev/null +++ b/frontend/src/app/modules/formulas/dialogs/new-formula-dialog/new-formula-dialog.component.html @@ -0,0 +1,67 @@ +
+
+
{{title}}
+
+
+ +
+
+
+
+
+
+
+ +
+
+ + +
+
+ + +
+ +
+ + + +
+ Policy + {{ currentPolicy.name }} +
+
+ +
+ {{policy.name}} + ({{policy.version}}) +
+
+
+
+
+ +
+
+ \ No newline at end of file diff --git a/frontend/src/app/modules/formulas/dialogs/new-formula-dialog/new-formula-dialog.component.scss b/frontend/src/app/modules/formulas/dialogs/new-formula-dialog/new-formula-dialog.component.scss new file mode 100644 index 0000000000..2da16109a5 --- /dev/null +++ b/frontend/src/app/modules/formulas/dialogs/new-formula-dialog/new-formula-dialog.component.scss @@ -0,0 +1,58 @@ +.context { + position: relative; + overflow-y: auto; + padding: 14px 0 20px 0; + display: flex; + flex-direction: row; + height: 100%; + font-family: Inter, serif; + font-style: normal; +} + +.dialog-header { + .header-item { + padding: 6px 12px; + margin: 0px 16px; + border-radius: 6px; + font-size: var(--guardian-primary-font-size); + color: var(--guardian-dry-run-color); + background: var(--guardian-dry-run-background); + user-select: none; + cursor: default; + } +} + +form { + width: 100%; + + .guardian-input-container { + input[readonly] { + border: none; + padding-left: 0; + } + } +} + +.guardian-input-container { + margin-bottom: 24px; +} + +.dialog-body { + height: 300px; +} + +.action-buttons { + display: flex; + justify-content: flex-end; + user-select: none; + + &>div { + margin-left: 15px; + margin-right: 0px; + } +} + +.close-icon-color { + fill: #848FA9; + color: #848FA9; +} \ No newline at end of file diff --git a/frontend/src/app/modules/formulas/dialogs/new-formula-dialog/new-formula-dialog.component.ts b/frontend/src/app/modules/formulas/dialogs/new-formula-dialog/new-formula-dialog.component.ts new file mode 100644 index 0000000000..d6e7bab060 --- /dev/null +++ b/frontend/src/app/modules/formulas/dialogs/new-formula-dialog/new-formula-dialog.component.ts @@ -0,0 +1,80 @@ +import { Component } from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { DialogService, DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; + +@Component({ + selector: 'new-formula-dialog', + templateUrl: './new-formula-dialog.component.html', + styleUrls: ['./new-formula-dialog.component.scss'], +}) +export class NewFormulaDialog { + public loading = true; + public policy: any; + public policies: any[]; + public dataForm = new FormGroup({ + name: new FormControl('', Validators.required), + description: new FormControl(''), + policy: new FormControl(null, Validators.required) + }); + public title: string; + public action: string; + public readonly: boolean; + + constructor( + public ref: DynamicDialogRef, + public config: DynamicDialogConfig, + private dialogService: DialogService, + ) { + this.title = this.config.data?.title || ''; + this.action = this.config.data?.action || ''; + this.policies = this.config.data?.policies || []; + this.policies = this.policies.filter((p) => p.topicId); + + const formula = this.config.data?.formula; + const id = this.config.data?.policy?.id; + this.policy = this.policies.find((p) => p.id === id) || null; + if (formula) { + this.readonly = true; + this.dataForm.setValue({ + name: formula.name || 'N\\A', + description: formula.description || 'N\\A', + policy: this.policy + }) + } else { + this.readonly = false; + this.dataForm.setValue({ + name: '', + description: '', + policy: this.policy + }) + } + } + + public get currentPolicy(): any { + return this.dataForm.value.policy; + } + + ngOnInit() { + this.loading = false; + } + + ngOnDestroy(): void { + } + + public onClose(): void { + this.ref.close(null); + } + + public onSubmit(): void { + if (this.dataForm.valid) { + const { name, description, policy } = this.dataForm.value; + this.ref.close({ + name, + description, + policyId: policy?.id, + policyTopicId: policy?.topicId, + policyInstanceTopicId: policy?.instanceTopicId, + }); + } + } +} diff --git a/frontend/src/app/modules/formulas/dialogs/upload-formula-file-dialog/upload-formula-file-dialog.component.html b/frontend/src/app/modules/formulas/dialogs/upload-formula-file-dialog/upload-formula-file-dialog.component.html new file mode 100644 index 0000000000..d8732bbcc7 --- /dev/null +++ b/frontend/src/app/modules/formulas/dialogs/upload-formula-file-dialog/upload-formula-file-dialog.component.html @@ -0,0 +1,98 @@ +
+
+
Upload file
+
+
+ +
+
+
+
+ +
+
+
+ + + + +
+ {{ tab.label }} +
+
+
+
+ +
+ + + + + +
+
+ + +
+
+ + +
+
+
+ +
+
+ + +
+
+ + +
+
+
+ +
+ +
+
+ \ No newline at end of file diff --git a/frontend/src/app/modules/formulas/dialogs/upload-formula-file-dialog/upload-formula-file-dialog.component.scss b/frontend/src/app/modules/formulas/dialogs/upload-formula-file-dialog/upload-formula-file-dialog.component.scss new file mode 100644 index 0000000000..fd493f67b8 --- /dev/null +++ b/frontend/src/app/modules/formulas/dialogs/upload-formula-file-dialog/upload-formula-file-dialog.component.scss @@ -0,0 +1,38 @@ +.close-icon-color { + fill: #848FA9; + color: #848FA9; +} + +.context { + position: relative; + overflow-y: auto; + padding: 0px 0 20px 0; + display: flex; + flex-direction: column; + height: 100%; + font-family: Inter, serif; + font-style: normal; +} + +.dialog-body { + height: 320px; +} + +.action-buttons { + display: flex; + justify-content: flex-end; + user-select: none; + + &>div { + margin-left: 15px; + margin-right: 0px; + } +} + +.form-container { + padding-top: 24px; +} + +.guardian-input-container { + margin-bottom: 24px; +} \ No newline at end of file diff --git a/frontend/src/app/modules/formulas/dialogs/upload-formula-file-dialog/upload-formula-file-dialog.component.ts b/frontend/src/app/modules/formulas/dialogs/upload-formula-file-dialog/upload-formula-file-dialog.component.ts new file mode 100644 index 0000000000..62b20bd6cc --- /dev/null +++ b/frontend/src/app/modules/formulas/dialogs/upload-formula-file-dialog/upload-formula-file-dialog.component.ts @@ -0,0 +1,89 @@ +import { ChangeDetectorRef, Component } from '@angular/core'; +import { UntypedFormBuilder, Validators } from '@angular/forms'; +import { DialogService, DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { IPFS_SCHEMA } from 'src/app/services/api'; +import { IPFSService } from 'src/app/services/ipfs.service'; + +@Component({ + selector: 'upload-formula-file-dialog', + templateUrl: './upload-formula-file-dialog.component.html', + styleUrls: ['./upload-formula-file-dialog.component.scss'], +}) +export class UploadFormulaFileDialog { + public loading = true; + public importType: string = 'file'; + public dataForm = this.fb.group({ + name: ['', Validators.required], + url: ['', Validators.required], + }); + public items = [ + { label: 'Import from file' }, + { label: 'Import from IPFS' }, + ]; + public fileExtension = ''; + public placeholder = 'Upload file'; + public step: number = 0; + + constructor( + public ref: DynamicDialogRef, + public config: DynamicDialogConfig, + private dialogService: DialogService, + private fb: UntypedFormBuilder, + private ipfs: IPFSService, + private changeDetectorRef: ChangeDetectorRef, + ) { + + } + + ngOnInit() { + this.loading = false; + this.importType = 'file'; + this.step = 0; + } + + ngOnDestroy(): void { + } + + public setImportType(event: any): void { + this.importType = event.index === 0 ? 'file' : 'url'; + this.step = 0; + this.dataForm.setValue({ + name: '', + url: '' + }) + this.changeDetectorRef.detectChanges(); + } + + public onClose(): void { + this.ref.close(null); + } + + public onSubmit(): void { + if (this.dataForm.valid) { + const { name, url } = this.dataForm.value; + this.ref.close({ + name, + url, + type: this.importType + }); + } + } + + public importFromFile(file: any) { + const name = file.name; + this.upload(name, file); + } + + private upload(name: string, file: any) { + this.loading = true; + this.ipfs.addFile(file) + .subscribe((res) => { + const url = IPFS_SCHEMA + res; + this.dataForm.setValue({ name, url }); + this.step = 1; + this.loading = false; + }, (error) => { + this.loading = false; + }); + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/formulas/formula-configuration/formula-configuration.component.html b/frontend/src/app/modules/formulas/formula-configuration/formula-configuration.component.html new file mode 100644 index 0000000000..7c0d1acbcd --- /dev/null +++ b/frontend/src/app/modules/formulas/formula-configuration/formula-configuration.component.html @@ -0,0 +1,361 @@ +
+
+
+
+ +
+ Before starting work you need to get DID + here +
+ +
+
+ +
+ +
+ {{item?.name}} +
+ Policy Name: {{policy.name}} + Version: {{policy.version}} +
+
+
+ +
+
+
+
+
+ + +
+
Overview
+
+
+
+
+ + +
+
Edit Formula
+
+
+
+
+ + +
+
Attach Files
+
+
+
+
+ +
+ +
+
+
+ + +
+
+ + +
+ +
+ + +
+
+
+
+ +
+
Select Type
+ Constant + Variable + Formula + Text +
+
+
+
+ + +
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+ +
Constant
+
Variable
+
Formula
+
Text
+
+ +
+
+
+
+ + +
+ + +
+ + +
+
+ + +
+ + +
+
+ + +
+ + +
+
+ + +
+
+ + + +
+ {{ option.name || 'N/A' }} +
+
+ +
+ {{ option.name || 'N/A' }} + - {{ option.description }} +
+
+
+
+
+ +
+ + +
+
+ + +
+
+ + + +
+ {{ option.name || 'N/A' }} +
+
+ +
+ {{ option.name || 'N/A' }} + - {{ option.description }} +
+
+
+
+
+ +
+ + +
+
+
+
+
+ + There are no components added yet + Please add a new component to see the data +
+
+
+ + +
+ +
+
+
+
+
+
NAME
+
URL
+
+
+
+
+
+ {{file.name}} +
+
+ +
+
+ +
+
+
+
+
+
+ + +
+
There were no files attached yet.
+
+
+
+ +
+
+
+ + + + + + + + + + +
+
\ No newline at end of file diff --git a/frontend/src/app/modules/formulas/formula-configuration/formula-configuration.component.scss b/frontend/src/app/modules/formulas/formula-configuration/formula-configuration.component.scss new file mode 100644 index 0000000000..2afb2c8923 --- /dev/null +++ b/frontend/src/app/modules/formulas/formula-configuration/formula-configuration.component.scss @@ -0,0 +1,684 @@ +.guardian-page { + position: relative; + padding: 0px; + user-select: none; + + .header-container { + padding: 56px 48px 10px 48px; + background: var(--guardian-primary-color, #4169E2); + min-height: 178px; + height: 178px; + position: relative; + z-index: 2; + + .guardian-user-back-button { + button { + border-color: var(--guardian-background, #FFFFFF); + } + } + + .guardian-user-page-header { + color: var(--guardian-background, #FFFFFF); + } + + + .policy-name { + color: var(--guardian-background, #FFFFFF); + font-size: 14px; + font-weight: 500; + line-height: 16px; + position: absolute; + top: 34px; + right: 0; + + .policy-version { + padding-left: 16px; + } + } + } + + .step-container { + min-height: 40px; + height: 40px; + width: 100%; + display: flex; + justify-content: center; + padding: 0px; + background: var(--guardian-background, #FFFFFF); + border-bottom: 1px solid var(--guardian-border-color, #E1E7EF); + position: relative; + box-shadow: 0px 4px 8px 0px var(--guardian-shadow, #00000014); + position: relative; + z-index: 2; + } + + .overview-viewer { + position: relative; + z-index: 0; + + form { + box-shadow: 0px 4px 8px 0px var(--guardian-shadow, #00000014); + background: var(--guardian-background, #FFFFFF); + padding: 24px 24px 2px 24px; + border-radius: 8px; + + .guardian-input-container { + margin-bottom: 24px; + } + } + } + + .body-container { + position: relative; + z-index: 0; + width: 100%; + height: 100%; + user-select: none; + position: relative; + padding: 32px 48px 16px 48px; + overflow: auto; + + .item-filters { + display: flex; + flex-direction: row; + margin-bottom: 24px; + + .item-filters-header { + font-family: Inter; + font-size: 14px; + font-weight: 400; + line-height: 16px; + color: #848FA9; + margin-right: 24px; + height: 24px; + padding: 6px 0px; + } + + .item-filter { + margin-right: 8px; + height: 28px; + min-width: 108px; + } + + .constant-filter { + --checkbox-button-on-first-color: var(--formula-constant-color, #4169E2); + --checkbox-button-on-second-color: var(--formula-constant-background, #e1e7fa); + } + + .variable-filter { + --checkbox-button-on-first-color: var(--formula-variable-color, #DA9B22); + --checkbox-button-on-second-color: var(--formula-variable-background, #FFF6E3); + } + + .formula-filter { + --checkbox-button-on-first-color: var(--formula-function-color, #E50299); + --checkbox-button-on-second-color: var(--formula-function-background, #fbd9ef); + } + + .text-filter { + --checkbox-button-on-first-color: var(--formula-text-color, #19BE47); + --checkbox-button-on-second-color: var(--formula-text-background, #d7f5e2); + } + } + } + + &[keyboard="true"] { + .body-container { + // top: -270px; + margin-bottom: 265px; + } + } + + .action-container { + position: relative; + z-index: 1; + height: 64px; + min-height: 64px; + display: flex; + flex-direction: row; + border-top: 1px solid var(--guardian-grey-color-2, #E1E7EF); + background: #fff; + padding: 12px 48px; + position: relative; + + button { + height: 40px; + margin-right: 18px; + min-width: 140px; + } + + menu-button { + height: 40px; + width: 210px; + } + + .save-button { + position: absolute; + top: 12px; + right: 48px; + margin-right: 0px; + } + } +} + +.file-menu { + display: flex; + justify-content: flex-end; + height: 40px; + width: 100%; + + button { + min-width: 125px; + } +} + +.files-viewer { + padding: 16px 48px 32px 48px; + position: absolute; + left: 0; + right: 0; + top: 80px; + bottom: 0; + overflow: auto; + + .variables-container { + box-shadow: 0px 4px 8px 0px var(--guardian-shadow, #00000014); + background: var(--guardian-background, #FFFFFF); + padding: 24px; + border-radius: 8px; + margin-bottom: 16px; + min-width: 1300px; + + .variables-header { + color: var(--guardian-font-color, #23252E); + font-size: 16px; + font-weight: 600; + line-height: 20px; + text-align: left; + padding-bottom: 16px; + } + + .variables-empty-grid { + font-family: Inter; + font-size: 14px; + font-weight: 400; + color: var(--guardian-disabled-color, #848FA9); + height: 87px; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + } + + .variables-grid { + overflow: auto; + display: grid; + + .cell-48 { + min-width: 48px; + width: 48px; + max-width: 48px; + } + + .cell-56 { + min-width: 56px; + width: 56px; + max-width: 56px; + } + + .cell-64 { + min-width: 64px; + width: 64px; + max-width: 64px; + } + + .cell-84 { + min-width: 84px; + width: 84px; + max-width: 84px; + } + + .cell-125 { + min-width: 115px; + width: 115px; + max-width: 115px; + } + + .cell-150 { + min-width: 150px; + width: 150px; + max-width: 150px; + } + + .cell-200 { + min-width: 200px; + width: 200px; + max-width: 200px; + } + + .cell-250 { + min-width: 250px; + width: 250px; + max-width: 250px; + } + + .cell-300 { + min-width: 300px; + width: 300px; + max-width: 300px; + } + + .cell-max { + min-width: 250px; + width: auto; + } + + .cell-btn { + button { + width: 24px; + height: 24px; + margin-left: 7px; + } + } + + .cell-multiselect { + padding: 1px 10px !important; + } + + .cell-select { + padding: 1px 1px !important; + + &::ng-deep { + .p-dropdown-label { + padding-left: 16px; + } + } + } + + .variables-grid-cell { + min-height: 40px; + padding: 6px 16px; + display: flex; + align-items: center; + width: 100%; + position: relative; + font-family: Inter; + font-size: 14px; + font-weight: 400; + text-align: left; + user-select: text; + + .cell-focus { + display: none; + position: absolute; + top: -1px; + left: -1px; + right: -1px; + bottom: -1px; + border: 1px solid var(--guardian-primary-color, #4169E2); + } + + &:first-child { + .cell-focus { + left: -9px; + } + } + + .p-inputwrapper-focus+.cell-focus, + .guardian-input:focus+.cell-focus { + display: block; + } + } + + .variables-grid-cell-transparent { + border-right: 1px solid transparent !important; + } + + .variables-grid-row { + &:first-child .variables-grid-cell:first-child .cell-focus { + border-top-left-radius: 8px; + } + + &:last-child .variables-grid-cell:first-child .cell-focus { + border-bottom-left-radius: 8px; + } + } + + .variables-grid-header { + display: flex; + flex-direction: row; + padding: 0px 8px; + width: 100%; + min-width: 100%; + + &>div { + color: var(--guardian-disabled-color, #848FA9); + font-family: Inter; + font-size: 12px; + font-weight: 400; + text-align: left; + } + } + + .variables-grid-body { + width: 100%; + min-width: 100%; + border: 1px solid var(--guardian-border-color, #E1E7EF); + border-radius: 8px; + + .variables-grid-row { + min-height: 40px; + display: flex; + flex-direction: row; + border-bottom: 1px solid var(--guardian-border-color, #E1E7EF); + padding: 0px 8px; + + &>div { + border-right: 1px solid var(--guardian-border-color, #E1E7EF); + + &:last-child { + border-right: none; + } + } + + &:last-child { + border-bottom: none; + } + } + + .rule-type-name { + background: #E1E7EF; + height: 28px; + padding: 4px 10px 4px 10px; + border-radius: 6px; + font-family: Inter; + font-size: 14px; + font-weight: 500; + line-height: 16px; + text-align: left; + color: #848FA9; + text-transform: capitalize; + display: flex; + justify-content: center; + align-items: center; + } + } + } + + .variables-actions { + padding-top: 24px; + + button { + height: 28px; + width: 120px; + } + } + } + + .cell-option-values { + .cell-option-value { + width: 270px; + height: 30px; + padding: 7px 8px 7px 8px; + border-radius: 6px; + background: var(--guardian-grey-color, #EFF3F7); + font-family: Inter; + font-size: 14px; + font-weight: 400; + line-height: 16px; + text-align: left; + color: var(--guardian-disabled-color, #848FA9); + margin-bottom: 8px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + } +} + +.formula-link { + height: 40px; + border-radius: 8px; + outline: none; + border: 1px solid var(--guardian-grey-color-2, #E1E7EF); + font-family: Inter; + font-size: 14px; + font-weight: 400; + color: #000; + padding: 10px 16px; + position: relative; + cursor: pointer; + + &:hover { + border: 1px solid var(--guardian-primary-color, #4169E2); + } + + .formula-link-remove { + position: absolute; + top: 4px; + right: 4px; + height: 30px; + width: 30px; + + button { + height: 30px; + width: 30px; + } + } + + .formula-link-value { + display: flex; + flex-direction: row; + + &__entity { + font-family: Inter; + font-size: 14px; + font-weight: 500; + line-height: 16px; + color: var(--guardian-entity-color, #DA9B22); + margin-right: 8px; + } + + &__item { + font-family: Inter; + font-size: 14px; + font-weight: 400; + line-height: 16px; + } + } +} + +.guardian-input-container { + margin-bottom: 16px; +} + +.guardian-textarea-container { + margin-bottom: 16px; +} + +.multiselect-selected-value { + width: auto; + height: 30px; + padding: 7px 12px 7px 12px; + border-radius: 6px; + background: var(--guardian-grey-color, #EFF3F7); + color: var(--guardian-disabled-color, #848FA9); + font-family: Inter; + font-size: 14px; + font-weight: 400; + line-height: 16px; + text-align: left; + margin-right: 8px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + display: inline-flex; +} + +.multiselect-selected-value:last-child { + margin-right: 0px; +} + +.relationships-multiselect::ng-deep .p-multiselect-label { + padding: 4px 8px !important; +} + +.not-exist { + position: absolute; + left: 48px; + right: 48px; + bottom: 56px; + top: 50%; + transform: translate(0px, -50%); + color: var(--color-grey-black-2); + font-family: Inter, serif; + font-size: 20px; + font-style: normal; + font-weight: 400; + line-height: 16px; + display: flex; + flex-direction: column; + align-items: center; + row-gap: 8px; + justify-content: center; + + .info-text { + color: var(--color-grey-5, #848fa9); + text-align: center; + font-family: Inter, sans-serif; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: normal; + } + + .info-text-strong { + color: var(--color-grey-5, #848fa9); + text-align: center; + font-family: Inter, sans-serif; + font-size: 16px; + font-style: normal; + font-weight: 600; + line-height: normal; + } +} + +@media (max-height: 767px) { + .guardian-page[keyboard="true"] { + .body-container { + // top: -270px; + margin-bottom: 190px; + } + } +} + +.formula-item { + box-shadow: 0px 4px 8px 0px var(--guardian-shadow, #00000014); + background: var(--guardian-background, #FFFFFF); + border-radius: 8px; + margin-bottom: 16px; + display: flex; + flex-direction: row; + overflow: hidden; + + .formula-item-icon { + width: 32px; + min-width: 32px; + width: 32px; + min-width: 32px; + display: flex; + justify-content: center; + align-items: center; + cursor: move; + + * { + pointer-events: none; + } + + &[readonly="true"] { + cursor: default; + } + } + + .formula-item-container { + width: 100%; + padding: 16px; + + .formula-item-header { + height: 24px; + width: 100%; + margin-bottom: 16px; + position: relative; + display: flex; + + &__icon { + margin-right: 8px; + } + + &__name { + font-family: Poppins; + font-size: 16px; + font-weight: 600; + height: 24px; + line-height: 24px; + } + + &__delete { + position: absolute; + top: 0px; + right: 0px; + width: 24px; + height: 24px; + + .guardian-icon-button { + width: 24px; + height: 24px; + } + } + } + + .formula-item-body {} + } + + &[type="constant"] { + border: 1px solid var(--formula-constant-color, #4169E2); + + .formula-item-icon { + background: var(--formula-constant-background, #e1e7fa); + border-right: 1px solid #E1E7EF; + } + } + + &[type="variable"] { + border: 1px solid var(--formula-variable-color, #DA9B22); + + .formula-item-icon { + background: var(--formula-variable-background, #FFF6E3); + border-right: 1px solid #E1E7EF; + } + } + + &[type="formula"] { + border: 1px solid var(--formula-function-color, #E50299); + + .formula-item-icon { + background: var(--formula-function-background, #fbd9ef); + border-right: 1px solid #E1E7EF; + } + } + + &[type="text"] { + border: 1px solid var(--formula-text-color, #19BE47); + + .formula-item-icon { + background: var(--formula-text-background, #d7f5e2); + border-right: 1px solid #E1E7EF; + } + } +} + +.cdk-drag.formula-item.cdk-drag-preview {} + +.formula-item.cdk-drag-placeholder { + opacity: 0.5; +} \ No newline at end of file diff --git a/frontend/src/app/modules/formulas/formula-configuration/formula-configuration.component.ts b/frontend/src/app/modules/formulas/formula-configuration/formula-configuration.component.ts new file mode 100644 index 0000000000..c2fe069366 --- /dev/null +++ b/frontend/src/app/modules/formulas/formula-configuration/formula-configuration.component.ts @@ -0,0 +1,393 @@ +import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { IFormulaItem, FormulaItemType, IFormulaLink, Schema, UserPermissions, EntityStatus } from '@guardian/interfaces'; +import { forkJoin, Subscription } from 'rxjs'; +import { ProfileService } from 'src/app/services/profile.service'; +import { FormulasService } from 'src/app/services/formulas.service'; +import { CustomConfirmDialogComponent } from '../../common/custom-confirm-dialog/custom-confirm-dialog.component'; +import { DialogService } from 'primeng/dynamicdialog'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { MathLiveComponent } from '../../common/mathlive/mathlive.component'; +import { LinkDialog } from '../dialogs/link-dialog/link-dialog.component'; +import { Formulas } from '../models/formulas'; +import { CdkDragDrop } from '@angular/cdk/drag-drop'; +import { UploadFormulaFileDialog } from '../dialogs/upload-formula-file-dialog/upload-formula-file-dialog.component'; + +@Component({ + selector: 'app-formula-configuration', + templateUrl: './formula-configuration.component.html', + styleUrls: ['./formula-configuration.component.scss'], +}) +export class FormulaConfigurationComponent implements OnInit { + public readonly title: string = 'Configuration'; + + @ViewChild('body', { static: true }) body: ElementRef; + + public loading: boolean = true; + public isConfirmed: boolean = false; + public user: UserPermissions = new UserPermissions(); + public owner: string; + + private subscription = new Subscription(); + private formulaId: string; + + public item: any; + public policy: any; + public schemas: Schema[]; + public formulas: any[]; + public readonly: boolean = false; + public keyboard: boolean = false; + + public config: Formulas = new Formulas(); + public readonly options = [ + { + id: 'constant', + text: 'Add New Constant', + icon: 'const', + color: 'icon-color-primary' + }, + { + id: 'variable', + text: 'Add New Variable', + icon: 'variable', + color: 'icon-color-primary' + }, + { + id: 'formula', + text: 'Add New Formula', + icon: 'function', + color: 'icon-color-primary' + }, + { + id: 'text', + text: 'Add New Text', + icon: 'text', + color: 'icon-color-primary' + } + ]; + + public readonly filters = { + constant: true, + variable: true, + formula: true, + text: true + } + + public stepper = [true, false]; + + public overviewForm = new FormGroup({ + name: new FormControl('', Validators.required), + description: new FormControl(''), + policy: new FormControl('', Validators.required), + }); + + private schemasMap: Map = new Map(); + private schemasFieldMap: Map = new Map(); + private formulasMap: Map = new Map(); + private formulasFieldMap: Map = new Map(); + + constructor( + private profileService: ProfileService, + private formulasService: FormulasService, + private dialogService: DialogService, + private router: Router, + private route: ActivatedRoute + ) { + } + + ngOnInit() { + this.subscription.add( + this.route.queryParams.subscribe((queryParams) => { + this.loadProfile(); + }) + ); + } + + ngOnDestroy(): void { + this.subscription.unsubscribe(); + } + + private loadProfile() { + this.isConfirmed = false; + this.loading = true; + this.profileService + .getProfile() + .subscribe((profile) => { + this.isConfirmed = !!(profile && profile.confirmed); + this.user = new UserPermissions(profile); + this.owner = this.user.did; + + if (this.isConfirmed) { + this.loadData(); + } else { + setTimeout(() => { + this.loading = false; + }, 500); + } + }, (e) => { + this.loading = false; + }); + } + + private loadData() { + this.formulaId = this.route.snapshot.params['formulaId']; + this.loading = true; + forkJoin([ + this.formulasService.getFormula(this.formulaId), + this.formulasService.getRelationships(this.formulaId), + ]).subscribe(([item, relationships]) => { + this.item = item; + this.readonly = this.item?.status === EntityStatus.PUBLISHED; + this.updateRelationships(relationships); + + this.overviewForm.setValue({ + name: item.name || '', + description: item.description || '', + policy: this.policy?.name || '', + }); + this.config.fromData(item?.config); + + setTimeout(() => { + this.loading = false; + }, 1000); + }, (e) => { + this.loading = false; + }); + } + + private updateRelationships(relationships: any) { + this.policy = relationships?.policy || {}; + const schemas = relationships?.schemas || []; + const formulas = relationships?.formulas || []; + + this.schemas = []; + for (const schema of schemas) { + try { + const item = new Schema(schema); + this.schemas.push(item); + } catch (error) { + console.log(error); + } + } + + this.schemasMap.clear(); + this.schemasFieldMap.clear(); + for (const schema of this.schemas) { + this.schemasMap.set(String(schema.iri), String(schema.name)); + const fields = schema.getFields(); + for (const field of fields) { + this.schemasFieldMap.set(`${schema.iri}.${field.path}`, String(field.description)); + } + } + + this.formulas = []; + for (const formula of formulas) { + this.formulas.push(formula); + } + + this.formulasMap.clear(); + this.formulasFieldMap.clear(); + this.formulasMap.set(String(this.item.uuid), String(this.item.name)); + for (const formula of this.formulas) { + this.formulasMap.set(String(formula.uuid), String(formula.name)); + const fields = formula?.config?.formulas || []; + for (const field of fields) { + this.formulasFieldMap.set(`${formula.uuid}.${field.uuid}`, String(field.name)); + } + } + } + + public onBack() { + this.router.navigate(['/formulas']); + } + + public onSave() { + this.loading = true; + const config = this.config.getJson(); + const value = this.overviewForm.value; + const item = { + ...this.item, + name: value.name, + description: value.description, + config + }; + this.formulasService + .updateFormula(item) + .subscribe((data) => { + this.item = data; + this.formulasMap.set(String(this.item.uuid), String(this.item.name)); + setTimeout(() => { + this.loading = false; + }, 1000); + }, (e) => { + this.loading = false; + }); + } + + public addItem(option: any) { + const type: FormulaItemType = option.id; + this.config.add(type); + } + + public deleteItem(item: any) { + const dialogRef = this.dialogService.open(CustomConfirmDialogComponent, { + showHeader: false, + width: '640px', + styleClass: 'guardian-dialog', + data: { + header: 'Delete item', + text: 'Are you sure want to delete item?', + buttons: [{ + name: 'Close', + class: 'secondary' + }, { + name: 'Delete', + class: 'delete' + }] + }, + }); + dialogRef.onClose.subscribe((result: string) => { + if (result === 'Delete') { + this.config.delete(item); + } + }); + } + + public deleteFile(file: any) { + const dialogRef = this.dialogService.open(CustomConfirmDialogComponent, { + showHeader: false, + width: '640px', + styleClass: 'guardian-dialog', + data: { + header: 'Delete file', + text: 'Are you sure want to delete file?', + buttons: [{ + name: 'Close', + class: 'secondary' + }, { + name: 'Delete', + class: 'delete' + }] + }, + }); + dialogRef.onClose.subscribe((result: string) => { + if (result === 'Delete') { + this.config.deleteFile(file); + } + }); + } + + public onFilter() { + this.config.setFilters(this.filters); + } + + public onStep(index: number) { + this.loading = true; + for (let i = 0; i < this.stepper.length; i++) { + this.stepper[i] = false; + } + this.stepper[index] = true; + this.loading = false; + this.keyboard = false; + } + + public isActionStep(index: number): boolean { + return this.stepper[index]; + } + + public onKeyboard($event: boolean) { + this.keyboard = $event; + } + + public onKeyboardFocus($event: MathLiveComponent) { + setTimeout(() => { + if (this.keyboard) { + const focus = $event.getElement(); + const scroll = this.body; + const targetRect = focus.nativeElement.getBoundingClientRect(); + const scrollRect = scroll.nativeElement.getBoundingClientRect(); + const y = targetRect.y - scrollRect.y; + const height = scrollRect.height; + const d = y - height + 60; + if (d > 0) { + scroll.nativeElement.scrollTop += d; + } + } + }); + } + + public onLink(item: IFormulaItem) { + if (this.readonly) { + return; + } + const dialogRef = this.dialogService.open(LinkDialog, { + showHeader: false, + width: '800px', + styleClass: 'guardian-dialog', + data: { + link: item.link, + schemas: this.schemas, + formulas: [ + this.item, + ...this.formulas + ] + }, + }); + dialogRef.onClose.subscribe((result: IFormulaLink | null) => { + if (result) { + item.link = result + } + }); + } + + public getEntityName(link: IFormulaLink): string { + if (link.type === 'schema') { + return this.schemasMap.get(link.entityId) || ''; + } + if (link.type === 'formula') { + return this.formulasMap.get(link.entityId) || ''; + } + return ''; + } + + public getFieldName(link: IFormulaLink): string { + if (link.type === 'schema') { + return this.schemasFieldMap.get(`${link.entityId}.${link.item}`) || ''; + } + if (link.type === 'formula') { + if (link.entityId === this.item?.uuid) { + return this.config.getItem(link.item)?.name || ''; + } else { + return this.formulasFieldMap.get(`${link.entityId}.${link.item}`) || ''; + } + } + return ''; + } + + public deleteLink(item: IFormulaItem, $event: any) { + if (this.readonly) { + return; + } + $event.preventDefault(); + $event.stopPropagation(); + item.link = null; + } + + public drop(event: CdkDragDrop) { + this.config.reorder(event.previousIndex, event.currentIndex); + } + + public uploadFile() { + const dialogRef = this.dialogService.open(UploadFormulaFileDialog, { + showHeader: false, + width: '640px', + styleClass: 'guardian-dialog', + data: {}, + }); + dialogRef.onClose.subscribe((result: any) => { + if(result) { + this.config.addFile(result); + } + }); + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/formulas/formulas.module.ts b/frontend/src/app/modules/formulas/formulas.module.ts new file mode 100644 index 0000000000..a8bcc64637 --- /dev/null +++ b/frontend/src/app/modules/formulas/formulas.module.ts @@ -0,0 +1,67 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { AngularSvgIconModule } from 'angular-svg-icon'; +import { InputTextModule } from 'primeng/inputtext'; +import { DialogService, DynamicDialogModule } from 'primeng/dynamicdialog'; +import { TableModule } from 'primeng/table'; +import { TooltipModule } from 'primeng/tooltip'; +import { DropdownModule } from 'primeng/dropdown'; +import { TabViewModule } from 'primeng/tabview'; +import { CheckboxModule } from 'primeng/checkbox'; +import { RadioButtonModule } from 'primeng/radiobutton'; +import { MultiSelectModule } from 'primeng/multiselect'; +import { OverlayPanelModule } from 'primeng/overlaypanel'; +import { DragDropModule } from 'primeng/dragdrop'; +import { TreeModule } from 'primeng/tree'; +import { TreeDragDropService } from 'primeng/api'; +import { TieredMenuModule } from 'primeng/tieredmenu'; +import { AppRoutingModule } from 'src/app/app-routing.module'; +import { MaterialModule } from 'src/app/modules/common/material.module'; +import { CommonComponentsModule } from '../common/common-components.module'; +import { LinkDialog } from './dialogs/link-dialog/link-dialog.component'; +import { FormulasComponent } from './formulas/formulas.component'; +import { FormulaConfigurationComponent } from './formula-configuration/formula-configuration.component'; +import { NewFormulaDialog } from './dialogs/new-formula-dialog/new-formula-dialog.component'; +import { FormulasViewDialog } from './dialogs/formulas-view-dialog/formulas-view-dialog.component'; +import { UploadFormulaFileDialog } from './dialogs/upload-formula-file-dialog/upload-formula-file-dialog.component'; + +@NgModule({ + declarations: [ + FormulasComponent, + FormulaConfigurationComponent, + NewFormulaDialog, + LinkDialog, + FormulasViewDialog, + UploadFormulaFileDialog + ], + imports: [ + CommonModule, + FormsModule, + MaterialModule, + CommonComponentsModule, + AppRoutingModule, + DynamicDialogModule, + TableModule, + TooltipModule, + InputTextModule, + DropdownModule, + TabViewModule, + CheckboxModule, + RadioButtonModule, + MultiSelectModule, + OverlayPanelModule, + DragDropModule, + TreeModule, + TieredMenuModule, + AngularSvgIconModule.forRoot(), + ], + exports: [ + FormulasViewDialog + ], + providers: [ + DialogService, + TreeDragDropService + ], +}) +export class FormulasModule { } diff --git a/frontend/src/app/modules/formulas/formulas/formulas.component.html b/frontend/src/app/modules/formulas/formulas/formulas.component.html new file mode 100644 index 0000000000..874f221ddb --- /dev/null +++ b/frontend/src/app/modules/formulas/formulas/formulas.component.html @@ -0,0 +1,189 @@ +
+
+
+
+ +
+ Before starting work you need to get DID + here +
+ + + +
+
+ + +
+ Policy + {{ currentPolicy.name }} +
+
+ Policy + All +
+
+ +
+ {{policy.name}} + ({{policy.version}}) +
+
+ All +
+
+
+
+
+ + + +
+
+ +
+ +
+ + + + + {{column.title}} + + + + + + + + + + {{ row.policyTopicId }} + + + + + {{ row.policy }} + + + + + + +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ + {{row[column.id]}} + + +
+ +
+
+
+ +
+
+
+ +
+ + There were no formula created yet + Please create new formula to see the data +
+
+
+
\ No newline at end of file diff --git a/frontend/src/app/modules/formulas/formulas/formulas.component.scss b/frontend/src/app/modules/formulas/formulas/formulas.component.scss new file mode 100644 index 0000000000..1f69ba9431 --- /dev/null +++ b/frontend/src/app/modules/formulas/formulas/formulas.component.scss @@ -0,0 +1,43 @@ +.page-header { + font-size: 32px; + font-weight: 600; + color: var(--guardian-header-color, #000); + height: 72px; + padding: 8px 0px 26px 0px; + position: relative; +} + +.policy-name { + color: var(--guardian-disabled-color, #848FA9); + font-size: 14px; + font-weight: 500; + line-height: 16px; + position: absolute; + top: 34px; + right: 0; + + .policy-version { + padding-left: 16px; + } +} +.guardian-dropdown { + &::ng-deep .p-dropdown { + width: 250px; + } +} + +.grid-btn { + width: 80px; + height: 30px; + margin-right: 16px; + + &:last-child { + margin-right: 0px; + } +} + +a { + color: var(--color-primary); + cursor: pointer; + text-decoration: underline; +} \ No newline at end of file diff --git a/frontend/src/app/modules/formulas/formulas/formulas.component.ts b/frontend/src/app/modules/formulas/formulas/formulas.component.ts new file mode 100644 index 0000000000..c5ff707d54 --- /dev/null +++ b/frontend/src/app/modules/formulas/formulas/formulas.component.ts @@ -0,0 +1,393 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { EntityStatus, PolicyType, UserPermissions } from '@guardian/interfaces'; +import { forkJoin, Subscription } from 'rxjs'; +import { PolicyEngineService } from 'src/app/services/policy-engine.service'; +import { ProfileService } from 'src/app/services/profile.service'; +import { DialogService } from 'primeng/dynamicdialog'; +import { FormulasService } from 'src/app/services/formulas.service'; +import { CustomConfirmDialogComponent } from '../../common/custom-confirm-dialog/custom-confirm-dialog.component'; +import { IImportEntityResult, ImportEntityDialog, ImportEntityType } from '../../common/import-entity-dialog/import-entity-dialog.component'; +import { NewFormulaDialog } from '../dialogs/new-formula-dialog/new-formula-dialog.component'; + +interface IColumn { + id: string; + title: string; + type: string; + size: string; + tooltip: boolean; + permissions?: (user: UserPermissions) => boolean; + canDisplay?: () => boolean; +} + +@Component({ + selector: 'app-formulas', + templateUrl: './formulas.component.html', + styleUrls: ['./formulas.component.scss'], +}) +export class FormulasComponent implements OnInit { + public readonly title: string = 'Formulas'; + + public loading: boolean = true; + public isConfirmed: boolean = false; + public user: UserPermissions = new UserPermissions(); + public owner: string; + public page: any[]; + public pageIndex: number; + public pageSize: number; + public pageCount: number; + public columns: IColumn[]; + public allPolicies: any[] = []; + public currentPolicy: any = null; + + private subscription = new Subscription(); + + constructor( + private profileService: ProfileService, + private formulasService: FormulasService, + private policyEngineService: PolicyEngineService, + private dialogService: DialogService, + private router: Router, + private route: ActivatedRoute + ) { + this.columns = [{ + id: 'name', + title: 'Name', + type: 'text', + size: 'auto', + tooltip: true + }, { + id: 'policy', + title: 'Policy', + type: 'text', + size: 'auto', + tooltip: false + }, { + id: 'topic', + title: 'Topic', + type: 'text', + size: '180', + tooltip: false + }, { + id: 'status', + title: 'Status', + type: 'text', + size: '180', + tooltip: false + }, { + id: 'edit', + title: '', + type: 'text', + size: '56', + tooltip: false + }, { + id: 'export', + title: '', + type: 'text', + size: '56', + tooltip: false + }, { + id: 'delete', + title: '', + type: 'text', + size: '64', + tooltip: false + }] + } + + public statuses = [{ + label: 'Draft', + value: EntityStatus.DRAFT, + description: 'Return to editing.', + disable: true + }, { + label: 'Published', + value: EntityStatus.PUBLISHED, + description: 'Release version into public domain.', + disable: (value: string, item?: any): boolean => { + return ( + (value !== EntityStatus.DRAFT && value !== EntityStatus.ERROR) || + (item?.policyStatus !== PolicyType.PUBLISH) + ); + } + }, { + label: 'Error', + value: EntityStatus.ERROR, + description: '', + disable: true + }] + + ngOnInit() { + this.page = []; + this.pageIndex = 0; + this.pageSize = 10; + this.pageCount = 0; + this.subscription.add( + this.route.queryParams.subscribe((queryParams) => { + this.loadProfile(); + }) + ); + } + + ngOnDestroy(): void { + this.subscription.unsubscribe(); + } + + private loadProfile() { + this.isConfirmed = false; + this.loading = true; + forkJoin([ + this.profileService.getProfile(), + this.policyEngineService.all(), + ]).subscribe(([profile, policies]) => { + this.isConfirmed = !!(profile && profile.confirmed); + this.user = new UserPermissions(profile); + this.owner = this.user.did; + this.allPolicies = policies || []; + this.allPolicies.unshift({ + name: 'All', + id: null + }); + this.allPolicies.forEach((p: any) => p.label = p.name); + + const policy = this.route.snapshot.queryParams['policy']; + if(policy) { + this.currentPolicy = this.allPolicies.find((p) => p.id === policy); + } + + if(!this.currentPolicy) { + this.currentPolicy = this.allPolicies[0]; + } + + if (this.isConfirmed) { + this.loadData(); + } else { + setTimeout(() => { + this.loading = false; + }, 500); + } + }, (e) => { + this.loading = false; + }); + } + + private loadData() { + const filters: any = {}; + if (this.currentPolicy?.id) { + filters.policyId = this.currentPolicy?.id; + } + this.loading = true; + this.formulasService + .getFormulas( + this.pageIndex, + this.pageSize, + filters + ) + .subscribe((response) => { + const { page, count } = this.formulasService.parsePage(response); + this.page = page; + this.pageCount = count; + for (const item of this.page) { + const policy = this.allPolicies.find((p) => p.id && p.id === item.policyId); + item.policy = policy?.name; + item.policyStatus = policy?.status; + } + setTimeout(() => { + this.loading = false; + }, 500); + }, (e) => { + this.loading = false; + }); + } + + public onBack() { + this.router.navigate(['/policy-viewer']); + } + + public onPage(event: any): void { + if (this.pageSize != event.pageSize) { + this.pageIndex = 0; + this.pageSize = event.pageSize; + } else { + this.pageIndex = event.pageIndex; + this.pageSize = event.pageSize; + } + this.loadData(); + } + + public onFilter(event: any) { + if (event.value === null) { + this.currentPolicy = this.allPolicies[0]; + } + this.pageIndex = 0; + const policy = this.currentPolicy?.id || 'all' + this.router.navigate(['/formulas'], { queryParams: { policy } }); + this.loadData(); + } + + public onCreate() { + const dialogRef = this.dialogService.open(NewFormulaDialog, { + showHeader: false, + width: '720px', + styleClass: 'guardian-dialog', + data: { + title: 'Create New', + policies: this.allPolicies, + policy: this.currentPolicy, + action: 'Create' + } + }); + dialogRef.onClose.subscribe(async (result) => { + if (result) { + this.loading = true; + this.formulasService + .createFormula(result) + .subscribe((newItem) => { + this.loadData(); + }, (e) => { + this.loading = false; + }); + } + }); + } + + public onEdit(item: any) { + this.router.navigate(['/formulas', item.id]); + } + + public onImport() { + const dialogRef = this.dialogService.open(ImportEntityDialog, { + showHeader: false, + width: '720px', + styleClass: 'guardian-dialog', + data: { + type: ImportEntityType.Formula, + } + }); + dialogRef.onClose.subscribe(async (result: IImportEntityResult | null) => { + if (result) { + this.importDetails(result); + } + }); + } + + private importDetails(result: IImportEntityResult) { + const { type, data, formula } = result; + const dialogRef = this.dialogService.open(NewFormulaDialog, { + showHeader: false, + width: '720px', + styleClass: 'guardian-dialog', + data: { + title: 'Preview', + action: 'Import', + policies: this.allPolicies, + policy: this.currentPolicy, + formula + } + }); + dialogRef.onClose.subscribe(async (result) => { + if (result && result.policyId) { + this.loading = true; + this.formulasService + .import(result.policyId, data) + .subscribe((newItem) => { + this.loadData(); + }, (e) => { + this.loading = false; + }); + } + }); + } + + public onExport(item: any) { + this.loading = true; + this.formulasService.export(item.id) + .subscribe((fileBuffer) => { + const downloadLink = document.createElement('a'); + downloadLink.href = window.URL.createObjectURL( + new Blob([new Uint8Array(fileBuffer)], { + type: 'application/guardian-formula' + }) + ); + downloadLink.setAttribute('download', `${item.name}_${Date.now()}.formula`); + document.body.appendChild(downloadLink); + downloadLink.click(); + downloadLink.remove(); + setTimeout(() => { + this.loading = false; + }, 500); + }, (error) => { + this.loading = false; + }); + } + + public onDelete(item: any) { + if (item.status === EntityStatus.PUBLISHED) { + return; + } + const dialogRef = this.dialogService.open(CustomConfirmDialogComponent, { + showHeader: false, + width: '640px', + styleClass: 'guardian-dialog', + data: { + header: 'Delete formula', + text: `Are you sure want to delete formula (${item.name})?`, + buttons: [{ + name: 'Close', + class: 'secondary' + }, { + name: 'Delete', + class: 'delete' + }] + }, + }); + dialogRef.onClose.subscribe((result: string) => { + if (result === 'Delete') { + this.loading = true; + this.formulasService + .deleteFormula(item.id) + .subscribe((result) => { + this.loadData(); + }, (e) => { + this.loading = false; + }); + } + }); + } + + public onChangeStatus($event: string, row: any): void { + this.publish(row) + } + + private publish(row: any) { + const dialogRef = this.dialogService.open(CustomConfirmDialogComponent, { + showHeader: false, + width: '640px', + styleClass: 'guardian-dialog', + data: { + header: 'Publish Formula', + text: `Are you sure want to publish formula (${row.name})?`, + buttons: [{ + name: 'Close', + class: 'secondary' + }, { + name: 'Publish', + class: 'primary' + }] + }, + }); + dialogRef.onClose.subscribe((result: string) => { + if (result === 'Publish') { + this.loading = true; + this.formulasService + .publish(row) + .subscribe((response) => { + this.loadData(); + }, (e) => { + this.loading = false; + }); + } + }); + + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/formulas/models/formula-tree.ts b/frontend/src/app/modules/formulas/models/formula-tree.ts new file mode 100644 index 0000000000..afb181a1f3 --- /dev/null +++ b/frontend/src/app/modules/formulas/models/formula-tree.ts @@ -0,0 +1,636 @@ +import { IFormulaItem, IFormula, IFormulaLink, FormulaItemType, Schema, SchemaField, IVCDocument, ISchema, IVPDocument, IVC, IVP, ICredentialSubject, IFormulaFile } from "@guardian/interfaces"; + +export interface Link { + schema: string; + path: string; + item: FormulaItem; +} + +export class FormulaFiles { + public readonly name: string; + public readonly type: string; + public readonly url: string; + + constructor(config: IFormulaFile) { + this.name = config.name || ''; + this.type = config.type || ''; + this.url = config.url || ''; + } +} + +export class DocumentItem { + private _type: Set; + private _map: Map; + + constructor(document: IVCDocument | IVPDocument) { + this._type = new Set(); + this._map = new Map(); + this.parsDoc(document); + } + + private parsDoc(document: IVCDocument | IVPDocument) { + try { + if (!document || !document.document) { + return; + } + const json = document.document; + if ((json as IVC).credentialSubject) { + this.parsVC(json as IVC); + } else if ((json as IVP).verifiableCredential) { + this.parsVP(json as IVP); + } else { + return; + } + } catch (error) { + console.error(error); + } + } + + private addValue(key: string, value: any, prev?: string) { + if (value) { + let path: string; + if (prev) { + if (key) { + path = `${prev}.${key}`; + } else { + path = `${prev}`; + } + } else { + if (key) { + path = `${key}`; + } else { + path = ''; + } + } + switch (typeof value) { + case 'boolean': + case 'number': + case 'string': { + const old = this._map.get(path); + if (old) { + if (Array.isArray(old)) { + old.push(value); + this._map.set(path, old); + } else { + this._map.set(path, [old, value]); + } + } else { + this._map.set(path, value); + } + break; + } + case 'object': { + if (Array.isArray(value)) { + for (const e of value) { + this.addValue('', e, path); + } + } else { + this.parsFields(value, path); + } + break; + } + default: { + return; + } + } + } + } + + private parsFields(json: any, prev?: string) { + if (json) { + for (const [key, value] of Object.entries(json)) { + this.addValue(key, value, prev); + } + } + } + + private parsCredentialSubject(cs: any) { + if (cs) { + this._type.add(cs.type); + this.parsFields(cs); + } + } + + private parsVC(vc: IVC) { + if (vc && vc.credentialSubject) { + if (Array.isArray(vc.credentialSubject)) { + for (const cs of vc.credentialSubject) { + this.parsCredentialSubject(cs); + } + } else { + this.parsCredentialSubject(vc.credentialSubject); + } + } + } + + private parsVP(vp: IVP) { + if (vp && vp.verifiableCredential) { + if (Array.isArray(vp.verifiableCredential)) { + for (const vc of vp.verifiableCredential) { + this.parsVC(vc); + } + } else { + this.parsVC(vp.verifiableCredential); + } + } + } + + public has(type?: string): boolean { + if (type) { + return this._type.has(type); + } + return false; + } + + public get(path?: string): any { + if (path) { + return this._map.get(path); + } + return null; + } +} + +export class SchemaItem { + public readonly type = 'schema'; + + private _value: any; + private _schema: Schema; + private _field: SchemaField; + private _type: string | undefined; + private _path: string | undefined; + + constructor(schema: Schema, field: SchemaField) { + this._schema = schema; + this._field = field; + this._type = schema.type; + this._path = field.path; + } + + public get name() { + return this._schema?.name; + } + + public get description() { + return this._schema?.description; + } + + public get field() { + return this._field?.description; + } + + public get value() { + return this._value; + } + + public setDocuments(documents: DocumentItem[]) { + for (const doc of documents) { + if (doc.has(this._type)) { + this._value = doc.get(this._path); + if (Array.isArray(this._value)) { + this._value = `[${this._value.join(',')}]`; + } + } + } + } +} + +export class FormulaItem { + public readonly uuid: string; + public readonly name: string; + public readonly description: string; + public readonly type: FormulaItemType; + + private _value: any; + private _link: IFormulaLink | null; + private _relationships: string[] | null; + + private _schemaLink: { schema: string, path: string } | null; + private _formulaLink: { formula: string, variable: string } | null; + private _parent: FormulaTree; + + private _relationshipItems: FormulaItem[]; + private _parentItems: FormulaItem[]; + private _linkEntity: FormulaTree | Schema | null; + private _linkItem: FormulaItem | SchemaItem | null; + + constructor(config: IFormulaItem) { + this.uuid = config.uuid || ''; + this.name = config.name || ''; + this.description = config.description || ''; + this.type = config.type || ''; + + this._value = config.value || ''; + this._link = config.link || null; + this._relationships = config.relationships || null; + + this._schemaLink = null; + this._formulaLink = null; + if (this._link) { + if (this._link.type === 'schema') { + this._schemaLink = { + schema: this._link.entityId, + path: this._link.item + } + } + if (this._link.type === 'formula') { + this._formulaLink = { + formula: this._link.entityId, + variable: this._link.item + } + } + } + + this._relationshipItems = []; + this._parentItems = []; + this._linkItem = null; + } + + public get value() { + return this._value; + } + + public get hasLink() { + return !!this._link; + } + + public get schemaLink() { + return this._schemaLink; + } + + public get formulaLink() { + return this._formulaLink; + } + + public get linkEntity() { + return this._linkEntity; + } + + public get linkItem() { + return this._linkItem; + } + + public get linkEntityName() { + if (this._formulaLink && this._linkEntity) { + return this._linkEntity.name; + } + if (this._schemaLink && this._linkEntity) { + return this._linkEntity.name; + } + return null; + } + + public get linkItemName() { + if (this._formulaLink && this._linkItem) { + return (this._linkItem as FormulaItem).name; + } + if (this._schemaLink && this._linkItem) { + return (this._linkItem as SchemaItem).field; + } + return null; + } + + public get relationshipItems() { + return this._relationshipItems; + } + + public setParent(parent: FormulaTree) { + this._parent = parent; + } + + public setRelationship(items: FormulaItem[]) { + if (Array.isArray(this._relationships)) { + this._relationshipItems = items.filter((e) => this._relationships?.includes(e.uuid)); + } else { + this._relationshipItems = []; + } + + this._parentItems = []; + for (const item of items) { + if (Array.isArray(item._relationships) && item._relationships.includes(this.uuid)) { + this._parentItems.push(item); + } + } + } + + public setFormulas(formulas: FormulaTree[]) { + if (this._formulaLink) { + this._linkEntity = formulas.find((e) => e.uuid === this._formulaLink?.formula) || null; + if (this._linkEntity) { + this._linkItem = this._linkEntity.get(this._formulaLink.variable); + } + } + } + + public setSchemas(schemas: Schema[]) { + if (this._schemaLink) { + const schema = schemas.find((e) => e.iri === this._schemaLink?.schema) || null; + this._linkEntity = schema; + if (schema) { + const field = schema.getField(this._schemaLink.path); + if (field) { + this._linkItem = new SchemaItem(schema, field); + } + } + } + } + + public setDocuments(documents: DocumentItem[]) { + if (this._schemaLink && this._linkItem) { + (this._linkItem as SchemaItem).setDocuments(documents); + } + } + + public createNav(list: Set): any { + const item: any = { + view: 'component', + type: this.type, + name: this.name, + data: this, + children: [] + } + if (this._formulaLink) { + item.children.push({ + view: 'link', + type: this._linkItem?.type, + entity: this._linkEntity?.name, + name: this._linkItem?.name, + data: this._linkItem, + children: [] + }); + } + if (this._schemaLink) { + item.children.push({ + view: 'link', + type: 'field', + entity: this._linkEntity?.name, + name: (this._linkItem as SchemaItem)?.field, + data: this._linkItem, + children: [] + }); + } + + for (const ref of this._relationshipItems) { + if (!list.has(ref)) { + const newList = new Set(list); + newList.add(ref); + const nav = ref.createNav(newList); + item.children.push(nav); + } + } + return item; + } + + public getFiles(): FormulaFiles[] { + return this._parent?.getFiles() || []; + } +} + +export class FormulaTree { + public readonly uuid: string; + public readonly name: string; + public readonly description: string; + + private _links: Map>; + private _items: FormulaItem[]; + private _files: FormulaFiles[]; + + constructor(formula: IFormula) { + this.uuid = formula.uuid || ''; + this.name = formula.name || ''; + this.description = formula.description || ''; + + this._links = new Map>(); + this.parse(formula?.config?.formulas); + this.parseFiles(formula?.config?.files); + } + + private parse(items?: IFormulaItem[]) { + if (!items) { + return; + } + + this._items = []; + for (const config of items) { + const item = new FormulaItem(config); + item.setParent(this); + const link = item.schemaLink; + if (link) { + const map = this._links.get(link.schema) || new Map(); + const array = map.get(link.path) || []; + array.push(item); + map.set(link.path, array); + this._links.set(link.schema, map); + } + this._items.push(item); + } + + for (const item of this._items) { + item.setRelationship(this._items); + } + } + + private parseFiles(files?: IFormulaFile[]) { + if (!files) { + return; + } + + this._files = []; + for (const config of files) { + const file = new FormulaFiles(config); + this._files.push(file); + } + } + + public setFormulas(formulas: FormulaTree[]) { + for (const item of this._items) { + item.setFormulas(formulas); + } + } + + public setSchemas(schemas: Schema[]) { + for (const item of this._items) { + item.setSchemas(schemas); + } + } + + public setDocuments(documents: DocumentItem[]) { + for (const item of this._items) { + item.setDocuments(documents); + } + } + + public hasLink(schema: string, path: string): boolean { + return this._links.get(schema)?.has(path) || false; + } + + public getLink(schema: string, path: string): FormulaItem[] { + return this._links.get(schema)?.get(path) || []; + } + + public get(variable: string): FormulaItem | null { + for (const item of this._items) { + if (item.name === variable) { + return item; + } + } + return null; + } + + public merge(links: Map>) { + for (const [schema, map] of this._links.entries()) { + const fullMap = links.get(schema) || new Map(); + for (const [path, array] of map.entries()) { + const fullArray = fullMap.get(path) || []; + for (const item of array) { + fullArray.push(item); + } + fullMap.set(path, fullArray); + } + links.set(schema, fullMap); + } + } + + public getFiles(): FormulaFiles[] { + return this._files; + } +} + +export class FormulasTree { + public items: FormulaTree[]; + + private _links: Map>; + + constructor() { + this._links = new Map>(); + } + + public setFormulas(formulas: IFormula[]) { + if (Array.isArray(formulas)) { + this.items = formulas.map((f) => new FormulaTree(f)); + } else { + this.items = []; + } + for (const item of this.items) { + item.setFormulas(this.items); + } + } + + public setSchemas(schemas: Schema[]) { + for (const item of this.items) { + item.setSchemas(schemas); + } + } + + public setDocuments(documents: (IVCDocument | IVPDocument) | (IVCDocument | IVPDocument)[]) { + const _documents: DocumentItem[] = []; + if (Array.isArray(documents)) { + for (const doc of documents) { + const _doc = new DocumentItem(doc); + _documents.push(_doc); + } + } else if (documents) { + const _doc = new DocumentItem(documents); + _documents.push(_doc); + } + + for (const item of this.items) { + item.setDocuments(_documents); + } + } + + public update() { + this._links.clear(); + for (const item of this.items) { + item.merge(this._links); + } + } + + public has(schema: string, path: string): boolean { + return this._links.get(schema)?.has(path) || false; + } + + public get(schema: string, path: string): FormulaItem[] { + return this._links.get(schema)?.get(path) || []; + } + + public getFiles(items: FormulaItem[]): FormulaFiles[] { + const result = new Set(); + for (const item of items) { + for (const files of item.getFiles()) { + result.add(files); + } + } + return Array.from(result); + } + + public getFields(schema?: string) { + const result: any = {}; + if (schema) { + const map = this._links.get(schema); + if (map) { + for (const path of map.keys()) { + result[`${schema}/${path}`] = { + tree: this, + schema, + path + }; + } + } + } + return result; + } + + public static from(response?: { + formulas: IFormula[], + schemas: ISchema[], + document: IVCDocument, + relationships: IVCDocument[] + }) { + if (!response) { + return null; + } + + const documents = []; + if (response.document) { + documents.push(response.document); + } + if (response.relationships) { + for (const document of response.relationships) { + documents.push(document); + } + } + + const schemas = []; + if (response.schemas) { + for (const s of response.schemas) { + const schema = Schema.from(s); + if (schema) { + schemas.push(schema); + } + } + } + + const tree = new FormulasTree(); + tree.setFormulas(response.formulas); + tree.setSchemas(schemas); + tree.setDocuments(documents); + tree.update(); + + return tree; + } + + public static createNav(items: FormulaItem[]): any { + const root: any = { + view: 'root', + type: 'root', + children: [] + } + for (const item of items) { + const list = new Set(); + list.add(item); + const nav = item.createNav(list); + root.children.push(nav); + } + return root; + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/formulas/models/formulas.ts b/frontend/src/app/modules/formulas/models/formulas.ts new file mode 100644 index 0000000000..2b7ea24605 --- /dev/null +++ b/frontend/src/app/modules/formulas/models/formulas.ts @@ -0,0 +1,137 @@ +import { moveItemInArray } from '@angular/cdk/drag-drop'; +import { IFormulaItem, FormulaItemType, GenerateUUIDv4, IFormulaConfig, IFormulaFile } from '@guardian/interfaces'; + +export class Formulas { + private items: IFormulaItem[]; + private filterMap: Map; + public files: IFormulaFile[]; + public data: IFormulaItem[]; + + constructor() { + this.items = []; + this.files = []; + this.data = []; + this.filterMap = new Map(); + this.filterMap.set(FormulaItemType.Constant, true); + this.filterMap.set(FormulaItemType.Variable, true); + this.filterMap.set(FormulaItemType.Formula, true); + this.filterMap.set(FormulaItemType.Text, true); + } + + public get all() { + return this.items; + } + + private create(type: FormulaItemType): IFormulaItem { + const item: IFormulaItem = { + uuid: GenerateUUIDv4(), + name: '', + description: '', + type: type, + }; + if (type === FormulaItemType.Constant) { + item.value = ''; + return item; + } else if (type === FormulaItemType.Variable) { + item.value = ''; + item.link = null; + return item; + } else if (type === FormulaItemType.Formula) { + item.value = ''; + item.link = null; + item.relationships = []; + return item; + } else if (type === FormulaItemType.Text) { + item.value = ''; + item.link = null; + item.relationships = []; + return item; + } else { + return item; + } + } + + public add(type: FormulaItemType): void { + const item = this.create(type); + this.items.push(item); + this.update(); + } + + public delete(item: IFormulaItem): void { + this.items = this.items.filter((e) => e.uuid !== item?.uuid); + this.update(); + } + + public addFile(file: IFormulaFile): void { + if (file) { + this.files.push(file); + } + } + + public deleteFile(file: IFormulaFile): void { + this.files = this.files.filter((e) => e !== file); + } + + public setFilters(filter: any): void { + this.filterMap.set(FormulaItemType.Constant, filter.constant); + this.filterMap.set(FormulaItemType.Variable, filter.variable); + this.filterMap.set(FormulaItemType.Formula, filter.formula); + this.filterMap.set(FormulaItemType.Text, filter.text); + this.update(); + } + + private update(): void { + this.data = this.items.filter((e) => this.filterMap.get(e.type)); + } + + public fromData(config: IFormulaConfig) { + const items: IFormulaItem[] = config?.formulas || []; + const files: IFormulaFile[] = config?.files || []; + this.items = items.map((e) => this._fromItemJson(e)); + this.files = files.map((e) => this._fromFileJson(e)); + this.update(); + } + + public getJson(): IFormulaConfig { + return { + formulas: this.items.map((e) => this._toItemJson(e)), + files: this.files.map((e) => this._toFileJson(e)), + }; + } + + private _fromItemJson(item: IFormulaItem): IFormulaItem { + return item; + } + + private _toItemJson(item: IFormulaItem): IFormulaItem { + return item; + } + + private _fromFileJson(item: IFormulaFile): IFormulaFile { + return item; + } + + private _toFileJson(item: IFormulaFile): IFormulaFile { + return item; + } + + public getItem(uuid: string): IFormulaItem | null { + for (const item of this.items) { + if (item.uuid === uuid) { + return item; + } + } + return null; + } + + public reorder(previousIndex: number, currentIndex: number) { + if (previousIndex !== currentIndex) { + const prevItem = this.data[previousIndex]; + const currentItem = this.data[currentIndex]; + const originalPreviousIndex = this.items.findIndex((e) => e === prevItem); + const originalCurrentIndex = this.items.findIndex((e) => e === currentItem); + moveItemInArray(this.items, originalPreviousIndex, originalCurrentIndex); + this.update(); + } + } +} diff --git a/frontend/src/app/modules/policy-engine/dialogs/multi-policy-dialog/multi-policy-dialog.component.html b/frontend/src/app/modules/policy-engine/dialogs/multi-policy-dialog/multi-policy-dialog.component.html index 531b6744b8..b27f6a4560 100644 --- a/frontend/src/app/modules/policy-engine/dialogs/multi-policy-dialog/multi-policy-dialog.component.html +++ b/frontend/src/app/modules/policy-engine/dialogs/multi-policy-dialog/multi-policy-dialog.component.html @@ -1,141 +1,137 @@ -
-
-
- Multi Policy -
+
+
+
Multi Policy
-
-
-
-
-
+
+ +
+
+ +
+
+
+
+
-
+
-
-
-
-
- - - +
+
+
+
+ + + +
+
+
+ Type: + Main Policy
-
-
- Type: - Main Policy -
-
- Main Policy: - {{ mainPolicyTopicId1 }} -
-
- Topic for synchronization: - {{ synchronizationTopicId1 }} -
-
- +
+ Main Policy: + {{ mainPolicyTopicId1 }} +
+
+ Topic for synchronization: + {{ synchronizationTopicId1 }} +
+
+
+
+
-
-
- - - +
+
+ + + +
+
+
+ Type: + Sub Policy
-
-
- Type: - Sub Policy -
-
- Main Policy: - {{ mainPolicyTopicId2 }} -
-
- Topic for synchronization: - {{ synchronizationTopicId2 }} -
-
- +
+ Main Policy: + {{ mainPolicyTopicId2 }} +
+
+ Topic for synchronization: + {{ synchronizationTopicId2 }} +
+
+
+
-
-
-
- Policy is already bound +
+ +
+
+ Policy is already bound +
+
+
+ Type: + {{ policyType }} Policy
-
-
- Type: - {{ policyType }} Policy -
-
- Main Policy: - {{ mainPolicyTopicId1 }} -
-
- Topic for synchronization: - {{ synchronizationTopicId1 }} -
-
- Current Policy: - {{ instanceTopicId }} -
-
- - -
+
+ Main Policy: + {{ mainPolicyTopicId1 }} +
+
+ Topic for synchronization: + {{ synchronizationTopicId1 }} +
+
+ Current Policy: + {{ instanceTopicId }} +
+
+ +
+ + +
-
-
- +
+ + \ No newline at end of file diff --git a/frontend/src/app/modules/policy-engine/dialogs/multi-policy-dialog/multi-policy-dialog.component.scss b/frontend/src/app/modules/policy-engine/dialogs/multi-policy-dialog/multi-policy-dialog.component.scss index fe10a7929d..e00d31d9f1 100644 --- a/frontend/src/app/modules/policy-engine/dialogs/multi-policy-dialog/multi-policy-dialog.component.scss +++ b/frontend/src/app/modules/policy-engine/dialogs/multi-policy-dialog/multi-policy-dialog.component.scss @@ -1,56 +1,70 @@ -.container { +.context { + position: relative; + overflow-y: auto; + padding: 14px 0 20px 0; display: flex; - justify-content: space-between; - padding-right: 14px; + flex-direction: row; + height: 100%; + font-family: Inter, serif; + font-style: normal; } -.container button { - margin-top: 5px; - margin-left: 10px; - min-width: 280px; +.dialog-header { + .header-item { + padding: 6px 12px; + margin: 0px 16px; + border-radius: 6px; + font-size: var(--guardian-primary-font-size); + color: var(--guardian-dry-run-color); + background: var(--guardian-dry-run-background); + user-select: none; + cursor: default; + } } -.schema-field { - flex: 1; +form { + width: 100%; + + .guardian-input-container { + input[readonly] { + border: none; + padding-left: 0; + } + } } -:host .content { - max-height: 400px; +.guardian-input-container { + margin-bottom: 24px; } -.loading { - background: #fff; - position: absolute; - z-index: 99; - left: 0; - bottom: 0; - right: 0; - top: 0; - display: flex; - align-items: center; - justify-items: center; - justify-content: center; - align-content: center; +.dialog-body { + height: 500px; } -.g-dialog-title-btn i, -.g-dialog-title-btn svg,{ - position: absolute; - font-size: 32px; - top: 7px; - left: 7px; - color: #646464; +.action-buttons { + display: flex; + justify-content: flex-end; + user-select: none; + + &>div { + margin-left: 15px; + margin-right: 0px; + } } -.context { - position: relative; - min-height: 275px; +.close-icon-color { + fill: #848FA9; + color: #848FA9; } .group-name { padding: 0px 0px 15px 0px; font-weight: 500; font-size: 16px; + + label { + margin-left: 8px; + } } .group-value { @@ -63,84 +77,12 @@ display: none; } -@media (max-width: 810px) { - .g-dialog-cancel{order: 1;} - .g-dialog-title{order: 2;} - .g-dialog-body{order: 3;} - .g-dialog-actions{order: 4;} - .g-dialog-header { - box-shadow: none; - } - - .mat-radio-container { - background-color: #2C78F6 !important; - border-color: #2C78F6 !important; - } - .context { - width: 90%; - margin: 0 auto; - } - - .group-value { - padding-left: 0; - width: 90%; - margin: 0 auto; - } - - #title { - width: 192px; - height: 56px; - - /* 24 px semibold */ - font-family: 'Roboto'; - font-style: normal; - font-weight: 600; - font-size: 24px; - line-height: 28px; - - /* Grey black */ - color: #222222; - - /* Inside auto layout */ - flex: none; - order: 0; - flex-grow: 1; - } - - .g-dialog-body { - /* Auto layout */ - display: flex; - flex-direction: column; - align-items: flex-start; - padding: 0px; - gap: 24px; - margin: 0 auto; - - position: relative; - - width: 100%; - - /* Inside auto layout */ - flex: none; - order: 1; - flex-grow: 0; - } +@media (max-width: 810px) { - .g-dialog-actions { - display: flex; - justify-content: center; - } - .g-dialog-actions-btn { - position: absolute; - bottom: 0; - right: 0; - width: 90%; - margin: 20px 5% !important; - } .table-container { display: grid; @@ -151,150 +93,16 @@ .table-container span:nth-child(odd) { /* targets the first in each
*/ - grid-column: 1; /* places the element in the first column */ + grid-column: 1; + /* places the element in the first column */ color: #BABABA; } .table-container span:nth-child(even) { /* targets the second in each
*/ - grid-column: 2; /* places the element in the second column */ + grid-column: 2; + /* places the element in the second column */ color: #222222; margin: auto 0; } -} - -form { - display: flex; - flex-direction: column; - width: 452px; - overflow: visible; - padding-bottom: 25px; -} - -.context { - position: relative; - overflow-y: auto; -} - -.loading { - background: #fff; - position: absolute; - z-index: 99; - left: 0; - bottom: 0; - right: 0; - top: 0; - display: flex; - align-items: center; - justify-items: center; - justify-content: center; - align-content: center; -} - -.schema-header { - height: 44px; - font-size: 34px; - display: block; - padding: 12px; - box-sizing: border-box; - text-align: center; - color: #000; -} - -.delimiter { - padding: 50px 50px; - width: 100%; - height: 100px; - box-sizing: border-box; - position: relative; -} - -.delimiter::after { - content: ''; - position: absolute; - height: 2px; - top: 50px; - left: 0px; - right: 0px; - border-top: 1px solid #c8c8c8; -} - -.field-value { - color: #000; - font-size: 20px; -} - -.field-name { - margin-bottom: 12px; -} - -.field { - margin-top: 5px; - margin-bottom: 28px; -} - -.field-last { - margin-bottom: 0px; -} - -.version-container { - display: flex; - gap: 15px; -} - -.version-container__new-versions { - margin-top: 3px; -} - -.g-dialog-header { - padding: 0; -} - -.g-dialog-title { - height: auto !important; - padding: 0 !important; - margin-bottom: 24px !important; - color: #495057 !important; - font-weight: 600 !important; - font-size: 1.25rem !important; - font-family: Poppins, sans-serif !important; - font-style: normal !important; -} - -.form-input-container { - display: flex; - flex-direction: column; - margin-bottom: 24px; -} - -.form-label { - margin-bottom: 6px; - color: var(--color-grey-black-1, #181818); - font-family: Inter; - font-size: 12px; - font-style: normal; - font-weight: 500; - line-height: 14px; -} - -:host ::ng-deep { - .mat-dialog-title { - margin-bottom: 32px; - color: #000; - font-family: Poppins; - font-size: 24px; - font-style: normal; - font-weight: 600; - line-height: normal; - } - - .mat-form-field { - width: 100%; - } - - .mat-dialog-actions { - display: flex; - justify-content: flex-end; - gap: 16px; - } -} +} \ No newline at end of file diff --git a/frontend/src/app/modules/policy-engine/dialogs/multi-policy-dialog/multi-policy-dialog.component.ts b/frontend/src/app/modules/policy-engine/dialogs/multi-policy-dialog/multi-policy-dialog.component.ts index 45da20faf3..d490b18ab1 100644 --- a/frontend/src/app/modules/policy-engine/dialogs/multi-policy-dialog/multi-policy-dialog.component.ts +++ b/frontend/src/app/modules/policy-engine/dialogs/multi-policy-dialog/multi-policy-dialog.component.ts @@ -1,6 +1,6 @@ -import {AfterContentInit, Component, Inject, OnInit} from '@angular/core'; -import {PolicyEngineService} from 'src/app/services/policy-engine.service'; -import {DynamicDialogConfig, DynamicDialogRef} from 'primeng/dynamicdialog'; +import { AfterContentInit, Component, Inject, OnInit } from '@angular/core'; +import { PolicyEngineService } from 'src/app/services/policy-engine.service'; +import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; /** * Export schema dialog. @@ -80,7 +80,7 @@ export class MultiPolicyDialogComponent implements OnInit, AfterContentInit { }, 100); } - onOk(): void { + onClose(): void { this.dialogRef.close(); } diff --git a/frontend/src/app/modules/policy-engine/dialogs/new-policy-dialog/new-policy-dialog.component.html b/frontend/src/app/modules/policy-engine/dialogs/new-policy-dialog/new-policy-dialog.component.html index c108e3365a..1e1afd5627 100644 --- a/frontend/src/app/modules/policy-engine/dialogs/new-policy-dialog/new-policy-dialog.component.html +++ b/frontend/src/app/modules/policy-engine/dialogs/new-policy-dialog/new-policy-dialog.component.html @@ -62,7 +62,7 @@
-
+
+
+ + @if (item.type === 'constant') { +
+ + +
+ } + + @if (item.type === 'variable') { +
+ + +
+ } + + @if (item.type === 'formula') { +
+ + +
+
+ + +
+
+ +
+ @for (relationship of item.relationships; track relationship) { +
{{getRelationshipName(relationship)}}
+ } +
+
+ } + @if (item.type === 'text') { +
+ + +
+
+ + +
+
+ +
+ @for (relationship of item.relationships; track relationship) { +
{{getRelationshipName(relationship)}}
+ } +
+
+ } +
+
+
+ } +
+ + + +
+ @for (file of config.files; track file) { +
+
+ + +
+
+ + +
+
+ } +
+
+ +
+} \ No newline at end of file diff --git a/indexer-frontend/src/app/components/formula-view/formula-view.component.scss b/indexer-frontend/src/app/components/formula-view/formula-view.component.scss new file mode 100644 index 0000000000..6048dede42 --- /dev/null +++ b/indexer-frontend/src/app/components/formula-view/formula-view.component.scss @@ -0,0 +1,272 @@ +.formula-container { + position: relative; + z-index: 0; + width: 100%; + height: 100%; + user-select: none; + position: relative; + padding: 24px; + overflow: auto; + background: #fff; + border-radius: 6px; + + .item-filters { + display: flex; + flex-direction: row; + margin-bottom: 24px; + + .item-filters-header { + font-family: var(--font-family-inter); + font-size: 14px; + font-weight: 400; + line-height: 16px; + color: #848FA9; + margin-right: 24px; + height: 24px; + padding: 6px 0px; + } + + .item-filter { + margin-right: 8px; + height: 28px; + min-width: 108px; + } + + .constant-filter { + --checkbox-button-on-first-color: var(--formula-constant-color, #4169E2); + --checkbox-button-on-second-color: var(--formula-constant-background, #e1e7fa); + } + + .variable-filter { + --checkbox-button-on-first-color: var(--formula-variable-color, #DA9B22); + --checkbox-button-on-second-color: var(--formula-variable-background, #FFF6E3); + } + + .formula-filter { + --checkbox-button-on-first-color: var(--formula-function-color, #E50299); + --checkbox-button-on-second-color: var(--formula-function-background, #fbd9ef); + } + + .text-filter { + --checkbox-button-on-first-color: var(--formula-text-color, #19BE47); + --checkbox-button-on-second-color: var(--formula-text-background, #d7f5e2); + } + } + + .formula-item { + box-shadow: 0px 4px 8px 0px var(--guardian-shadow, #00000014); + background: var(--guardian-background, #FFFFFF); + border-radius: 8px; + margin-bottom: 16px; + display: flex; + flex-direction: row; + overflow: hidden; + + .formula-item-icon { + width: 32px; + min-width: 32px; + width: 32px; + min-width: 32px; + display: flex; + justify-content: center; + align-items: center; + cursor: move; + + * { + pointer-events: none; + } + } + + .formula-item-container { + width: 100%; + padding: 16px; + + .formula-item-header { + height: 24px; + width: 100%; + margin-bottom: 16px; + position: relative; + display: flex; + + &__icon { + margin-right: 8px; + + svg { + fill: #000; + } + } + + &__name { + font-family: var(--font-family-inter); + font-size: 16px; + font-weight: 600; + height: 24px; + line-height: 24px; + } + + &__delete { + position: absolute; + top: 0px; + right: 0px; + width: 24px; + height: 24px; + + .guardian-icon-button { + width: 24px; + height: 24px; + } + } + } + + .formula-item-body {} + } + + &[type="constant"] { + border: 1px solid var(--formula-constant-color, #4169E2); + + .formula-item-icon { + background: var(--formula-constant-background, #e1e7fa); + border-right: 1px solid #E1E7EF; + } + } + + &[type="variable"] { + border: 1px solid var(--formula-variable-color, #DA9B22); + + .formula-item-icon { + background: var(--formula-variable-background, #FFF6E3); + border-right: 1px solid #E1E7EF; + } + } + + &[type="formula"] { + border: 1px solid var(--formula-function-color, #E50299); + + .formula-item-icon { + background: var(--formula-function-background, #fbd9ef); + border-right: 1px solid #E1E7EF; + } + } + + &[type="text"] { + border: 1px solid var(--formula-text-color, #19BE47); + + .formula-item-icon { + background: var(--formula-text-background, #d7f5e2); + border-right: 1px solid #E1E7EF; + } + } + } + + .formula-link { + height: 40px; + border-radius: 8px; + outline: none; + border: 1px solid var(--guardian-grey-color-2, #E1E7EF); + font-family: Inter; + font-size: 14px; + font-weight: 400; + color: #000; + padding: 10px 16px; + position: relative; + cursor: pointer; + + &:hover { + border: 1px solid var(--guardian-primary-color, #4169E2); + } + + .formula-link-remove { + position: absolute; + top: 4px; + right: 4px; + height: 30px; + width: 30px; + + button { + height: 30px; + width: 30px; + } + } + + .formula-link-value { + display: flex; + flex-direction: row; + + &__entity { + font-family: Inter; + font-size: 14px; + font-weight: 500; + line-height: 16px; + color: var(--guardian-entity-color, #DA9B22); + margin-right: 8px; + } + + &__item { + font-family: Inter; + font-size: 14px; + font-weight: 400; + line-height: 16px; + } + } + } + + .guardian-input-container { + margin-bottom: 16px; + } + + .guardian-textarea-container { + margin-bottom: 16px; + } + + .formula-relationships { + border-radius: 8px; + outline: none; + border: 1px solid var(--guardian-grey-color-2, #E1E7EF); + font-family: Inter; + font-size: 14px; + font-weight: 400; + color: #000; + padding: 8px 16px 0px 16px; + display: flex; + flex-direction: row; + flex-wrap: wrap; + min-height: 40px; + + .formula-relationship { + width: auto; + height: 30px; + padding: 7px 12px 7px 12px; + border-radius: 6px; + background: var(--guardian-grey-color, #EFF3F7); + color: var(--guardian-disabled-color, #848FA9); + font-family: Inter; + font-size: 14px; + font-weight: 400; + line-height: 16px; + text-align: left; + margin-right: 8px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + display: inline-flex; + margin-bottom: 8px; + cursor: pointer; + + &:hover { + filter: brightness(0.95); + } + } + } +} + +.files-container { + width: 100%; + + .file-item { + width: 100%; + border: 1px solid #E1E7EF; + border-radius: 8px; + padding: 24px; + margin-bottom: 24px; + } +} \ No newline at end of file diff --git a/indexer-frontend/src/app/components/formula-view/formula-view.component.ts b/indexer-frontend/src/app/components/formula-view/formula-view.component.ts new file mode 100644 index 0000000000..cf0ccd4d3e --- /dev/null +++ b/indexer-frontend/src/app/components/formula-view/formula-view.component.ts @@ -0,0 +1,137 @@ +import { Component, Input } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { CheckboxButton } from '@components/checkbox-button/checkbox-button.component'; +import { MathLiveComponent } from '@components/math-live/math-live.component'; +import { TranslocoModule } from '@jsverse/transloco'; +import { Formulas } from '../../models/formulas'; +import { Schema } from '@indexer/interfaces'; +import { TabViewModule } from 'primeng/tabview'; + +@Component({ + selector: 'app-formula-view', + standalone: true, + imports: [ + TranslocoModule, + RouterModule, + CheckboxButton, + MathLiveComponent, + TabViewModule + ], + templateUrl: './formula-view.component.html', + styleUrl: './formula-view.component.scss' +}) +export class FormulaViewComponent { + @Input() data: any; + + public readonly filters = { + constant: true, + variable: true, + formula: true, + text: true + } + + public config: Formulas = new Formulas(); + + private schemasMap: Map = new Map(); + private schemasFieldMap: Map = new Map(); + private formulasMap: Map = new Map(); + private formulasFieldMap: Map = new Map(); + private variableMap: Map = new Map(); + + public isDocument: boolean = false; + + ngOnChanges() { + const item = this.data.item; + const schemas = this.data.schemas; + const formulas = this.data.formulas; + + this.isDocument = false; + if (!(item && item.analytics && item.analytics.config)) { + return; + } + this.isDocument = true; + + if (item?.analytics?.config) { + this.config.fromData(item.analytics.config.config); + } + if (schemas) { + this.schemasMap.clear(); + this.schemasFieldMap.clear(); + for (const message of schemas) { + const schema = this.getSchemaDocument(message); + if (schema) { + this.schemasMap.set(String(schema.iri), String(schema.name)); + const fields = schema.getFields(); + for (const field of fields) { + this.schemasFieldMap.set(`${schema.iri}.${field.path}`, String(field.description)); + } + } + } + } + if (formulas) { + for (const message of formulas) { + const formula = this.getFormulaDocument(message); + if (formula) { + this.formulasMap.set(String(formula.uuid), String(formula.name)); + const fields = formula?.config?.formulas || []; + for (const field of fields) { + this.formulasFieldMap.set(`${formula.uuid}.${field.uuid}`, String(field.name)); + } + } + } + } + this.variableMap.clear(); + for (const v of this.config.all) { + this.variableMap.set(v.uuid, v.name); + } + } + + public getSchemaDocument(message: any) { + try { + const schema = new Schema(message.documents[0], ''); + return schema; + } catch (error) { + return null; + } + } + + public getFormulaDocument(message: any) { + try { + return message?.analytics?.config; + } catch (error) { + return null; + } + } + + public onFilter() { + this.config.setFilters(this.filters); + } + + public getEntityName(link: any): string { + if (link.type === 'schema') { + return this.schemasMap.get(link.entityId) || ''; + } + if (link.type === 'formula') { + return this.formulasMap.get(link.entityId) || ''; + } + return ''; + } + + public getFieldName(link: any): string { + if (link.type === 'schema') { + return this.schemasFieldMap.get(`${link.entityId}.${link.item}`) || ''; + } + if (link.type === 'formula') { + if (link.entityId === this.data?.uuid) { + return this.config.getItem(link.item)?.name || ''; + } else { + return this.formulasFieldMap.get(`${link.entityId}.${link.item}`) || ''; + } + } + return ''; + } + + public getRelationshipName(relationship: any) { + return this.variableMap.get(relationship); + } +} diff --git a/indexer-frontend/src/app/components/header/header.component.ts b/indexer-frontend/src/app/components/header/header.component.ts index 3ab801e237..f0b85f6e8e 100644 --- a/indexer-frontend/src/app/components/header/header.component.ts +++ b/indexer-frontend/src/app/components/header/header.component.ts @@ -93,6 +93,10 @@ export class HeaderComponent { label: 'header.labels', routerLink: '/labels', }, + { + label: 'header.formulas', + routerLink: '/formulas', + }, ]; public documentsMenu: MenuItem[] = [ diff --git a/indexer-frontend/src/app/components/math-live/keyboards/math-keyboard.ts b/indexer-frontend/src/app/components/math-live/keyboards/math-keyboard.ts new file mode 100644 index 0000000000..d3df58d23d --- /dev/null +++ b/indexer-frontend/src/app/components/math-live/keyboards/math-keyboard.ts @@ -0,0 +1,469 @@ +export const mathKeyboard = { + label: "math", + tooltip: "Math", + rows: [ + [ + { + latex: "+", + shift: "\\oplus", + variants: [ + { latex: "+" }, + { latex: "\\oplus" }, + ] + }, + { + latex: "-", + shift: "\\pm", + variants: [ + { latex: "-", }, + { latex: "\\pm" }, + { latex: "\\ominus" }, + ] + }, + { + latex: "\\times", + shift: "\\otimes", + variants: [ + { latex: "\\times" }, + { latex: "\\otimes" }, + { latex: "\\cdot" }, + ] + }, + { + latex: "\\frac{#@}{#?}", + shift: "\\%", + variants: [ + { latex: "\\frac{#@}{#?}" }, + { latex: "/" }, + { latex: "\\div" }, + { latex: "\\%" }, + { latex: "\\oslash" }, + ] + }, + { + latex: "=", + shift: "\\neq", + variants: [ + { latex: "=" }, + { latex: "\\neq" }, + { latex: "\\equiv" }, + { latex: "\\varpropto" }, + { latex: "\\thickapprox" }, + { latex: "\\lt" }, + { latex: "\\gt" }, + { latex: "\\le" }, + { latex: "\\ge" }, + ] + }, + { + latex: ".", + shift: ",", + variants: [ + { latex: "." }, + { latex: "," }, + { latex: ";" }, + { latex: "\\colon" }, + { latex: "\\Colon" }, + { latex: "?" }, + { latex: "\\cdotp" }, + { latex: "\\ldots" }, + { latex: "\\cdots" }, + { latex: "\\therefore" }, + { latex: "\\because" }, + { latex: "\\Colon:" }, + { latex: "\\vdots" }, + { latex: "\\ddots" }, + { latex: "\\ldotp" }, + ] + }, + { + latex: "(", + shift: "\\lbrack", + variants: [ + { latex: "(" }, + { latex: "\\lbrack" }, + { latex: "\\langle" }, + { latex: "\\lfloor" }, + { latex: "\\lceil" }, + { latex: "\\lbrace" }, + ] + }, + { + latex: ")", + shift: "\\rbrack", + variants: [ + { latex: ")" }, + { latex: "\\rbrack" }, + { latex: "\\rangle" }, + { latex: "\\rfloor" }, + { latex: "\\rceil" }, + { latex: "\\rbrace" }, + ] + }, + "\\sqrt{#0}", + "#0^2", + ], + [ + "#@^{#?}", + "#@_{#?}", + "|#0|", + "\\sqrt[#0]{#0}", + { + class: "small", + latex: "\\log_{#0}#0", + shift: "\\ln", + variants: [ + { class: "small", latex: "\\log_{#0}#0" }, + { class: "small", latex: "\\ln#0" }, + { class: "small", latex: "\\log_{10}#0" }, + ] + }, + { + latex: "\\exponentialE", + shift: "\\exp", + variants: [ + { class: "small", latex: "\\exponentialE" }, + { class: "small", latex: "\\exp\\left(#0\\right)" }, + { class: "small", latex: "\\times10^{#0}" }, + ] + }, + "\\lim_{#0}", + { + class: "small", + latex: "\\sum_{#0}^{#0}#0", + shift: "\\Sigma", + variants: [ + { class: "small", latex: "\\sum_{#0}^{#0}#0" }, + { class: "small", latex: "\\sum #0" }, + ] + }, + { + class: "small", + latex: "\\prod_{#0}^{#0}#0", + shift: "\\Pi", + variants: [ + { class: "small", latex: "\\prod_{#0}^{#0}#0", }, + { class: "small", latex: "\\prod#0", }, + ] + }, + { + class: "small", + latex: "\\int_{#0}^{#0}#0", + shift: "\\smallint", + variants: [ + { class: "small", latex: "\\int_{#0}^{#0}#0" }, + { class: "small", latex: "\\int#0" }, + { class: "small", latex: "\\iint #0" }, + { class: "small", latex: "\\iiint#0" }, + { class: "small", latex: "\\oint#0" }, + { class: "small", latex: "\\intclockwise#0" }, + { class: "small", latex: "\\varointclockwise#0" }, + { class: "small", latex: "\\ointctrclockwise#0" }, + { class: "small", latex: "\\intctrclockwise#0" }, + { class: "small", latex: "\\oiint#0" }, + { class: "small", latex: "\\oiiint#0" }, + ] + }, + ], + [ + { + latex: "\\overrightarrow{#@}", + shift: "\\overleftarrow{#@}", + variants: [ + { latex: "\\overrightarrow{#@}" }, + { latex: "\\overleftarrow{#@}" }, + { latex: "\\underleftarrow{#@}" }, + { latex: "\\underrightarrow{#@}" }, + { latex: "\\overleftrightarrow{#@}" }, + { latex: "\\underleftrightarrow{#@}" }, + ] + }, + { + latex: "\\overline{#@}", + shift: "\\underline{#@}", + variants: [ + { latex: "\\overline{#@}" }, + { latex: "\\underline{#@}" }, + { latex: "\\tilde{#@}" }, + { latex: "\\grave{#@}" }, + { latex: "\\dot{#@}" }, + { latex: "\\ddot{#@}" }, + { latex: "\\mathring{#@}" }, + { latex: "\\breve{#@}" }, + { latex: "\\acute{#@}" }, + { latex: "\\bar{#@}" }, + { latex: "\\vec{#@}" }, + { latex: "\\hat{#@}" }, + { latex: "\\check{#@}" }, + { latex: "\\undergroup{#@}" }, + { latex: "\\overgroup{#@}" }, + { latex: "\\underbrace{#@}" }, + { latex: "\\overbrace{#@}" }, + { latex: "\\overlinesegment{#@}" }, + { latex: "\\underlinesegment{#@}" }, + ] + }, + { + latex: "#@^{\\prime}", + shift: "#@^{\\doubleprime}", + variants: [ + { latex: "#@^{\\prime}" }, + { latex: "#@^{\\doubleprime}" }, + { latex: "#@\\degree" }, + ] + }, + { + class: "small", + latex: "\\mathrm{abs}\\left(#0\\right)", + }, + { + latex: "\\cup", + shift: "\\cap", + variants: [ + { latex: "\\cup" }, + { latex: "\\cap" }, + { latex: "\\subset" }, + { latex: "\\subseteq" }, + { latex: "\\subsetneq" }, + { latex: "\\varsubsetneq" }, + { latex: "\\subsetneqq" }, + { latex: "\\nsubset" }, + { latex: "\\nsubseteq" }, + { latex: "\\supset" }, + { latex: "\\supseteq" }, + { latex: "\\supsetneq" }, + { latex: "\\supsetneqq" }, + { latex: "\\nsupset" }, + { latex: "\\nsupseteq" }, + ] + }, + + { + latex: "\\exists", + shift: "\\forall", + variants: [ + { latex: "\\exists" }, + { latex: "\\nexists" }, + { latex: "\\forall" }, + { latex: "\\lnot" }, + { latex: "\\land" }, + { latex: "\\lor" }, + { latex: "\\oplus" }, + { latex: "\\downarrow" }, + { latex: "\\uparrow" }, + { latex: "\\curlywedge" }, + { latex: "\\bar{\\curlywedge}" }, + { latex: "\\in" }, + { latex: "\\owns" }, + { latex: "\\notin" }, + { latex: "\\ni" }, + { latex: "\\not\\owns" }, + ] + }, + { + latex: "\\rightarrow", + shift: "\\larr", + variants: [ + { latex: "\\rightarrow" }, + { latex: "\\implies" }, + { latex: "\\to" }, + { latex: "\\dashv" }, + { latex: "\\roundimplies" }, + { latex: "\\larr" }, + { latex: "\\impliedby" }, + { latex: "\\gets" }, + { latex: "\\lArr" }, + { latex: "\\vdash" }, + { latex: "\\models" }, + { latex: "\\in" }, + { latex: "\\lrArr" }, + { latex: "\\iff" }, + { latex: "\\leftrightarrow" }, + { latex: "\\leftrightarrows" }, + { latex: "\\Leftrightarrow" }, + { latex: "^{\\biconditional}" }, + ] + }, + { + latex: "\\infty", + shift: "\\omega", + variants: [ + { latex: "\\infty" }, + { latex: "\\aleph_0" }, + { latex: "\\aleph_1" }, + { latex: "\\omega" }, + { latex: "\\mathfrak{m}" }, + ] + }, + { + latex: "\\imaginaryI", + shift: "\\Re", + variants: [ + { latex: "\\Re" }, + { latex: "\\Im" }, + { latex: "\\imaginaryJ" }, + { latex: "\\imaginaryI" }, + { latex: "\\Vert#0\\Vert" }, + ] + }, + { + latex: "\\mathrm{d}#0", + shift: "\\partial", + variants: [ + { latex: "\\mathrm{d}#0" }, + { latex: "\\dfrac{\\mathrm{d}}{\\mathrm{d}#0}" }, + { latex: "\\frac{\\partial}{\\partial #0}" }, + { latex: "\\mathrm{d}" }, + { latex: "\\partial" }, + ] + }, + ], + [ + { + latex: "\\sin", + shift: "\\sin^{-1}", + variants: [ + { class: "small", latex: "\\sin" }, + { class: "small", latex: "\\sinh" }, + { class: "small", latex: "\\sin^{-1}" }, + { class: "small", latex: "\\arsinh" }, + ] + }, + { + latex: "\\cos", + shift: "\\cos^{-1}", + variants: [ + { class: "small", latex: "\\cos" }, + { class: "small", latex: "\\cosh" }, + { class: "small", latex: "\\cos^{-1}" }, + { class: "small", latex: "\\arcosh" }, + ] + }, + { + latex: "\\tan", + shift: "\\tan^{-1}", + variants: [ + { class: "small", latex: "\\tan" }, + { class: "small", latex: "\\tg" }, + { class: "small", latex: "\\tan^{-1}" }, + { class: "small", latex: "\\tanh" }, + { class: "small", latex: "\\artanh" }, + { class: "small", latex: "\\arctan" }, + { class: "small", latex: "\\arctg" }, + { class: "small", latex: "\\cot" }, + ] + }, + "\\Delta", + { + latex: "\\pi", + shift: "\\tau", + variants: [ + { latex: "\\pi" }, + { latex: "\\tau" }, + { latex: "\\rho" }, + { latex: "\\theta" }, + ] + }, + { + latex: "f(#0)", + shift: "x_{i}", + variants: [ + { class: "small", latex: "f(#0)" }, + { class: "small", latex: "g(#0)" }, + { latex: "x^{n}" }, + { latex: "x^{#0}" }, + { latex: "x_{n}" }, + { latex: "x_{i}" }, + { latex: "x_{#0}" }, + ] + }, + + { + latex: "#@_{i}", + shift: "#@^{n}", + variants: [ + { latex: "#@_{i}" }, + { latex: "#@_{n}" }, + { latex: "#@^{n}" }, + { latex: "#@_{ij}" }, + { latex: "#@_{t}" }, + ] + }, + { + latex: "\\text{\\_}", + shift: "\\circ", + variants: [ + { latex: "\\ast" }, + { latex: "\\circ" }, + { latex: "\\bigcirc" }, + { latex: "\\bullet" }, + { latex: "\\odot" }, + { latex: "\\oslash" }, + { latex: "\\circledcirc" }, + { latex: "\\star" }, + { latex: "\\times" }, + { latex: "\\doteq" }, + { latex: "\\doteqdot" }, + ] + }, + { + latex: "+", + aside: "New Line", + class: "action", + command: ["performWithFeedback", "addRowAfter"], + shift: { + latex: "-", + class: "action", + aside: "Delete Line", + command: ["performWithFeedback", "removeRow"], + }, + variants: [ + { + latex: "+", + aside: "New Line", + class: "small", + command: ["performWithFeedback", "addRowAfter"], + }, + { + latex: "-", + aside: "Delete Line", + class: "small", + command: ["performWithFeedback", "removeRow"], + }, + ] + }, + { + label: "T", + aside: "Text Mode", + class: "action", + command: ["switchMode", "text"], + shift: { + label: "M", + aside: "Math Mode", + class: "small action", + command: ["switchMode", "math"], + }, + variants: [ + { + label: "T", + class: "small", + aside: "Math Mode", + command: ["switchMode", "text"], + }, + { + label: "M", + class: "small", + aside: "Math Mode", + command: ["switchMode", "math"], + }, + { + label: "L", + class: "small", + aside: "LaTeX Mode", + command: ["switchMode", "latex"], + }, + ] + }, + ] + ] +}; \ No newline at end of file diff --git a/indexer-frontend/src/app/components/math-live/keyboards/matrix-keyboard.ts b/indexer-frontend/src/app/components/math-live/keyboards/matrix-keyboard.ts new file mode 100644 index 0000000000..8f1f5ea783 --- /dev/null +++ b/indexer-frontend/src/app/components/math-live/keyboards/matrix-keyboard.ts @@ -0,0 +1,252 @@ +export const matrixKeyboard = { + label: "matrix", + tooltip: "Matrix", + rows: [ + [ + { + label: "1x1", + latex: "\\begin{pmatrix}#0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0\\end{Bmatrix}" }, + ] + }, + { + label: "1x2", + latex: "\\begin{pmatrix}#0 & #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0 & #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0 & #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0 & #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0 & #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0 & #0\\end{Bmatrix}" }, + ] + }, + { + label: "1x3", + latex: "\\begin{pmatrix}#0 & #0 & #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0 & #0 & #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0 & #0 & #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0 & #0 & #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0 & #0 & #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0 & #0 & #0\\end{Bmatrix}" }, + ] + }, + { + label: "1x4", + latex: "\\begin{pmatrix}#0 & #0 & #0 & #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0 & #0 & #0 & #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0 & #0 & #0 & #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0 & #0 & #0 & #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0 & #0 & #0 & #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0 & #0 & #0 & #0\\end{Bmatrix}" }, + ] + }, + { + latex: "\\cdots", + command: ["performWithFeedback", "addRowAfter"], + aside: "Add Row After", + shift: "\\cdots", + variants: [ + { + latex: "\\cdots", + command: ["performWithFeedback", "addRowAfter"], + aside: "Add Row After", + }, + { + latex: "\\cdots", + command: ["performWithFeedback", "addRowBefore"], + aside: "Add Row Before", + } + ] + } + ], + [ + { + label: "2x1", + latex: "\\begin{pmatrix}#0\\\\ #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0\\\\ #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0\\\\ #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0\\\\ #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0\\\\ #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0\\\\ #0\\end{Bmatrix}" }, + ] + }, + { + label: "2x2", + latex: "\\begin{pmatrix}#0 & #0\\\\ #0 & #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0 & #0\\\\ #0 & #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0 & #0\\\\ #0 & #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0 & #0\\\\ #0 & #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0 & #0\\\\ #0 & #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0 & #0\\\\ #0 & #0\\end{Bmatrix}" }, + ] + }, + { + label: "2x3", + latex: "\\begin{pmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\end{Bmatrix}" }, + ] + }, + { + label: "2x4", + latex: "\\begin{pmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{Bmatrix}" }, + ] + }, + { + latex: "\\vdots", + command: ["performWithFeedback", "addColumnAfter"], + aside: "Add Column After", + shift: "\\vdots", + variants: [ + { + latex: "\\vdots", + command: ["performWithFeedback", "addColumnAfter"], + aside: "Add Column After", + }, + { + latex: "\\vdots", + command: ["performWithFeedback", "addColumnBefore"], + aside: "Add Column Before", + } + ] + } + ], + [ + { + label: "3x1", + latex: "\\begin{pmatrix}#0\\\\ #0\\\\ #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0\\\\ #0\\\\ #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0\\\\ #0\\\\ #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0\\\\ #0\\\\ #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0\\\\ #0\\\\ #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0\\\\ #0\\\\ #0\\end{Bmatrix}" }, + ] + }, + { + label: "3x2", + latex: "\\begin{pmatrix}#0 & #0\\\\ #0 & #0\\\\ #0 & #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0 & #0\\\\ #0 & #0\\\\ #0 & #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0 & #0\\\\ #0 & #0\\\\ #0 & #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0 & #0\\\\ #0 & #0\\\\ #0 & #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0 & #0\\\\ #0 & #0\\\\ #0 & #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0 & #0\\\\ #0 & #0\\\\ #0 & #0\\end{Bmatrix}" }, + ] + }, + { + label: "3x3", + latex: "\\begin{pmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\end{Bmatrix}" }, + ] + }, + { + label: "3x4", + latex: "\\begin{pmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{Bmatrix}" }, + ] + }, + { + latex: "\\cdots", + aside: "Delelte Row", + command: ["performWithFeedback", "removeRow"], + } + ], + [ + { + label: "4x1", + latex: "\\begin{pmatrix}#0\\\\ #0\\\\ #0\\\\ #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0\\\\ #0\\\\ #0\\\\ #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0\\\\ #0\\\\ #0\\\\ #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0\\\\ #0\\\\ #0\\\\ #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0\\\\ #0\\\\ #0\\\\ #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0\\\\ #0\\\\ #0\\\\ #0\\end{Bmatrix}" }, + ] + }, + { + label: "4x2", + latex: "\\begin{pmatrix}#0 & #0\\\\ #0 & #0\\\\ #0 & #0\\\\ #0 & #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0 & #0\\\\ #0 & #0\\\\ #0 & #0\\\\ #0 & #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0 & #0\\\\ #0 & #0\\\\ #0 & #0\\\\ #0 & #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0 & #0\\\\ #0 & #0\\\\ #0 & #0\\\\ #0 & #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0 & #0\\\\ #0 & #0\\\\ #0 & #0\\\\ #0 & #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0 & #0\\\\ #0 & #0\\\\ #0 & #0\\\\ #0 & #0\\end{Bmatrix}" }, + ] + }, + { + label: "4x3", + latex: "\\begin{pmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\\\ #0 & #0 & #0\\end{Bmatrix}" }, + ] + }, + { + label: "4x4", + latex: "\\begin{pmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{pmatrix}", + shift: "[#0]", + variants: [ + { latex: "\\ddots", insert: "\\begin{matrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{matrix}" }, + { latex: "(\\ddots)", insert: "\\begin{pmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{pmatrix}" }, + { latex: "\\lbrack\\ddots\\rbrack", insert: "\\begin{bmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{bmatrix}" }, + { latex: "\\vert\\ddots\\vert", insert: "\\begin{vmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{vmatrix}" }, + { latex: "\\lbrace\\ddots\\rbrace", insert: "\\begin{Bmatrix}#0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\\\ #0 & #0 & #0 & #0\\end{Bmatrix}" }, + ] + }, + { + latex: "\\vdots", + aside: "Delete Column", + command: ["performWithFeedback", "removeColumn"], + } + ] + ] +}; \ No newline at end of file diff --git a/indexer-frontend/src/app/components/math-live/math-live.component.html b/indexer-frontend/src/app/components/math-live/math-live.component.html new file mode 100644 index 0000000000..ece16ca201 --- /dev/null +++ b/indexer-frontend/src/app/components/math-live/math-live.component.html @@ -0,0 +1,3 @@ +
+ +
\ No newline at end of file diff --git a/indexer-frontend/src/app/components/math-live/math-live.component.scss b/indexer-frontend/src/app/components/math-live/math-live.component.scss new file mode 100644 index 0000000000..a98b7c0535 --- /dev/null +++ b/indexer-frontend/src/app/components/math-live/math-live.component.scss @@ -0,0 +1,20 @@ +.mathlive-content { + display: flex; + flex-flow: column; +} + +.mathlive-content::ng-deep { + math-field::part(virtual-keyboard-toggle) { + display: none; + } + + math-field { + min-height: 40px; + border-radius: 8px; + outline: none; + border: 1px solid var(--guardian-grey-color-2, #E1E7EF); + padding-left: 12px; + font-size: 24px; + user-select: all; + } +} \ No newline at end of file diff --git a/indexer-frontend/src/app/components/math-live/math-live.component.ts b/indexer-frontend/src/app/components/math-live/math-live.component.ts new file mode 100644 index 0000000000..854ff53dc8 --- /dev/null +++ b/indexer-frontend/src/app/components/math-live/math-live.component.ts @@ -0,0 +1,73 @@ +import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MathfieldElement } from 'mathlive'; +import { matrixKeyboard } from './keyboards/matrix-keyboard'; +import { mathKeyboard } from './keyboards/math-keyboard'; + +@Component({ + selector: 'math-live', + templateUrl: './math-live.component.html', + styleUrl: './math-live.component.scss', + standalone: true, + imports: [ + CommonModule + ] +}) +export class MathLiveComponent { + @ViewChild('mathLiveContent', { static: true }) mathLiveContent!: ElementRef; + @Input('readonly') readonly: boolean = false; + @Input('value') value!: string; + @Output('valueChange') valueChange = new EventEmitter(); + @Output('keyboard') keyboard = new EventEmitter(); + @Output('focus') focus = new EventEmitter(); + + private readonly mfe: MathfieldElement; + + + constructor() { + MathfieldElement.keypressSound = null; + MathfieldElement.plonkSound = null; + this.mfe = new MathfieldElement(); + } + + ngOnInit(): void { + const mathVirtualKeyboard: any = window.mathVirtualKeyboard; + mathVirtualKeyboard.layouts = [ + mathKeyboard, + matrixKeyboard, + "numeric", + "symbols", + "greek" + ]; + this.mfe.mathVirtualKeyboardPolicy = "manual"; + this.mfe.addEventListener("focusin", () => { + this.keyboard.emit(true); + this.focus.emit(this); + // return mathVirtualKeyboard.show(); + }); + this.mfe.addEventListener("focusout", () => { + this.keyboard.emit(false); + this.focus.emit(this); + // return mathVirtualKeyboard.hide(); + }); + this.mfe.addEventListener('input', (ev: any) => { + this.value = ev?.target?.value; + this.valueChange.emit(this.value); + }); + this.mfe.value = this.value || ''; + this.mfe.readonly = this.readonly; + } + + ngAfterViewInit() { + const container = this.mathLiveContent.nativeElement; + container.appendChild(this.mfe) + } + + ngOnDestroy() { + this.mfe.remove(); + } + + public getElement(): ElementRef { + return this.mathLiveContent; + } +} diff --git a/indexer-frontend/src/app/components/schema-form-view/schema-form-view.component.html b/indexer-frontend/src/app/components/schema-form-view/schema-form-view.component.html index 558dbc1eaf..a215b1ce6e 100644 --- a/indexer-frontend/src/app/components/schema-form-view/schema-form-view.component.html +++ b/indexer-frontend/src/app/components/schema-form-view/schema-form-view.component.html @@ -30,6 +30,18 @@
{{item.unit}}
+ +
+ +
diff --git a/indexer-frontend/src/app/components/schema-form-view/schema-form-view.component.css b/indexer-frontend/src/app/components/schema-form-view/schema-form-view.component.scss similarity index 82% rename from indexer-frontend/src/app/components/schema-form-view/schema-form-view.component.css rename to indexer-frontend/src/app/components/schema-form-view/schema-form-view.component.scss index 30a1358c85..61e06b195a 100644 --- a/indexer-frontend/src/app/components/schema-form-view/schema-form-view.component.css +++ b/indexer-frontend/src/app/components/schema-form-view/schema-form-view.component.scss @@ -4,7 +4,7 @@ form { } .group-label { - color: rgba(0,0,0,.6); + color: rgba(0, 0, 0, .6); } .group-form { @@ -33,7 +33,7 @@ form { .group-label, .array-label { - color: rgba(0,0,0,.6); + color: rgba(0, 0, 0, .6); background: #fff; top: -10px; left: 5px; @@ -100,20 +100,20 @@ form { margin-right: 20px; } -.form-field-array-item > .form-field-group { +.form-field-array-item>.form-field-group { padding-left: 40px; } -.schema-form-field-array-group > .form-field-array-item > .form-field-group, -.form-field-array-item > .form-field-group { +.schema-form-field-array-group>.form-field-array-item>.form-field-group, +.form-field-array-item>.form-field-group { border-left: 1px solid var(--color-grey-3) !important; } .form-field-group { - margin-bottom: 15px; + margin-bottom: 15px; } -.form-field-group > .form-field-array-item { +.form-field-group>.form-field-array-item { padding-right: 0; } @@ -161,6 +161,27 @@ form { font-size: 20px; } + +.form-field-formula { + padding: 0px 2px 0px 4px; + margin-left: 10px; + display: flex; + justify-content: flex-start; + align-items: center; + font-size: 20px; + + .guardian-icon-button { + width: 38px; + height: 38px; + border-radius: 8px; + border: 1px solid var(--color-grey-3, #E1E7EF); + + svg { + fill: #878787; + } + } +} + .loading { margin: 10px auto; } @@ -202,4 +223,4 @@ form { top: 22px; display: block; position: absolute; -} +} \ No newline at end of file diff --git a/indexer-frontend/src/app/components/schema-form-view/schema-form-view.component.ts b/indexer-frontend/src/app/components/schema-form-view/schema-form-view.component.ts index 7ade7bad5d..48dbf1f6b6 100644 --- a/indexer-frontend/src/app/components/schema-form-view/schema-form-view.component.ts +++ b/indexer-frontend/src/app/components/schema-form-view/schema-form-view.component.ts @@ -16,6 +16,8 @@ import { CheckboxModule } from 'primeng/checkbox'; import { FormsModule } from '@angular/forms'; import { SchemaField, Schema } from '@indexer/interfaces'; import { InputTextareaModule } from 'primeng/inputtextarea'; +import { DialogService } from 'primeng/dynamicdialog'; +import { FormulasViewDialog } from '../../dialogs/formulas-view-dialog/formulas-view-dialog.component'; /** * Form view by schema @@ -23,7 +25,7 @@ import { InputTextareaModule } from 'primeng/inputtextarea'; @Component({ selector: 'app-schema-form-view', templateUrl: './schema-form-view.component.html', - styleUrls: ['./schema-form-view.component.css'], + styleUrls: ['./schema-form-view.component.scss'], standalone: true, imports: [ InputTextModule, @@ -36,7 +38,9 @@ import { InputTextareaModule } from 'primeng/inputtextarea'; CheckboxModule, FormsModule, InputTextareaModule, + FormulasViewDialog ], + providers: [DialogService], changeDetection: ChangeDetectionStrategy.OnPush, }) export class SchemaFormViewComponent { @@ -45,10 +49,15 @@ export class SchemaFormViewComponent { @Input('fields') schemaFields!: SchemaField[]; @Input('delimiter-hide') delimiterHide: boolean = false; @Input('values') values: any; + @Input() formulas?: any; fields: any[] | undefined = []; pageSize: number = 20; + constructor( + private dialogService: DialogService + ) { } + isBooleanView(item: boolean | any): string { return typeof item === 'boolean' ? String(item) : 'Unset'; } @@ -77,14 +86,15 @@ export class SchemaFormViewComponent { } const item: any = { ...field, + fullPath: field.fullPath || '', hide: false, isInvalidType: false, }; if (!field.isArray && !field.isRef) { item.value = !this.values || - this.values[item.name] === null || - this.values[item.name] === undefined + this.values[item.name] === null || + this.values[item.name] === undefined ? '' : this.values[item.name]; } @@ -174,4 +184,18 @@ export class SchemaFormViewComponent { isPostfix(item: SchemaField): boolean { return item.unitSystem === 'postfix'; } + + public isFormulas(item: any) { + return this.formulas ? this.formulas[item.fullPath] : undefined; + } + + public showFormulas(formulas: any) { + const dialogRef = this.dialogService.open(FormulasViewDialog, { + showHeader: false, + width: '950px', + styleClass: 'guardian-dialog', + data: formulas, + }); + dialogRef.onClose.subscribe((result: any) => { }); + } } diff --git a/indexer-frontend/src/app/dialogs/formulas-view-dialog/formulas-view-dialog.component.html b/indexer-frontend/src/app/dialogs/formulas-view-dialog/formulas-view-dialog.component.html new file mode 100644 index 0000000000..81d2362baf --- /dev/null +++ b/indexer-frontend/src/app/dialogs/formulas-view-dialog/formulas-view-dialog.component.html @@ -0,0 +1,240 @@ +
+
+
View Formula
+
+
+ + + +
+
+
+ + + +
+ + +
+ + @if (current && current.type === 'constant') { +
Constant
+
+ +
+ + +
+
+ + +
+
+ + +
+
+ } + + @if (current && current.type === 'variable') { +
Variable
+
+
+ + +
+
+ + +
+ @if (current.hasLink) { +
+ + +
+ } +
+ } + + @if (current && current.type === 'formula') { +
Formula
+
+
+ + +
+
+ + +
+
+ + +
+ @if (current.hasLink) { +
+ + +
+ } +
+ +
+ @for (relationship of current.relationshipItems; track $index) { +
+ {{relationship.name}} +
+ } +
+
+
+ } + @if (current && current.type === 'text') { +
Text
+
+
+ + +
+
+ + +
+
+ + +
+ @if (current.hasLink) { +
+ + +
+ } +
+ +
+ @for (relationship of current.relationshipItems; track $index) { +
+ {{relationship.name}} +
+ } +
+
+
+ } + + @if (current && current.type === 'schema') { +
Schema
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ } + +
+
+
+ +
+
+ @for (file of files; track file) { +
+
+ + +
+
+ + +
+
+ } + @if (!files.length) { +
+
There were no files attached.
+
+ } +
+
+
+
+
+ \ No newline at end of file diff --git a/indexer-frontend/src/app/dialogs/formulas-view-dialog/formulas-view-dialog.component.scss b/indexer-frontend/src/app/dialogs/formulas-view-dialog/formulas-view-dialog.component.scss new file mode 100644 index 0000000000..de57e4f2fe --- /dev/null +++ b/indexer-frontend/src/app/dialogs/formulas-view-dialog/formulas-view-dialog.component.scss @@ -0,0 +1,301 @@ +.dialog-header { + .header-item { + padding: 6px 12px; + margin: 0px 16px; + border-radius: 6px; + font-size: var(--guardian-primary-font-size); + color: var(--guardian-dry-run-color); + background: var(--guardian-dry-run-background); + user-select: none; + cursor: default; + } +} + +.guardian-input-container { + margin-bottom: 24px; +} + +.guardian-textarea-container { + margin-bottom: 24px; +} + +.dialog-body { + display: flex; + flex-direction: column; + min-height: 600px; + padding-bottom: 24px; +} + +.view-body { + display: flex; + flex-direction: row; + min-height: 600px; + height: 600px; +} + +.action-buttons { + display: flex; + justify-content: flex-end; + user-select: none; + position: relative; + + &>div { + margin-left: 15px; + margin-right: 0px; + } +} + +.close-icon-color { + fill: #848FA9; + color: #848FA9; +} + +.context { + position: relative; + height: 100%; + display: flex; + flex-direction: column; + width: 100%; + overflow: hidden; + + .current-item-name { + font-family: Poppins; + font-size: 24px; + font-weight: 600; + line-height: 32px; + color: #181818; + margin-bottom: 24px; + } + + .current-item { + border: 1px solid #E1E7EF; + border-radius: 8px; + height: 100%; + overflow: auto; + padding: 24px; + } +} + +.formula-relationships { + border-radius: 8px; + outline: none; + border: 1px solid var(--guardian-grey-color-2, #E1E7EF); + font-family: Inter; + font-size: 14px; + font-weight: 400; + color: #000; + padding: 8px 16px 0px 16px; + display: flex; + flex-direction: row; + flex-wrap: wrap; + min-height: 40px; + + .formula-relationship { + width: auto; + height: 30px; + padding: 7px 12px 7px 12px; + border-radius: 6px; + background: var(--guardian-grey-color, #EFF3F7); + color: var(--guardian-disabled-color, #848FA9); + font-family: Inter; + font-size: 14px; + font-weight: 400; + line-height: 16px; + text-align: left; + margin-right: 8px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + display: inline-flex; + margin-bottom: 8px; + cursor: pointer; + + &:hover { + filter: brightness(0.95); + } + } +} + +.formula-link { + height: 40px; + border-radius: 8px; + outline: none; + border: 1px solid var(--guardian-grey-color-2, #E1E7EF); + font-family: Inter; + font-size: 14px; + font-weight: 400; + color: #000; + padding: 12px 16px; + cursor: pointer; + + &:hover { + background: var(--guardian-hover, #F0F3FC); + } + + .formula-link-value { + display: flex; + flex-direction: row; + pointer-events: none; + + &__entity { + font-family: Inter; + font-size: 14px; + font-weight: 500; + line-height: 16px; + color: var(--guardian-entity-color, #DA9B22); + margin-right: 8px; + } + + &__item { + font-family: Inter; + font-size: 14px; + font-weight: 400; + line-height: 16px; + } + } +} + +.nav-container { + display: flex; + width: 260px; + min-width: 260px; + height: 100%; + padding: 16px 0px 16px 0px; + border: 1px solid #E1E7EF; + border-radius: 8px; + margin-right: 24px; + position: relative; + overflow: auto; +} + +.field-tree { + width: auto; + min-width: 100%; + + .field-item { + width: auto; + padding: 8px 24px 8px 16px; + font-family: Inter; + font-size: 14px; + font-weight: 500; + line-height: 18px; + text-align: left; + display: flex; + justify-content: flex-start; + align-items: center; + + .field-offset { + width: 0px; + height: 24px; + overflow: hidden; + } + + .field-collapse { + width: 24px; + min-width: 24px; + height: 24px; + overflow: hidden; + } + + .field-select { + width: 24px; + min-width: 24px; + height: 24px; + overflow: hidden; + cursor: pointer; + + * { + pointer-events: none; + } + } + + .field-container { + padding-left: 8px; + cursor: pointer; + + * { + pointer-events: none; + } + + &[readonly="true"] { + cursor: default; + } + + &__view { + text-transform: capitalize; + font-size: 12px; + font-weight: bold; + margin-bottom: 4px; + } + + &__entity { + font-family: Inter; + font-size: 14px; + font-weight: 500; + line-height: 16px; + color: var(--guardian-entity-color, #DA9B22); + margin-bottom: 4px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 150px; + } + + &__header { + display: flex; + height: 18px; + + &-type { + font-family: Inter; + font-size: 12px; + font-weight: 400; + line-height: 16px; + color: #848FA9; + margin-right: 4px; + padding: 2px 0px; + text-transform: capitalize; + } + + &-name { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 150px; + } + } + } + + &[selected="true"] { + background: var(--guardian-primary-background, #e1e7fa); + } + } +} + +p-tabpanel::ng-deep .p-tabview-panels { + margin-top: 24px; +} + + +.files-container { + width: 100%; + + .file-item { + width: 100%; + border: 1px solid #E1E7EF; + border-radius: 8px; + padding: 24px; + margin-bottom: 24px; + } + + .no-files { + font-family: Inter; + font-size: 14px; + font-weight: 400; + color: var(--guardian-disabled-color, #848FA9); + height: 87px; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + } +} \ No newline at end of file diff --git a/indexer-frontend/src/app/dialogs/formulas-view-dialog/formulas-view-dialog.component.ts b/indexer-frontend/src/app/dialogs/formulas-view-dialog/formulas-view-dialog.component.ts new file mode 100644 index 0000000000..0bdc28fd8f --- /dev/null +++ b/indexer-frontend/src/app/dialogs/formulas-view-dialog/formulas-view-dialog.component.ts @@ -0,0 +1,90 @@ +import { Component } from '@angular/core'; +import { DialogService, DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { FormulaFiles, FormulaItem, FormulasTree, SchemaItem } from '../../models/formula-tree'; +import { TreeListData, TreeListItem, TreeListView } from '../../models/tree-list'; +import { MathLiveComponent } from '@components/math-live/math-live.component'; +import { TabViewModule } from 'primeng/tabview'; +import { TranslocoModule } from '@jsverse/transloco'; + +@Component({ + standalone: true, + selector: 'formulas-view-dialog', + templateUrl: './formulas-view-dialog.component.html', + styleUrls: ['./formulas-view-dialog.component.scss'], + imports: [ + TranslocoModule, + MathLiveComponent, + TabViewModule + ] +}) +export class FormulasViewDialog { + public loading = true; + public tree: FormulasTree; + public schema: string; + public path: string; + public items: FormulaItem[]; + public current: FormulaItem | SchemaItem | null; + public nav: TreeListView | null; + public files: FormulaFiles[]; + + constructor( + public ref: DynamicDialogRef, + public config: DynamicDialogConfig, + private dialogService: DialogService, + ) { + this.tree = this.config.data?.tree; + this.schema = this.config.data?.schema; + this.path = this.config.data?.path; + this.items = []; + this.files = []; + this.current = null; + this.nav = null; + } + + ngOnInit() { + this.loading = false; + this.items = this.tree?.get(this.schema, this.path) || []; + this.files = this.tree?.getFiles(this.items) || []; + const navTree = FormulasTree.createNav(this.items) + const fields = TreeListData.fromObject(navTree, 'children'); + this.nav = TreeListView.createView(fields, (s) => { return !s.parent }); + this.nav.collapseAll(false); + this.nav.updateHidden(); + this.selectItem(this.items[0]); + } + + ngOnDestroy() { + } + + public onClose(): void { + this.ref.close(null); + } + + public onLink(current: FormulaItem) { + this.selectItem(current.linkItem); + } + + public onRelationship(relationship: FormulaItem) { + this.selectItem(relationship); + } + + public onCollapseNav(item: TreeListItem) { + if (this.nav) { + this.nav.collapse(item, !item.collapsed); + this.nav.updateHidden(); + } + } + + public onSelectNav(item: TreeListItem) { + this.selectItem(item.data.data); + } + + private selectItem(current: FormulaItem | SchemaItem | null) { + this.current = current; + if (this.nav) { + this.nav.data.items.forEach((e) => e.selected = e.data.data === this.current); + this.nav.updateHidden(); + this.nav.updateSelected(); + } + } +} diff --git a/indexer-frontend/src/app/models/formula-tree.ts b/indexer-frontend/src/app/models/formula-tree.ts new file mode 100644 index 0000000000..35f8f56d24 --- /dev/null +++ b/indexer-frontend/src/app/models/formula-tree.ts @@ -0,0 +1,684 @@ +import { FormulaItemType, IFormula, IFormulaItem, IFormulaFile, IFormulaLink, Message, Schema, SchemaField } from "@indexer/interfaces"; + +export interface Link { + schema: string; + path: string; + item: FormulaItem; +} + +export class FormulaFiles { + public readonly name: string; + public readonly type: string; + public readonly url: string; + + constructor(config: IFormulaFile) { + this.name = config.name || ''; + this.type = config.type || ''; + this.url = config.url || ''; + } +} + +export class DocumentItem { + private _type: Set; + private _map: Map; + + constructor(document: any) { + this._type = new Set(); + this._map = new Map(); + this.parsDoc(document); + } + + private parsDoc(document: any) { + try { + if (!document || !document.document) { + return; + } + const json = document.document; + if ((json).credentialSubject) { + this.parsVC(json); + } else if ((json).verifiableCredential) { + this.parsVP(json); + } else { + return; + } + } catch (error) { + console.error(error); + } + } + + private addValue(key: string, value: any, prev?: string) { + if (value) { + let path: string; + if (prev) { + if (key) { + path = `${prev}.${key}`; + } else { + path = `${prev}`; + } + } else { + if (key) { + path = `${key}`; + } else { + path = ''; + } + } + switch (typeof value) { + case 'boolean': + case 'number': + case 'string': { + const old = this._map.get(path); + if (old) { + if (Array.isArray(old)) { + old.push(value); + this._map.set(path, old); + } else { + this._map.set(path, [old, value]); + } + } else { + this._map.set(path, value); + } + break; + } + case 'object': { + if (Array.isArray(value)) { + for (const e of value) { + this.addValue('', e, path); + } + } else { + this.parsFields(value, path); + } + break; + } + default: { + return; + } + } + } + } + + private parsFields(json: any, prev?: string) { + if (json) { + for (const [key, value] of Object.entries(json)) { + this.addValue(key, value, prev); + } + } + } + + private parsCredentialSubject(cs: any) { + if (cs) { + this._type.add(cs.type); + this.parsFields(cs); + } + } + + private parsVC(vc: any) { + if (vc && vc.credentialSubject) { + if (Array.isArray(vc.credentialSubject)) { + for (const cs of vc.credentialSubject) { + this.parsCredentialSubject(cs); + } + } else { + this.parsCredentialSubject(vc.credentialSubject); + } + } + } + + private parsVP(vp: any) { + if (vp && vp.verifiableCredential) { + if (Array.isArray(vp.verifiableCredential)) { + for (const vc of vp.verifiableCredential) { + this.parsVC(vc); + } + } else { + this.parsVC(vp.verifiableCredential); + } + } + } + + public has(type?: string): boolean { + if (type) { + return this._type.has(type); + } + return false; + } + + public get(path?: string): any { + if (path) { + return this._map.get(path); + } + return null; + } +} + +export class SchemaItem { + public readonly type = 'schema'; + + private _value: any; + private _schema: Schema | null; + private _field: SchemaField | null; + private _type: string | undefined; + private _path: string | undefined; + + constructor(schema: Schema | null, field: SchemaField | null) { + this._schema = schema; + this._field = field; + this._type = schema?.type; + this._path = field?.path; + } + + public get name() { + return this._schema?.name || ''; + } + + public get description() { + return this._schema?.description || ''; + } + + public get field() { + return this._field?.description || ''; + } + + public get value() { + return this._value || ''; + } + + public setDocuments(documents: DocumentItem[]) { + for (const doc of documents) { + if (doc.has(this._type)) { + this._value = doc.get(this._path); + if (Array.isArray(this._value)) { + this._value = `[${this._value.join(',')}]`; + } + } + } + } + + public static empty() { + return new SchemaItem(null, null); + } +} + +export class FormulaItem { + public readonly uuid: string; + public readonly name: string; + public readonly description: string; + public readonly type: FormulaItemType; + + private _value: any; + private _link: IFormulaLink | null; + private _relationships: string[] | null; + + private _schemaLink: { schema: string, path: string } | null; + private _formulaLink: { formula: string, variable: string } | null; + private _parent: FormulaTree | null; + + private _relationshipItems: FormulaItem[]; + private _parentItems: FormulaItem[]; + private _linkEntity: FormulaTree | Schema | null; + private _linkItem: FormulaItem | SchemaItem | null; + + constructor(config: IFormulaItem) { + this.uuid = config.uuid || ''; + this.name = config.name || ''; + this.description = config.description || ''; + this.type = config.type || ''; + + this._value = config.value || ''; + this._link = config.link || null; + this._relationships = config.relationships || null; + + this._schemaLink = null; + this._formulaLink = null; + if (this._link) { + if (this._link.type === 'schema') { + this._schemaLink = { + schema: this._link.entityId, + path: this._link.item + } + } + if (this._link.type === 'formula') { + this._formulaLink = { + formula: this._link.entityId, + variable: this._link.item + } + } + } + + this._parent = null; + this._relationshipItems = []; + this._parentItems = []; + this._linkItem = null; + this._linkEntity = null; + } + + public get value() { + return this._value; + } + + public get hasLink() { + return !!this._link; + } + + public get schemaLink() { + return this._schemaLink; + } + + public get formulaLink() { + return this._formulaLink; + } + + public get linkEntity() { + return this._linkEntity; + } + + public get linkItem() { + return this._linkItem; + } + + public get linkEntityName() { + if (this._formulaLink && this._linkEntity) { + return this._linkEntity.name; + } + if (this._schemaLink && this._linkEntity) { + return this._linkEntity.name; + } + return null; + } + + public get linkItemName() { + if (this._formulaLink && this._linkItem) { + return (this._linkItem as FormulaItem).name; + } + if (this._schemaLink && this._linkItem) { + return (this._linkItem as SchemaItem).field; + } + return null; + } + + public get relationshipItems() { + return this._relationshipItems; + } + + public setParent(parent: FormulaTree) { + this._parent = parent; + } + + public setRelationship(items: FormulaItem[]) { + if (Array.isArray(this._relationships)) { + this._relationshipItems = items.filter((e) => this._relationships?.includes(e.uuid)); + } else { + this._relationshipItems = []; + } + + this._parentItems = []; + for (const item of items) { + if (Array.isArray(item._relationships) && item._relationships.includes(this.uuid)) { + this._parentItems.push(item); + } + } + } + + public setFormulas(formulas: FormulaTree[]) { + if (this._formulaLink) { + this._linkEntity = formulas.find((e) => e.uuid === this._formulaLink?.formula) || null; + if (this._linkEntity) { + this._linkItem = this._linkEntity.get(this._formulaLink.variable); + } + } + } + + public setSchemas(schemas: Schema[]) { + if (this._schemaLink) { + const schema = schemas.find((e) => e.iri === this._schemaLink?.schema) || null; + this._linkEntity = schema; + if (schema) { + const field = schema.getField(this._schemaLink.path); + if (field) { + this._linkItem = new SchemaItem(schema, field); + } + } else { + this._linkItem = SchemaItem.empty(); + } + } + } + + public setDocuments(documents: DocumentItem[]) { + if (this._schemaLink && this._linkItem) { + (this._linkItem as SchemaItem).setDocuments(documents); + } + } + + public createNav(list: Set): any { + const item: any = { + view: 'component', + type: this.type, + name: this.name, + data: this, + children: [] + } + if (this._formulaLink) { + item.children.push({ + view: 'link', + type: this._linkItem?.type, + entity: this._linkEntity?.name, + name: this._linkItem?.name, + data: this._linkItem, + children: [] + }); + } + if (this._schemaLink) { + item.children.push({ + view: 'link', + type: 'field', + entity: this._linkEntity?.name, + name: (this._linkItem as SchemaItem)?.field, + data: this._linkItem, + children: [] + }); + } + + for (const ref of this._relationshipItems) { + if (!list.has(ref)) { + const newList = new Set(list); + newList.add(ref); + const nav = ref.createNav(newList); + item.children.push(nav); + } + } + return item; + } + + public getFiles(): FormulaFiles[] { + return this._parent?.getFiles() || []; + } +} + +export class FormulaTree { + public readonly uuid: string; + public readonly name: string; + public readonly description: string; + + private _links: Map>; + private _items: FormulaItem[]; + private _files: FormulaFiles[]; + + constructor(formula: IFormula) { + this.uuid = formula.uuid || ''; + this.name = formula.name || ''; + this.description = formula.description || ''; + + this._links = new Map>(); + this._items = []; + this._files = []; + this.parse(formula?.config?.formulas); + this.parseFiles(formula?.config?.files); + } + + private parse(items?: IFormulaItem[]) { + if (!items) { + return; + } + + this._items = []; + for (const config of items) { + const item = new FormulaItem(config); + item.setParent(this); + const link = item.schemaLink; + if (link) { + const map = this._links.get(link.schema) || new Map(); + const array = map.get(link.path) || []; + array.push(item); + map.set(link.path, array); + this._links.set(link.schema, map); + } + this._items.push(item); + } + + for (const item of this._items) { + item.setRelationship(this._items); + } + } + + private parseFiles(files?: IFormulaFile[]) { + if (!files) { + return; + } + + this._files = []; + for (const config of files) { + const file = new FormulaFiles(config); + this._files.push(file); + } + } + + public setFormulas(formulas: FormulaTree[]) { + for (const item of this._items) { + item.setFormulas(formulas); + } + } + + public setSchemas(schemas: Schema[]) { + for (const item of this._items) { + item.setSchemas(schemas); + } + } + + public setDocuments(documents: DocumentItem[]) { + for (const item of this._items) { + item.setDocuments(documents); + } + } + + public hasLink(schema: string, path: string): boolean { + return this._links.get(schema)?.has(path) || false; + } + + public getLink(schema: string, path: string): FormulaItem[] { + return this._links.get(schema)?.get(path) || []; + } + + public get(variable: string): FormulaItem | null { + for (const item of this._items) { + if (item.name === variable) { + return item; + } + } + return null; + } + + public merge(links: Map>) { + for (const [schema, map] of this._links.entries()) { + const fullMap = links.get(schema) || new Map(); + for (const [path, array] of map.entries()) { + const fullArray = fullMap.get(path) || []; + for (const item of array) { + fullArray.push(item); + } + fullMap.set(path, fullArray); + } + links.set(schema, fullMap); + } + } + + public getFiles(): FormulaFiles[] { + return this._files; + } +} + +export class FormulasTree { + public items: FormulaTree[]; + + private _links: Map>; + + constructor() { + this._links = new Map>(); + this.items = []; + } + + public setFormulas(formulas: IFormula[]) { + if (Array.isArray(formulas)) { + this.items = formulas.map((f) => new FormulaTree(f)); + } else { + this.items = []; + } + for (const item of this.items) { + item.setFormulas(this.items); + } + } + + public setSchemas(schemas: Schema[]) { + for (const item of this.items) { + item.setSchemas(schemas); + } + } + + public setDocuments(documents: (any) | (any)[]) { + const _documents: DocumentItem[] = []; + if (Array.isArray(documents)) { + for (const doc of documents) { + const _doc = new DocumentItem(doc); + _documents.push(_doc); + } + } else if (documents) { + const _doc = new DocumentItem(documents); + _documents.push(_doc); + } + + for (const item of this.items) { + item.setDocuments(_documents); + } + } + + public update() { + this._links.clear(); + for (const item of this.items) { + item.merge(this._links); + } + } + + public has(schema: string, path: string): boolean { + return this._links.get(schema)?.has(path) || false; + } + + public get(schema: string, path: string): FormulaItem[] { + return this._links.get(schema)?.get(path) || []; + } + + public getFiles(items: FormulaItem[]): FormulaFiles[] { + const result = new Set(); + for (const item of items) { + for (const files of item.getFiles()) { + result.add(files); + } + } + return Array.from(result); + } + + public getFields(schema?: string) { + const result: any = {}; + if (schema) { + const map = this._links.get(schema); + if (map) { + for (const path of map.keys()) { + result[`${schema}/${path}`] = { + tree: this, + schema, + path + }; + } + } + } + return result; + } + + private static getSchema(message: Message) { + try { + const schema = new Schema(message.documents[0], ''); + return schema; + } catch (error) { + return null; + } + } + + private static getDocument(message: Message) { + try { + return { + document: JSON.parse(message.documents[0]) + } + } catch (error) { + return null; + } + } + + private static getFormula(message: Message): IFormula | null { + try { + return message.analytics.config; + } catch (error) { + return null; + } + } + + public static from(response?: { + formulas: Message[], + schemas: Message[], + document: Message, + relationships: Message[] + }) { + if (!response) { + return null; + } + + const documents = []; + if (response.document) { + documents.push(FormulasTree.getDocument(response.document)); + } + if (response.relationships) { + for (const document of response.relationships) { + documents.push(FormulasTree.getDocument(document)); + } + } + + const schemas = []; + if (response.schemas) { + for (const s of response.schemas) { + const schema = FormulasTree.getSchema(s); + if (schema) { + schemas.push(schema); + } + } + } + + const formulas = []; + if (response.formulas) { + for (const f of response.formulas) { + const formula = FormulasTree.getFormula(f); + if (formula) { + formulas.push(formula); + } + } + } + + const tree = new FormulasTree(); + tree.setFormulas(formulas); + tree.setSchemas(schemas); + tree.setDocuments(documents); + tree.update(); + + return tree; + } + + public static createNav(items: FormulaItem[]): any { + const root: any = { + view: 'root', + type: 'root', + children: [] + } + for (const item of items) { + const list = new Set(); + list.add(item); + const nav = item.createNav(list); + root.children.push(nav); + } + return root; + } +} \ No newline at end of file diff --git a/indexer-frontend/src/app/models/formulas.ts b/indexer-frontend/src/app/models/formulas.ts new file mode 100644 index 0000000000..fd9f75722d --- /dev/null +++ b/indexer-frontend/src/app/models/formulas.ts @@ -0,0 +1,75 @@ +import { FormulaItemType, IFormulaConfig, IFormulaFile, IFormulaItem } from "@indexer/interfaces"; + +export class Formulas { + private items: IFormulaItem[]; + private filterMap: Map; + public files: IFormulaFile[]; + public data: IFormulaItem[]; + + constructor() { + this.items = []; + this.files = []; + this.data = []; + this.filterMap = new Map(); + this.filterMap.set(FormulaItemType.Constant, true); + this.filterMap.set(FormulaItemType.Variable, true); + this.filterMap.set(FormulaItemType.Formula, true); + this.filterMap.set(FormulaItemType.Text, true); + } + + public get all() { + return this.items; + } + + public setFilters(filter: any): void { + this.filterMap.set(FormulaItemType.Constant, filter.constant); + this.filterMap.set(FormulaItemType.Variable, filter.variable); + this.filterMap.set(FormulaItemType.Formula, filter.formula); + this.filterMap.set(FormulaItemType.Text, filter.text); + this.update(); + } + + private update(): void { + this.data = this.items.filter((e) => this.filterMap.get(e.type)); + } + + public fromData(config: IFormulaConfig) { + const items: IFormulaItem[] = config?.formulas || []; + const files: IFormulaFile[] = config?.files || []; + this.items = items.map((e) => this._fromItemJson(e)); + this.files = files.map((e) => this._fromFileJson(e)); + this.update(); + } + + public getJson(): IFormulaConfig { + return { + formulas: this.items.map((e) => this._toItemJson(e)), + files: this.files.map((e) => this._toFileJson(e)), + }; + } + + private _fromItemJson(item: IFormulaItem): IFormulaItem { + return item; + } + + private _toItemJson(item: IFormulaItem): IFormulaItem { + return item; + } + + private _fromFileJson(item: IFormulaFile): IFormulaFile { + return item; + } + + private _toFileJson(item: IFormulaFile): IFormulaFile { + return item; + } + + public getItem(uuid: string): IFormulaItem | null { + for (const item of this.items) { + if (item.uuid === uuid) { + return item; + } + } + return null; + } +} \ No newline at end of file diff --git a/indexer-frontend/src/app/models/tree-list.ts b/indexer-frontend/src/app/models/tree-list.ts new file mode 100644 index 0000000000..ba3b413dd1 --- /dev/null +++ b/indexer-frontend/src/app/models/tree-list.ts @@ -0,0 +1,401 @@ +export class TreeListItem { + public readonly data: T; + public readonly parent: TreeListItem | null; + public readonly lvl: number; + public readonly children: TreeListItem[]; + public readonly path: TreeListItem[]; + + public collapsed: boolean; + public expandable: boolean; + public selected: boolean; + public hidden: boolean; + public highlighted: boolean; + public searchHighlighted: '' | 'highlighted' | 'sub' | 'hidden'; + public search: string[]; + public searchChildren: string[]; + + constructor(data: T, parent: TreeListItem | null, lvl: number) { + this.data = data; + this.parent = parent; + this.lvl = lvl; + this.children = []; + + this.collapsed = true; + this.expandable = false; + this.selected = false; + this.highlighted = false; + this.search = []; + this.searchChildren = []; + this.searchHighlighted = ''; + this.hidden = false; + + if (this.parent) { + this.path = [...this.parent.path, this]; + } else { + this.path = [this]; + } + + } + + public setChildren(children: TreeListItem[]) { + this.children.length = 0; + for (const child of children) { + this.children.push(child); + } + this.expandable = this.children.length !== 0; + } + + public select(selected: boolean) { + this.selected = selected; + } + + public collapse(collapsed: boolean) { + this.collapsed = collapsed; + } + + public collapsePath(collapsed: boolean) { + this.collapsed = collapsed; + if (this.parent) { + this.parent.collapsePath(collapsed); + } + } + + public highlight(highlighted: boolean) { + this.highlighted = highlighted; + } + + private ifHidden(): boolean { + if (this.collapsed) { + return true; + } + if (this.parent) { + return this.parent.ifHidden(); + } else { + return false; + } + } + + public updateHidden() { + if (this.parent) { + this.hidden = this.parent.ifHidden(); + } else { + this.hidden = false; + } + } + + public setSearchRules(f: (item: T) => string[]) { + this.searchHighlighted = ''; + this.search = f(this.data); + this.searchChildren = this.search.map((s) => s + '|'); + for (const child of this.children) { + for (let i = 0; i < this.searchChildren.length; i++) { + this.searchChildren[i] = this.searchChildren[i] + child.searchChildren[i] + '|'; + } + } + } +} + +export class TreeListData { + public readonly list: TreeListItem[]; + + private _items: TreeListItem[]; + + public get items(): TreeListItem[] { + return this._items; + } + + constructor(list: TreeListItem[]) { + this._items = []; + this.list = list; + this.updateHidden(); + } + + public select(item: TreeListItem, selected: boolean) { + item.select(selected) + } + + public collapse(item: TreeListItem | null, collapsed: boolean) { + if (item) { + item.collapse(collapsed) + } + } + + public collapseAll(collapsed: boolean) { + for (const e of this.list) { + e.collapse(collapsed); + } + } + + public collapsePath(item: TreeListItem | null, collapsed: boolean) { + if (item) { + item.collapsePath(collapsed); + } + } + + public highlight(item: TreeListItem | null, highlighted: boolean) { + if (item) { + item.highlight(highlighted) + } + } + + public highlightAll(highlighted: boolean) { + for (const e of this.list) { + e.highlight(highlighted); + } + } + + public findOne(f: (item: T) => boolean): TreeListItem | null { + for (const item of this.list) { + if (f(item.data)) { + return item; + } + } + return null; + } + + public find(f: (item: T) => boolean): TreeListItem[] { + const result: TreeListItem[] = []; + for (const item of this.list) { + if (f(item.data)) { + result.push(item); + } + } + return result; + } + + public updateHidden() { + const list = this.list; + for (const item of list) { + item.updateHidden(); + } + this._items = list.filter((i) => !i.hidden); + } + + public setSearchRules(f: (item: T) => string[]) { + for (let index = this.list.length - 1; index > -1; index--) { + const item = this.list[index]; + item.setSearchRules(f); + } + } + + public searchItems(text: string, ruleIndex: number): void { + if (text) { + for (const item of this.list) { + if (item.search[ruleIndex].includes(text)) { + item.searchHighlighted = 'highlighted'; + } else { + if (item.searchChildren[ruleIndex].includes(text)) { + item.searchHighlighted = 'sub'; + item.collapsed = false; + } else { + item.searchHighlighted = 'hidden'; + } + } + } + } else { + for (const item of this.list) { + item.searchHighlighted = ''; + } + } + } + + public static fromObject( + root: any, + field: string, + f?: (item: TreeListItem) => TreeListItem + ): TreeListData { + const list: TreeListItem[] = []; + const getItem = ( + data: any, + key: string, + parent: TreeListItem | null, + lvl: number + ): TreeListItem[] => { + const children: TreeListItem[] = [] + const arrayObjects: any[] = data[key]; + if (Array.isArray(arrayObjects)) { + for (const data of arrayObjects) { + let newItem = new TreeListItem(data, parent, lvl); + if (f) { + newItem = f(newItem); + } + list.push(newItem); + children.push(newItem); + const child = getItem(data, field, newItem, lvl + 1); + newItem.setChildren(child); + } + } + return children; + } + getItem(root, field, null, 0) + return new TreeListData(list); + } +} + +export class TreeListView { + private readonly _data: TreeListData; + private readonly _indexes: Set; + + private _selectedFields: TreeListItem[] = []; + private _selectedCount = 0; + private _selectedAllCount = 0; + private _selectedLimit = 0; + private _views: TreeListView[]; + private _search: string; + private _searchHighlighted: boolean; + + public get data(): TreeListData { + return this._data; + } + + public get selectedFields(): TreeListItem[] { + return this._selectedFields; + } + + public get selectedCount(): number { + return this._selectedCount; + } + + public get selectedAllCount(): number { + return this._selectedAllCount; + } + + public get items(): TreeListItem[] { + return this._data.items; + } + + public get search(): string { + return this._search; + } + + public get searchHighlighted(): boolean { + return this._searchHighlighted; + } + + constructor(data: TreeListData, indexes?: number[]) { + this._data = data; + this._views = []; + this._search = ''; + this._searchHighlighted = false; + if (indexes) { + this._indexes = new Set(indexes); + } else { + this._indexes = new Set(); + for (let index = 0; index < data.list.length; index++) { + this._indexes.add(index); + } + } + this.updateHidden(); + this.updateSelected(); + } + + public setSelectedLimit(limit: number) { + this._selectedLimit = limit + } + + public select(item: TreeListItem, selected: boolean) { + this._data.select(item, selected); + } + + public collapse(item: TreeListItem | null, collapsed: boolean) { + this._data.collapse(item, collapsed); + } + + public collapseAll(collapsed: boolean) { + this._data.collapseAll(collapsed); + } + + public collapsePath(item: TreeListItem | null, collapsed: boolean) { + this._data.collapsePath(item, collapsed); + } + + public highlight(item: TreeListItem | null, highlighted: boolean) { + this._data.highlight(item, highlighted); + } + + public highlightAll(highlighted: boolean) { + this._data.highlightAll(highlighted); + } + + public findOne(f: (item: T) => boolean): TreeListItem | null { + return this._data.findOne(f); + } + + public find(f: (item: T) => boolean): TreeListItem[] { + return this._data.find(f); + } + + public setSearchRules(f: (item: T) => string[]) { + this._data.setSearchRules(f); + } + + public updateSearch() { + this._search = ''; + for (const index of this._indexes) { + const item = this._data.list[index]; + if (item) { + this._search = this._search + item.search[0] + '|'; + } + } + } + + public searchItems(text: string, ruleIndex: number): void { + const value = (text || '').trim().toLocaleLowerCase(); + this._data.searchItems(value, ruleIndex); + } + + public searchView(text: string): void { + const value = (text || '').trim().toLocaleLowerCase(); + this._searchHighlighted = false; + if (value && this._search.includes(value)) { + this._searchHighlighted = true; + } + } + + public updateHidden() { + this._data.updateHidden(); + } + + public updateSelected() { + const list = this._data.list; + this._selectedAllCount = list.filter((item) => item.selected).length; + this._selectedFields = list.filter((item, index) => item.selected && this._indexes.has(index)); + this._selectedCount = this._selectedFields.length; + if (this._selectedLimit) { + this._selectedFields = this._selectedFields.slice(0, this._selectedLimit); + } + for (const view of this._views) { + view.updateSelected(); + } + } + + public getSelected(): TreeListItem[] { + return this._data.list.filter((item) => item.selected); + } + + public createView(f: (item: TreeListItem) => boolean): TreeListView { + const indexes: number[] = []; + + for (let i = 0; i < this._data.list.length; i++) { + const item = this._data.list[i]; + if (f(item)) { + indexes.push(i); + } + } + const view = new TreeListView(this._data, indexes); + this._views.push(view); + return view; + } + + public static createView(data: TreeListData, f: (item: TreeListItem) => boolean): TreeListView { + const indexes: number[] = []; + + for (let i = 0; i < data.list.length; i++) { + const item = data.list[i]; + if (f(item)) { + indexes.push(i); + } + } + return new TreeListView(data, indexes); + } +} \ No newline at end of file diff --git a/indexer-frontend/src/app/services/entities.service.ts b/indexer-frontend/src/app/services/entities.service.ts index de65248277..7aca1e42d8 100644 --- a/indexer-frontend/src/app/services/entities.service.ts +++ b/indexer-frontend/src/app/services/entities.service.ts @@ -40,6 +40,9 @@ import { StatisticDetails, Label, LabelDetails, + Formula, + FormulaDetails, + FormulaRelationships, } from '@indexer/interfaces'; /** @@ -393,6 +396,31 @@ export class EntitiesService { //#endregion //#endregion + //#region FORMULAS + public getFormulas(filters: PageFilters): Observable> { + const entity = 'formulas'; + const options = ApiUtils.getOptions(filters); + return this.http.get>( + `${this.url}/${entity}`, + options + ) as any; + } + + public getFormula(messageId: string): Observable { + const entity = 'formulas'; + return this.http.get( + `${this.url}/${entity}/${messageId}` + ) as any; + } + + public getFormulaRelationships(messageId: string): Observable { + const entity = 'formulas'; + return this.http.get( + `${this.url}/${entity}/${messageId}/relationships` + ) as any; + } + //#endregion + public updateFiles(messageId: string): Observable { return this.http.post(`${this.url}/update-files`, { messageId diff --git a/indexer-frontend/src/app/themes/guardian/accordion.scss b/indexer-frontend/src/app/themes/guardian/accordion.scss new file mode 100644 index 0000000000..cd6c68cee8 --- /dev/null +++ b/indexer-frontend/src/app/themes/guardian/accordion.scss @@ -0,0 +1,46 @@ +.guardian-accordion { + .p-accordion-header-link { + background: transparent; + position: relative; + font-family: var(--font-family-inter); + font-size: 16px; + font-weight: 600; + line-height: 20px; + text-align: left; + color: var(--guardian-font-color, #23252E); + outline: none; + box-shadow: none; + + &[aria-expanded="false"] { + .p-accordion-toggle-icon { + transform: rotate(90deg); + } + } + + &[aria-expanded="true"] { + .p-accordion-toggle-icon { + transform: rotate(180deg); + } + } + + .p-accordion-toggle-icon { + position: absolute; + right: 10px; + } + } + + .p-accordion p-accordiontab:first-child .p-accordion-header .p-accordion-header-link { + border-top-right-radius: 8px; + border-top-left-radius: 8px; + } + + .p-accordion p-accordiontab:last-child .p-accordion-header:not(.p-highlight) .p-accordion-header-link { + border-bottom-right-radius: 8px; + border-bottom-left-radius: 8px; + } + + .p-accordion p-accordiontab:last-child .p-accordion-content { + border-bottom-right-radius: 8px; + border-bottom-left-radius: 8px; + } +} \ No newline at end of file diff --git a/indexer-frontend/src/app/themes/guardian/button.scss b/indexer-frontend/src/app/themes/guardian/button.scss new file mode 100644 index 0000000000..43f90903ab --- /dev/null +++ b/indexer-frontend/src/app/themes/guardian/button.scss @@ -0,0 +1,116 @@ +.guardian-button { + display: flex; + border-radius: 6px; + font-family: var(--guardian-font-family); + transition: all 0.25s ease-in-out; + white-space: nowrap; + user-select: none; + justify-content: center; + align-items: center; + margin: 0; + display: inline-flex; + overflow: hidden; + position: relative; + box-sizing: border-box; + cursor: pointer; + border: 1px solid transparent; + text-overflow: ellipsis; + outline: none !important; + + &:hover { + filter: brightness(0.95); + } + + &-primary { + color: var(--guardian-secondary-color); + background-color: var(--guardian-primary-color); + border: 1px solid var(--guardian-primary-color); + } + + &-secondary { + color: var(--guardian-primary-color); + background-color: var(--guardian-secondary-color); + border: 1px solid var(--guardian-primary-color); + } + + &-delete { + color: var(--guardian-delete-color); + background-color: var(--guardian-secondary-color); + border: 1px solid var(--guardian-delete-color); + } + + &-success { + color: var(--guardian-success-color); + background-color: var(--guardian-success-background); + border: 1px solid var(--guardian-success-color); + } + + .guardian-button-icon { + width: auto; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + pointer-events: none; + } + + .guardian-button-icon+.guardian-button-label { + padding-left: 8px; + pointer-events: none; + } + + .guardian-button-label+.guardian-button-icon { + margin-right: 8px; + pointer-events: none; + } +} + +button.guardian-button[disabled], +div.guardian-button[disabled="true"] { + cursor: default !important; + + color: var(--guardian-disabled-color) !important; + background-color: var(--guardian-disabled-background) !important; + border: 1px solid var(--guardian-disabled-color) !important; + filter: none !important; +} + +.guardian-icon-button { + display: flex; + border-radius: 4px; + transition: all 0.25s ease-in-out; + white-space: nowrap; + user-select: none; + justify-content: center; + align-items: center; + margin: 0; + display: inline-flex; + overflow: hidden; + position: relative; + box-sizing: border-box; + cursor: pointer; + border: none; + background-color: transparent; + + &:hover { + background-color: var(--guardian-primary-background); + } + + &.big:hover { + box-shadow: 0px 0px 0px 6px var(--guardian-primary-background); + } + + &-secondary { + background-color: var(--guardian-secondary-color); + border: 1px solid var(--guardian-primary-color); + } +} + +button.guardian-icon-button[disabled], +div.guardian-icon-button[disabled="true"] { + cursor: default !important; + background-color: transparent !important; + box-shadow: none !important; + filter: none !important; + pointer-events: none; +} \ No newline at end of file diff --git a/indexer-frontend/src/app/themes/guardian/checkbox.scss b/indexer-frontend/src/app/themes/guardian/checkbox.scss new file mode 100644 index 0000000000..4362f27d6e --- /dev/null +++ b/indexer-frontend/src/app/themes/guardian/checkbox.scss @@ -0,0 +1,114 @@ +.guardian-checkbox { + .p-checkbox:not(.p-checkbox-disabled) .p-checkbox-box.p-focus { + outline: 0 none; + outline-offset: 0; + box-shadow: none; + border-color: var(--guardian-primary-color, #4169E2); + } + + .p-checkbox { + .p-checkbox-box { + border: 2px solid var(--guardian-primary-color, #4169E2); + border-radius: 2px; + + &.p-highlight { + border-color: var(--guardian-primary-color, #4169E2); + background: var(--guardian-primary-color, #4169E2); + } + } + } + + .p-checkbox .p-checkbox-box:not(.p-disabled):not(.p-highlight):hover { + border-color: var(--guardian-primary-color, #4169E2); + } + + &.checkbox-24 { + .p-checkbox { + width: 24px; + height: 24px; + line-height: 24px; + padding: 4px; + + .p-checkbox-box { + width: 16px; + height: 16px; + + .p-checkbox-icon { + font-size: 10px; + } + } + } + } +} + +.guardian-radio-button { + .p-disabled, .p-component:disabled { + opacity: 1; + filter: grayscale(1); + } + + .p-radiobutton:not(.p-radiobutton-disabled) .p-radiobutton-box.p-focus { + outline: 0 none; + outline-offset: 0; + box-shadow: none; + border-color: var(--guardian-primary-color, #4169E2); + } + + .p-radiobutton .p-radiobutton-box:not(.p-disabled):not(.p-highlight):hover { + border-color: var(--guardian-primary-color, #4169E2); + } + + .p-radiobutton { + .p-radiobutton-box { + border-color: var(--guardian-primary-color, #4169E2); + background: #ffffff; + + .p-radiobutton-icon { + background: var(--guardian-primary-color, #4169E2); + box-shadow: 0px 0px 0px 2px #ffffff; + } + + &.p-highlight { + background: var(--guardian-primary-color, #4169E2); + } + } + } + + &.radio-button-24 { + .p-radiobutton { + width: 24px; + height: 24px; + line-height: 24px; + padding: 4px; + + .p-radiobutton-box { + width: 16px; + height: 16px; + + .p-radiobutton-icon { + width: 8px; + height: 8px; + } + } + } + } + + &.radio-button-32 { + .p-radiobutton { + width: 32px; + height: 32px; + line-height: 32px; + padding: 4px; + + .p-radiobutton-box { + width: 16px; + height: 16px; + + .p-radiobutton-icon { + width: 8px; + height: 8px; + } + } + } + } +} \ No newline at end of file diff --git a/indexer-frontend/src/app/themes/guardian/collapse.scss b/indexer-frontend/src/app/themes/guardian/collapse.scss new file mode 100644 index 0000000000..8308c77f7f --- /dev/null +++ b/indexer-frontend/src/app/themes/guardian/collapse.scss @@ -0,0 +1,44 @@ +.guardian-collapse { + position: relative; + cursor: pointer; + + &::before { + content: ""; + display: block; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + border: 6px solid var(--guardian-primary-color, #4169E2); + width: 0px; + height: 0px; + pointer-events: none; + } + + &[collapsed="true"] { + &::before { + border-top-color: transparent; + border-right-color: transparent; + border-bottom-color: transparent; + border-right-width: 0; + } + } + + &[collapsed="false"] { + &::before { + border-left-color: transparent; + border-right-color: transparent; + border-bottom-color: transparent; + border-bottom-width: 0; + } + } + + &.collapse-24 { + width: 24px; + height: 24px; + + &::before { + border-width: 6; + } + } +} \ No newline at end of file diff --git a/indexer-frontend/src/app/themes/guardian/datepicker.scss b/indexer-frontend/src/app/themes/guardian/datepicker.scss new file mode 100644 index 0000000000..75d66da50d --- /dev/null +++ b/indexer-frontend/src/app/themes/guardian/datepicker.scss @@ -0,0 +1,41 @@ +.guardian-datepicker { + + .p-calendar { + width: 100%; + + .p-datepicker { + width: auto; + min-width: auto; + } + + .p-inputtext { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + } + + .p-datepicker-trigger { + background: #fff; + color: #727374; + border-color: var(--guardian-grey-color-2, #E1E7EF); + border-left: none; + border-radius: 6px; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + + &:hover { + background: #fff; + color: #727374; + border-color: var(--guardian-primary-color, #4169E2); + box-shadow: -1px 0px 0px 0px var(--guardian-primary-color, #4169E2); + } + } + + .p-inputtext:enabled:focus+.p-datepicker-trigger { + border-color: var(--guardian-primary-color, #4169E2); + } +} + +.p-datepicker:not(.p-datepicker-inline) { + min-width: 200px !important; +} \ No newline at end of file diff --git a/indexer-frontend/src/app/themes/guardian/dialog.scss b/indexer-frontend/src/app/themes/guardian/dialog.scss new file mode 100644 index 0000000000..d4fb19334a --- /dev/null +++ b/indexer-frontend/src/app/themes/guardian/dialog.scss @@ -0,0 +1,127 @@ +.guardian-dialog { + border-radius: 16px; + + .p-dialog-content { + border-top-left-radius: 16px !important; + border-top-right-radius: 16px !important; + border-bottom-left-radius: 16px !important; + border-bottom-right-radius: 16px !important; + padding: 0 32px 32px 32px !important; + } + + .p-dialog-title { + font-family: var(--guardian-font-family); + font-size: 24px; + font-style: normal; + font-weight: 600; + } + + .p-dialog { + box-shadow: none; + } + + .p-dialog-header { + border-top-left-radius: 16px !important; + border-top-right-radius: 16px !important; + } + + .dialog-header { + height: 88px; + display: flex; + width: 100%; + position: relative; + padding: 32px 0 24px 0; + color: var(--guardian-header-color); + + .header-container { + width: 100%; + position: relative; + flex: 1; + display: flex; + flex-direction: row; + + .header-text { + font-family: var(--guardian-font-family); + font-size: var(--guardian-header-5-font-size); + font-style: var(--guardian-font-style); + font-weight: 600; + line-height: 32px; + height: 32px; + } + } + + .header-icon { + width: 32px; + min-width: 32px; + max-width: 32px; + position: relative; + flex: 32px; + padding: 4px; + border-radius: 100%; + cursor: pointer; + color: var(--guardian-close-color); + background-color: var(--guardian-secondary-color); + + &:hover { + filter: brightness(0.95); + } + } + } + + .dialog-body { + overflow-y: auto; + } + + .dialog-footer { + border-top: 1px solid var(--guardian-border-color); + padding-top: 20px; + + .action-buttons { + display: flex; + justify-content: flex-end; + } + + .dialog-button { + margin-left: 15px; + margin-right: 0px; + + button { + min-width: 100px; + height: 40px; + font-size: var(--guardian-primary-font-size); + font-weight: 500; + } + } + } +} + +@media (max-width: 810px) { + .guardian-dialog { + width: 100vw !important; + height: 100vh !important; + min-width: 100vw !important; + min-height: 100vh !important; + max-width: 100vw !important; + max-height: 100vh !important; + border-top-left-radius: 0px !important; + border-top-right-radius: 0px !important; + border-bottom-left-radius: 0px !important; + border-bottom-right-radius: 0px !important; + overflow: hidden; + + .p-dialog-content { + border-top-left-radius: 0px !important; + border-top-right-radius: 0px !important; + border-bottom-left-radius: 0px !important; + border-bottom-right-radius: 0px !important; + padding: 0 24px 24px 24px !important; + overflow: hidden; + } + + .dialog-body { + min-height: calc(100vh - 175px) !important; + height: calc(100vh - 175px) !important; + max-height: calc(100vh - 175px) !important; + } + } +} \ No newline at end of file diff --git a/indexer-frontend/src/app/themes/guardian/dropdown.scss b/indexer-frontend/src/app/themes/guardian/dropdown.scss new file mode 100644 index 0000000000..0a0a9ecd6a --- /dev/null +++ b/indexer-frontend/src/app/themes/guardian/dropdown.scss @@ -0,0 +1,163 @@ +.guardian-dropdown { + height: 40px; + + .p-dropdown { + background: #ffffff; + border: 1px solid var(--guardian-grey-color-2, #E1E7EF); + border-radius: 8px; + outline: 0 none; + height: 40px; + width: 100%; + + &:not(.p-disabled):hover { + border-color: var(--guardian-primary-color, #4169E2); + } + + &:not(.p-disabled).p-focus { + box-shadow: none; + border-color: var(--guardian-primary-color, #4169E2); + } + + .p-dropdown-label { + padding: 8px 16px; + } + + .p-dropdown-trigger { + .p-dropdown-trigger-icon { + font-size: 12px; + color: var(--guardian-font-color, #23252E); + } + } + + .p-inputtext { + font-family: var(--font-family-inter); + font-size: 14px; + font-weight: 400; + padding: 0px 16px; + color: #000; + // display: flex; + // justify-content: center; + margin: auto 0; + } + } + + &.full-size { + width: 100%; + height: 100%; + + .p-dropdown { + width: 100%; + height: 100%; + } + } + + &.vertical-align { + display: block; + position: relative; + top: 1px; + line-height: normal; + @include vertical-align(middle); + } + + .guardian-dropdown-selected { + text-overflow: ellipsis; + display: block; + white-space: nowrap; + overflow: hidden; + + .guardian-dropdown-label { + font-family: var(--font-family-inter); + font-size: 14px; + font-weight: 400; + line-height: 16px; + text-align: left; + color: #848FA9; + padding-right: 8px; + } + + .guardian-dropdown-item { + font-family: var(--font-family-inter); + font-size: 14px; + line-height: 16px; + text-align: left; + } + } + + .guardian-dropdown-item {} + + &.guardian-dropdown-cell { + width: 100%; + + .p-dropdown { + border: none !important; + } + } +} + +.guardian-dropdown-panel { + border: 1px solid var(--guardian-grey-color-2, #E1E7EF); + border-radius: 8px; + + .p-dropdown-item { + height: 40px; + padding: 12px 16px !important; + font-family: var(--font-family-inter); + font-size: 14px; + line-height: 16px; + text-align: left; + } + + .p-dropdown-item.p-highlight { + position: relative; + background: none !important; + + &::before { + content: ""; + display: block; + position: absolute; + background: var(--guardian-primary-color, #4169E2); + opacity: 0.16; + left: 0; + top: 0; + right: 0; + bottom: 0; + pointer-events: none; + } + } + + .p-dropdown-item:not(.p-highlight):not(.p-disabled):hover { + position: relative; + background: none !important; + + &::before { + content: ""; + display: block; + position: absolute; + background: var(--guardian-primary-color, #4169E2); + opacity: 0.08; + left: 0; + top: 0; + right: 0; + bottom: 0; + pointer-events: none; + } + } + + .p-dropdown-item:not(.p-highlight):not(.p-disabled).p-focus { + position: relative; + background: none !important; + + &::before { + content: ""; + display: block; + position: absolute; + background: var(--guardian-primary-color, #4169E2); + opacity: 0.08; + left: 0; + top: 0; + right: 0; + bottom: 0; + pointer-events: none; + } + } +} \ No newline at end of file diff --git a/indexer-frontend/src/app/themes/guardian/formula.scss b/indexer-frontend/src/app/themes/guardian/formula.scss new file mode 100644 index 0000000000..5718f6d6b5 --- /dev/null +++ b/indexer-frontend/src/app/themes/guardian/formula.scss @@ -0,0 +1,80 @@ +.guardian-formula-codemirror { + background: #ffffff; + border: 1px solid var(--guardian-grey-color-2, #E1E7EF); + border-radius: 8px; + outline: 0 none; + height: 40px; + overflow: hidden; + cursor: text; + padding: 0px 12px; + width: 100%; + + ngx-codemirror { + width: 100%; + + &::ng-deep { + .CodeMirror { + width: 100%; + height: 38px !important; + } + + .CodeMirror-sizer { + height: 38px; + } + + .CodeMirror-lines { + height: 38px; + padding: 8px 0px; + } + + .CodeMirror-cursor { + height: 20px !important; + } + + .CodeMirror-gutters { + display: none; + } + + .cm-number { + color: #15ab3f; + font-weight: 600; + } + + .cm-formula-variable { + color: #4267df; + font-weight: 600; + } + + .cm-formula-operation { + color: #8100b3; + font-weight: 600; + } + + .cm-formula-function { + color: #FF9800; + font-weight: 600; + } + + .CodeMirror pre.CodeMirror-placeholder { + color: #848FA9; + } + + .cm-string { + color: #a11; + } + } + } +} + +.guardian-formula-text { + color: #a11 !important; +} + +.guardian-formula-number { + color: #00A12D !important; + font-weight: 600; +} + +.CodeMirror-hints { + z-index: 1200; +} \ No newline at end of file diff --git a/indexer-frontend/src/app/themes/guardian/grid.scss b/indexer-frontend/src/app/themes/guardian/grid.scss new file mode 100644 index 0000000000..019578961e --- /dev/null +++ b/indexer-frontend/src/app/themes/guardian/grid.scss @@ -0,0 +1,397 @@ +$sizes: ( + 44: 44px, + 56: 56px, + 64: 64px, + 80: 80px, + 90: 90px, + 100: 100px, + 110: 110px, + 120: 120px, + 125: 125px, + 130: 130px, + 135: 135px, + 140: 140px, + 150: 150px, + 160: 160px, + 170: 170px, + 180: 180px, + 190: 190px, + 200: 200px, + 210: 210px, + 220: 220px, + 250: 250px +); + +.dialog-grid-container { + display: flex; + flex-direction: column; + width: 100%; + overflow: auto; + + .col-text { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .col-button { + padding: 0px 6px !important; + } + + .col-auto { + width: 100%; + } + + @each $name, $size in $sizes { + .col-#{$name} { + width: $size; + min-width: $size; + max-width: $size; + } + } + + .dialog-grid-header { + display: flex; + flex-direction: row; + width: 100%; + height: 46px; + min-height: 46px; + max-height: 46px; + padding: 0px 12px; + color: var(--guardian-grid-color); + + &>div { + font-size: var(--guardian-small-font-size); + text-transform: uppercase; + padding: 12px 8px; + } + } + + .dialog-grid-body { + display: flex; + flex-direction: column; + width: 100%; + border: 1px solid var(--guardian-border-color); + border-radius: 6px; + + &.vertical-border { + .dialog-grid-row { + &>div { + border-right: 1px solid var(--guardian-border-color); + + &:last-child { + border-right: none; + } + } + } + } + + .dialog-grid-row { + display: flex; + flex-direction: row; + width: 100%; + border-bottom: 1px solid var(--guardian-border-color); + cursor: default; + padding: 0px 12px; + font-size: var(--guardian-primary-font-size); + color: var(--guardian-font-color); + + &.row-52 { + height: 52px; + min-height: 52px; + max-height: 52px; + } + + + &.row-64 { + height: 64px; + min-height: 64px; + max-height: 64px; + } + + &>div { + padding: 0px 10px; + display: flex; + align-items: center; + position: relative; + + .cell-focus { + display: none; + position: absolute; + top: 0px; + left: 0px; + right: 0px; + bottom: 0px; + border: 1px solid var(--guardian-primary-color, #4169E2); + } + + .p-inputwrapper-focus+.cell-focus, + .guardian-input:focus+.cell-focus { + display: block; + } + + &:first-child { + .cell-focus { + left: -12px; + } + } + } + + &:first-child>div:first-child .cell-focus { + border-top-left-radius: 6px; + } + + &:last-child>div:first-child .cell-focus { + border-bottom-left-radius: 6px; + } + + &[expand="true"] { + border-bottom-color: transparent; + + .expand-icon { + transform: rotate(90deg); + } + } + + &:last-child { + border-bottom: none; + } + } + + .dialog-grid-expand-row { + width: 100%; + border-bottom: 1px solid var(--guardian-border-color); + padding: 0px 12px; + display: none; + + &[expand="true"] { + display: flex; + } + } + } + + .dialog-grid-empty { + font-family: var(--font-family-inter); + font-size: 14px; + font-weight: 400; + color: #848FA9; + height: 100px; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + } +} + +.guardian-grid-paginator { + .table-paginator { + display: flex; + align-items: center; + justify-content: flex-end; + column-gap: 24px; + padding: 0 16px 0 0; + color: var(--color-grey-black-2, #23252e); + font-family: var(--font-family-inter); + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 16px; + height: 64px !important; + border-radius: 0px !important; + border-bottom-left-radius: 8px !important; + border-bottom-right-radius: 8px !important; + background: #fff; + box-shadow: none !important; + position: relative; + + .p-dropdown { + border: 1px solid var(--guardian-grey-color-2, #E1E7EF); + border-radius: 8px; + outline: 0 none; + } + + .p-dropdown:not(.p-disabled):hover { + border-color: var(--guardian-primary-color, #4169E2); + } + + .p-dropdown:not(.p-disabled).p-focus { + box-shadow: none; + border-color: var(--guardian-primary-color, #4169E2); + } + + .svg-btn:hover { + box-shadow: 0px 0px 0px 1px var(--guardian-primary-color, #4169E2); + } + } +} + +.guardian-grid-container { + position: relative; + + &::before { + content: ""; + display: block; + position: absolute; + left: -1px; + right: -1px; + top: 47px; + bottom: -1px; + pointer-events: none; + border: 1px solid var(--guardian-shadow, #00000014); + border-radius: 8px; + box-shadow: 0px 4px 8px 0px var(--guardian-shadow, #00000014); + z-index: 2; + } + + .p-datatable-thead { + background: transparent !important; + background-color: transparent !important; + } + + .guardian-grid-header { + height: 46px; + + th { + font-family: var(--font-family-inter); + font-size: 12px; + font-weight: 400; + line-height: 15px; + text-align: left; + color: var(--guardian-grid-color, #848FA9); + text-transform: uppercase; + padding: 16px; + border: none; + background: transparent; + background-color: transparent; + position: relative; + + &:first-child { + padding-left: 24px; + + .text-truncate { + left: 24px; + } + } + + &:last-child { + padding-left: 24px; + } + } + + .text-truncate { + position: absolute; + left: 16px; + top: 50%; + right: 16px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + transform: translate(0px, -50%); + } + } + + .guardian-grid-row { + height: 64px; + + td { + padding: 16px; + border-bottom: 1px solid #e9ecef; + position: relative; + + &:first-child { + padding-left: 24px; + + .text-truncate { + left: 24px; + } + } + + &:last-child { + padding-right: 24px; + } + } + + &:first-child { + border-top-left-radius: 8px; + border-top-right-radius: 8px; + } + + .text-truncate { + position: absolute; + left: 16px; + top: 50%; + right: 16px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + transform: translate(0px, -50%); + } + } + + // .p-datatable-tbody { + // border: 1px solid #e9ecef; + // border-radius: 6px; + // } + + @each $name, $size in $sizes { + .col-#{$name} { + width: $size; + min-width: $size; + max-width: $size; + } + } + + @each $name, $size in $sizes { + .col-min-#{$name} { + min-width: $size; + } + } + + .col-auto { + width: auto; + } + + &.grid-scroll { + &::before { + z-index: 1; + border-top: none; + border-top-right-radius: 0px; + } + + &::after { + content: ""; + display: block; + position: absolute; + left: -1px; + right: 9px; + top: 45px; + bottom: 0px; + pointer-events: none; + border-top: 1px solid #ebebeb; + border-radius: 8px; + z-index: 1; + border-bottom-left-radius: 0px; + border-bottom-right-radius: 0px; + border-top-right-radius: 0px; + } + + .p-datatable-tbody { + border-radius: 8px; + } + + .p-datatable-wrapper { + padding-right: 2px; + } + + .p-datatable-thead { + background: #fff !important; + background-color: #fff !important; + } + } + + .p-frozen-column { + position: sticky !important; + background: #fff !important; + background-color: #fff !important; + } +} \ No newline at end of file diff --git a/indexer-frontend/src/app/themes/guardian/helpers.scss b/indexer-frontend/src/app/themes/guardian/helpers.scss new file mode 100644 index 0000000000..96404a52be --- /dev/null +++ b/indexer-frontend/src/app/themes/guardian/helpers.scss @@ -0,0 +1,17 @@ +@mixin vertical-align($align: middle) { + &:before { + content: ""; + display: inline-block; + height: 100%; + vertical-align: $align; + // you can add font-size 0 here and restore in the children to prevent + // the inline-block white-space to mess the width of your elements + font-size: 0; + } + + &>* { + vertical-align: $align; + // although you need to know the font-size, because "inherit" is 0 + font-size: 14px; + } +} \ No newline at end of file diff --git a/indexer-frontend/src/app/themes/guardian/icon.scss b/indexer-frontend/src/app/themes/guardian/icon.scss new file mode 100644 index 0000000000..e1155e7491 --- /dev/null +++ b/indexer-frontend/src/app/themes/guardian/icon.scss @@ -0,0 +1,66 @@ +.icon-color { + &-primary { + fill: var(--guardian-primary-color); + color: var(--guardian-primary-color); + } + + &-secondary { + fill: var(--guardian-secondary-color); + color: var(--guardian-secondary-color); + } + + &-disabled { + fill: var(--guardian-disabled-icon); + color: var(--guardian-disabled-icon); + } + + &-delete { + fill: var(--guardian-delete-color); + color: var(--guardian-delete-color); + } + + &-success { + fill: var(--guardian-success-color); + color: var(--guardian-success-color); + } + + &-failure { + fill: var(--guardian-failure-color); + color: var(--guardian-failure-color); + } + + &-warning { + fill: var(--guardian-warning-color); + color: var(--guardian-warning-color); + } + + &-close { + fill: var(--guardian-close-color); + color: var(--guardian-close-color); + } + + &-font { + fill: var(--guardian-font-color); + color: var(--guardian-font-color); + } + + &-constant { + fill: var(--formula-constant-color); + color: var(--formula-constant-color); + } + + &-variable { + fill: var(--formula-variable-color); + color: var(--formula-variable-color); + } + + &-function { + fill: var(--formula-function-color); + color: var(--formula-function-color); + } + + &-text { + fill: var(--formula-text-color); + color: var(--formula-text-color); + } +} \ No newline at end of file diff --git a/indexer-frontend/src/app/themes/guardian/index.scss b/indexer-frontend/src/app/themes/guardian/index.scss new file mode 100644 index 0000000000..45987dd44f --- /dev/null +++ b/indexer-frontend/src/app/themes/guardian/index.scss @@ -0,0 +1,27 @@ +::ng-deep body { + @import 'variables.scss'; + @import 'helpers.scss'; + @import 'icon.scss'; + @import 'button.scss'; + @import 'grid.scss'; + @import 'dialog.scss'; + @import 'progress.scss'; + @import 'label.scss'; + @import 'page.scss'; + @import 'input.scss'; + @import 'dropdown.scss'; + @import 'tabs.scss'; + @import 'checkbox.scss'; + @import 'collapse.scss'; + @import 'separator.scss'; + @import 'multi-select.scss'; + @import 'scroll.scss'; + @import 'menu.scss'; + @import 'formula.scss'; + @import 'datepicker.scss'; + @import 'textarea.scss'; + @import 'select-button.scss'; + @import 'accordion.scss'; + @import 'tree.scss'; + @import 'menu-button.scss'; +} \ No newline at end of file diff --git a/indexer-frontend/src/app/themes/guardian/input.scss b/indexer-frontend/src/app/themes/guardian/input.scss new file mode 100644 index 0000000000..d3f60f2c28 --- /dev/null +++ b/indexer-frontend/src/app/themes/guardian/input.scss @@ -0,0 +1,93 @@ +.guardian-input-container { + display: flex; + flex-direction: column; + height: 60px; + width: 100%; + + label { + font-family: var(--font-family-inter); + font-size: 12px; + font-weight: 500; + line-height: 14px; + text-align: left; + margin-bottom: 6px; + } + + input { + height: 40px; + border-radius: 8px; + outline: none; + border: 1px solid var(--guardian-grey-color-2, #E1E7EF); + font-family: var(--font-family-inter); + font-size: 14px; + font-weight: 400; + color: #000; + padding: 8px 16px; + + &:enabled:hover { + border-color: var(--guardian-primary-color, #4169E2); + } + + &:enabled:focus { + border-color: var(--guardian-primary-color, #4169E2); + box-shadow: none; + } + } + + .p-dropdown { + width: 100%; + } +} + +.guardian-input { + width: 100%; + height: 40px; + border-radius: 8px; + outline: none; + border: 1px solid var(--guardian-grey-color-2, #E1E7EF); + padding: 8px 8px; + box-shadow: none; + font-family: var(--font-family-inter); + font-size: 14px; + font-weight: 400; + color: #000; + + &:enabled:hover { + border-color: var(--guardian-primary-color, #4169E2); + } + + &:enabled:focus { + border-color: var(--guardian-primary-color, #4169E2); + box-shadow: none; + } + + &.guardian-input-cell { + width: 100%; + border: none !important; + padding: 8px 0px; + height: 100%; + } +} + +.guardian-search { + display: flex; + flex-direction: column; + height: 40px; + width: 100%; + + input { + height: 40px; + border-radius: 8px; + outline: none; + border: 1px solid var(--guardian-grey-color-2, #E1E7EF); + + &:enabled:hover { + border-color: var(--guardian-primary-color, #4169E2); + } + + &:enabled:focus { + border-color: var(--guardian-primary-color, #4169E2); + box-shadow: none; + } + } +} \ No newline at end of file diff --git a/indexer-frontend/src/app/themes/guardian/label.scss b/indexer-frontend/src/app/themes/guardian/label.scss new file mode 100644 index 0000000000..19ee41dce7 --- /dev/null +++ b/indexer-frontend/src/app/themes/guardian/label.scss @@ -0,0 +1,36 @@ +.guardian-label { + display: flex; + border-radius: 6px; + font-family: var(--guardian-font-family); + transition: all 0.25s ease-in-out; + white-space: nowrap; + user-select: none; + justify-content: center; + align-items: center; + margin: 0; + display: inline-flex; + overflow: hidden; + position: relative; + box-sizing: border-box; + text-overflow: ellipsis; + + &-primary { + color: var(--guardian-secondary-color); + background-color: var(--guardian-primary-color); + } + + &-secondary { + color: var(--guardian-primary-color); + background-color: var(--guardian-primary-background); + } + + &-delete { + color: var(--guardian-delete-color); + background-color: var(--guardian-delete-background); + } + + &-disabled { + color: var(--guardian-disabled-color); + background-color: var(--guardian-disabled-color); + } +} \ No newline at end of file diff --git a/indexer-frontend/src/app/themes/guardian/menu-button.scss b/indexer-frontend/src/app/themes/guardian/menu-button.scss new file mode 100644 index 0000000000..a4bbfe9a0b --- /dev/null +++ b/indexer-frontend/src/app/themes/guardian/menu-button.scss @@ -0,0 +1,148 @@ +.guardian-menu-container { + width: 100%; + height: 70px; + min-height: 70px; + overflow: hidden; + background: #fff; + border-bottom: 1px solid #E1E7EF; + display: flex; + flex-direction: row; + padding-left: 12px; +} + +.guardian-menu-label, +.guardian-menu-button { + width: 70px; + height: 70px; + min-width: 70px; + min-height: 70px; + overflow: hidden; + display: flex; + flex-direction: column; + cursor: pointer; + justify-content: center; + align-items: center; + position: relative; + + &>* { + pointer-events: none; + } + + &.guardian-menu-select { + width: 86px; + min-width: 86px; + padding-right: 16px; + + .guardian-menu-select-icon { + position: absolute; + top: 0; + bottom: 0; + right: 0; + width: 16px; + background: #e1e6fa; + + &::before { + content: ""; + display: block; + position: absolute; + width: 5px; + height: 5px; + left: 4px; + top: 28px; + border-left: 2px solid var(--primary-primary, #4169E2); + border-bottom: 2px solid var(--primary-primary, #4169E2); + pointer-events: none; + transform: rotate(-45deg); + } + } + } + + &:hover { + background: #f0f3fc; + + .guardian-menu-select-icon { + background: #dde3f6; + } + } + + &[open="true"] { + background: #e1e7fa !important; + + .guardian-menu-select-icon { + background: #c4d0f3 !important; + } + } + + div { + font-family: var(--font-family-inter); + font-size: 12px; + font-weight: 500; + line-height: 24px; + color: var(--guardian-primary-color); + } + + svg-icon svg { + fill: var(--guardian-primary-color); + color: var(--guardian-primary-color); + } + + &[disabled="true"] { + pointer-events: none; + + div { + color: var(--guardian-disabled-icon); + } + + svg-icon svg { + fill: var(--guardian-disabled-icon); + color: var(--guardian-disabled-icon); + } + } + + &.success-color { + div { + color: var(--guardian-success-color); + } + + svg-icon svg { + fill: var(--guardian-success-color); + color: var(--guardian-success-color); + } + } +} + +.guardian-menu-label { + pointer-events: none; +} + +.guardian-menu-separator { + width: 5px; + height: 70px; + position: relative; + + &::before { + content: ""; + display: block; + position: absolute; + left: 2px; + top: 0; + bottom: 0; + width: 3px; + border-left: 1px solid #E1E7EF; + pointer-events: none; + } + + &.guardian-menu-separator-last { + &::before { + left: 0px; + } + } +} + +.guardian-menu-popup { + width: 70px; + + .p-menuitem-content { + background: #fff !important; + } +} \ No newline at end of file diff --git a/indexer-frontend/src/app/themes/guardian/menu.scss b/indexer-frontend/src/app/themes/guardian/menu.scss new file mode 100644 index 0000000000..a6db5b3405 --- /dev/null +++ b/indexer-frontend/src/app/themes/guardian/menu.scss @@ -0,0 +1,15 @@ +.guardian-menu { + margin-top: 6px; + + &.p-overlaypanel:after { + display: none; + } + + &.p-overlaypanel::before { + display: none; + } + + .p-overlaypanel-content { + padding: 0px; + } +} \ No newline at end of file diff --git a/indexer-frontend/src/app/themes/guardian/multi-select.scss b/indexer-frontend/src/app/themes/guardian/multi-select.scss new file mode 100644 index 0000000000..50feb61800 --- /dev/null +++ b/indexer-frontend/src/app/themes/guardian/multi-select.scss @@ -0,0 +1,162 @@ +.guardian-multiselect { + height: 40px; + + .p-multiselect { + background: #ffffff; + border: 1px solid var(--guardian-grey-color-2, #E1E7EF); + border-radius: 8px; + outline: 0 none; + height: 40px; + width: 100%; + + &:not(.p-disabled):hover { + border-color: var(--guardian-primary-color, #4169E2); + } + + &:not(.p-disabled).p-focus { + box-shadow: none; + border-color: var(--guardian-primary-color, #4169E2); + } + + .p-multiselect-label { + padding: 8px 16px; + } + + .p-multiselect-trigger { + .p-multiselect-trigger-icon { + font-size: 12px; + color: var(--guardian-font-color, #23252E); + } + } + + .p-inputtext { + font-family: var(--font-family-inter); + font-size: 14px; + font-weight: 400; + padding: 0px 16px; + display: flex; + color: #000; + align-items: center; + } + } + + &.full-size { + width: 100%; + height: 100%; + + .p-multiselect { + width: 100%; + height: 100%; + } + } + + .guardian-multiselect-selected { + text-overflow: ellipsis; + display: block; + white-space: nowrap; + overflow: hidden; + + .guardian-multiselect-label { + font-family: var(--font-family-inter); + font-size: 14px; + font-weight: 400; + line-height: 16px; + text-align: left; + color: #848FA9; + padding-right: 8px; + } + + .guardian-multiselect-item { + font-family: var(--font-family-inter); + font-size: 14px; + line-height: 16px; + text-align: left; + } + } + + .guardian-multiselect-item {} + + &.guardian-multiselect-cell { + width: 100%; + + .p-multiselect { + border: none !important; + } + } +} + +.guardian-multiselect-panel { + border: 1px solid var(--guardian-grey-color-2, #E1E7EF); + border-radius: 8px; + + .p-inputtext:enabled:focus { + outline: 0 none; + outline-offset: 0; + box-shadow: none; + border-color: var(--guardian-primary-color, #4169E2); + } + + .p-multiselect-item { + padding: 12px 16px !important; + font-family: var(--font-family-inter); + font-size: 14px; + line-height: 16px; + text-align: left; + } + + .p-multiselect-items .p-multiselect-item:focus { + outline: 0 none; + outline-offset: 0; + box-shadow: none; + } + + .p-multiselect-item.p-highlight { + position: relative; + background: none !important; + + &::before { + content: ""; + display: block; + position: absolute; + background: var(--guardian-primary-color, #4169E2); + opacity: 0.16; + left: 0; + top: 0; + right: 0; + bottom: 0; + pointer-events: none; + } + } + + .p-multiselect-item:not(.p-highlight):not(.p-disabled):hover { + position: relative; + background: none !important; + + &::before { + content: ""; + display: block; + position: absolute; + background: var(--guardian-primary-color, #4169E2); + opacity: 0.08; + left: 0; + top: 0; + right: 0; + bottom: 0; + pointer-events: none; + } + } + + .p-checkbox:not(.p-checkbox-disabled) .p-checkbox-box.p-highlight:hover { + border-color: var(--guardian-primary-color, #4169E2); + background: var(--guardian-primary-color, #4169E2); + color: #ffffff; + } + .p-checkbox:not(.p-checkbox-disabled) .p-checkbox-box:hover { + border-color: var(--guardian-primary-color, #4169E2); + } + + .p-checkbox .p-checkbox-box.p-highlight { + border-color: var(--guardian-primary-color, #4169E2); + background: var(--guardian-primary-color, #4169E2); + } +} \ No newline at end of file diff --git a/indexer-frontend/src/app/themes/guardian/page.scss b/indexer-frontend/src/app/themes/guardian/page.scss new file mode 100644 index 0000000000..a29fd48d19 --- /dev/null +++ b/indexer-frontend/src/app/themes/guardian/page.scss @@ -0,0 +1,130 @@ +.guardian-page { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + position: relative; + padding: 56px 48px 48px 48px; + + .guardian-user-not-registered { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + font-size: 20px; + color: darkgrey; + z-index: 2; + } + + .guardian-user-not-data { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + color: var(--color-grey-black-2); + font-family: var(--font-family-inter); + font-size: 20px; + font-style: normal; + font-weight: 400; + line-height: 16px; + display: flex; + flex-direction: column; + align-items: center; + row-gap: 8px; + justify-content: center; + + &__text { + color: var(--color-grey-5, #848fa9); + text-align: center; + font-family: var(--font-family-inter); + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: normal; + } + + &__text-strong { + color: var(--color-grey-5, #848fa9); + text-align: center; + font-family: var(--font-family-inter); + font-size: 16px; + font-style: normal; + font-weight: 600; + line-height: normal; + } + } + + .guardian-user-back-button { + height: 40px; + width: 170px; + + button { + height: 40px; + width: 100%; + padding-right: 16px; + font-size: 14px; + font-weight: 500; + } + } + + .guardian-user-page-header { + font-size: 32px; + font-weight: 600; + color: var(--guardian-header-color, #000); + height: 72px; + padding: 32px 0px 18px 0px; + position: relative; + } + + .guardian-user-page-toolbar { + height: 56px; + width: 100%; + display: flex; + flex-direction: row; + padding: 0px 0px 16px 0px; + justify-content: flex-end; + + button { + height: 40px; + padding: 0px 16px; + } + + .guardian-user-page-filters { + width: 100%; + display: flex; + flex-direction: row; + justify-content: flex-start; + } + + .guardian-user-page-buttons { + width: 100%; + display: flex; + flex-direction: row; + justify-content: flex-end; + + button { + margin-left: 12px; + } + } + } + + .guardian-user-page-grid { + height: 100%; + width: 100%; + position: relative; + } +} + +.guardian-user-back-button { + height: 40px; + min-width: 170px; + width: min-content; + + button { + height: 40px; + width: 100%; + padding-right: 18px; + font-size: 14px; + font-weight: 500; + } +} \ No newline at end of file diff --git a/indexer-frontend/src/app/themes/guardian/progress.scss b/indexer-frontend/src/app/themes/guardian/progress.scss new file mode 100644 index 0000000000..f8f410da7e --- /dev/null +++ b/indexer-frontend/src/app/themes/guardian/progress.scss @@ -0,0 +1,128 @@ +.guardian-loading { + background: var(--guardian-secondary-background); + position: absolute; + z-index: 99; + top: 0; + left: 0; + bottom: 0; + right: 0; + display: flex; + align-items: center; + justify-items: center; + justify-content: center; + align-content: center; + + .guardian-loading-image { + width: 56px; + height: 56px; + border-top: 3px solid var(--guardian-primary-color); + border-left: 3px solid transparent; + border-right: 3px solid transparent; + border-bottom: 3px solid transparent; + border-radius: 100%; + filter: brightness(100%); + animation-name: guardian-loading-animation; + animation-duration: 7.5s; + animation-iteration-count: infinite; + animation-timing-function: ease-in-out; + } + + @keyframes guardian-loading-animation { + 0% { + transform: rotate(0deg); + opacity: 1; + } + + 25% { + transform: rotate(360deg); + border-top: 3px solid transparent; + border-right: 3px solid var(--guardian-primary-color); + border-left: 3px solid transparent; + border-bottom: 3px solid transparent; + } + + 50% { + transform: rotate(720deg); + border-top: 3px solid transparent; + border-left: 3px solid transparent; + border-bottom: 3px solid var(--guardian-primary-color); + border-right: 3px solid transparent; + } + + 75% { + transform: rotate(1080deg); + border-top: 3px solid transparent; + border-bottom: 3px solid transparent; + border-right: 3px solid transparent; + border-left: 3px solid var(--guardian-primary-color); + } + + 100% { + transform: rotate(1440deg); + border-top: 3px solid var(--guardian-primary-color); + border-left: 3px solid transparent; + border-right: 3px solid transparent; + border-bottom: 3px solid transparent; + } + } +} + +.progress-bar { + background-color: var(--guardian-disabled-background); + border-radius: 8px; + margin: 1px; + overflow: hidden; + position: relative; + + .progress-bar-value { + background-color: var(--guardian-primary-color); + font-size: 12px; + font-style: normal; + display: flex; + justify-content: center; + height: inherit; + overflow: hidden; + width: 0%; + border-top-right-radius: 8px; + border-bottom-right-radius: 8px; + transition: width 1s ease-in-out; + + &::before { + content: ""; + background: linear-gradient(135deg, + transparent 25%, + rgba(255, 255, 255, 0.5) 50%, + rgba(255, 255, 255, 0.5) 50%, + transparent 75%); + position: absolute; + left: 0; + top: 0; + bottom: 0; + right: 0; + transform: translate(-100%, 0px); + animation-duration: 1.5s; + animation-fill-mode: forwards; + animation-iteration-count: infinite; + animation-name: progress-bar-animation; + animation-timing-function: linear; + } + } + + &.static-bar { + .progress-bar-value { + &::before { + display: none; + } + } + } + + @keyframes progress-bar-animation { + 0% { + transform: translate(-100%, 0px); + } + + 100% { + transform: translate(100%, 0px); + } + } +} \ No newline at end of file diff --git a/indexer-frontend/src/app/themes/guardian/scroll.scss b/indexer-frontend/src/app/themes/guardian/scroll.scss new file mode 100644 index 0000000000..928b5e4f26 --- /dev/null +++ b/indexer-frontend/src/app/themes/guardian/scroll.scss @@ -0,0 +1,48 @@ +* { + -webkit-overflow-scrolling: auto; +} + +*::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +*::-webkit-scrollbar-track { + /*margin: 54px 0 53px;*/ +} + +*::-webkit-scrollbar-thumb { + background: rgba(0, 0, 0, 0.3); + border-radius: 10px; + box-shadow: rgba(255, 255, 255, 0.3) 0 0 0 1px; +} + +*:hover::-webkit-scrollbar-thumb { + background: rgba(0, 0, 0, 0.45); +} + +*::-webkit-scrollbar-thumb:hover { + background: rgba(0, 0, 0, 0.55); +} + +@-moz-document url-prefix() { + * { + /* Firefox 63 compatibility */ + scrollbar-face-color: rgba(0, 0, 0, 0.3); + /* Firefox 63 compatibility */ + scrollbar-track-color: transparent; + scrollbar-color: rgba(0, 0, 0, 0.3) transparent !important; + scrollbar-width: thin !important; + } +} + +@supports (-moz-appearance:none) { + * { + /* Firefox 63 compatibility */ + scrollbar-face-color: rgba(0, 0, 0, 0.3); + /* Firefox 63 compatibility */ + scrollbar-track-color: transparent; + scrollbar-color: rgba(0, 0, 0, 0.3) transparent !important; + scrollbar-width: thin !important; + } +} \ No newline at end of file diff --git a/indexer-frontend/src/app/themes/guardian/select-button.scss b/indexer-frontend/src/app/themes/guardian/select-button.scss new file mode 100644 index 0000000000..fb5f9a2559 --- /dev/null +++ b/indexer-frontend/src/app/themes/guardian/select-button.scss @@ -0,0 +1,75 @@ +.guardian-select-button { + height: 40px; + width: auto; + + .p-selectbutton { + height: 40px; + width: auto; + + .p-button:first-child { + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; + } + + .p-button:last-child { + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; + } + + .p-button { + outline: none; + box-shadow: none; + border-radius: 0px; + color: var(--guardian-primary-color); + background: var(--guardian-secondary-color); + border-color: var(--guardian-primary-color); + + &:hover { + color: var(--guardian-primary-color); + background: var(--guardian-secondary-color); + border-color: var(--guardian-primary-color); + filter: brightness(0.95); + } + + svg-icon svg { + fill: var(--guardian-primary-color); + color: var(--guardian-primary-color); + } + } + + .p-button.p-highlight { + color: var(--guardian-secondary-color); + background: var(--guardian-primary-color); + border-color: var(--guardian-primary-color); + + &:hover { + color: var(--guardian-secondary-color); + background: var(--guardian-primary-color); + border-color: var(--guardian-primary-color); + filter: brightness(0.95); + } + + svg-icon svg { + fill: var(--guardian-secondary-color); + color: var(--guardian-secondary-color); + } + } + } + + .guardian-select-button-item { + display: flex; + height: 22px; + align-items: center; + font-family: var(--font-family-inter); + font-size: 14px; + font-weight: 400; + line-height: 30px; + text-align: left; + pointer-events: none; + + svg-icon { + height: 18px; + width: 26px; + } + } +} \ No newline at end of file diff --git a/indexer-frontend/src/app/themes/guardian/separator.scss b/indexer-frontend/src/app/themes/guardian/separator.scss new file mode 100644 index 0000000000..0741a38e56 --- /dev/null +++ b/indexer-frontend/src/app/themes/guardian/separator.scss @@ -0,0 +1,159 @@ +.guardian-stepper { + width: 100%; + height: 100%; + display: flex; + user-select: none; + + &.horizontal-stepper { + flex-direction: row; + + .guardian-step-separator { + width: 90px; + height: 100%; + position: relative; + + &::before { + content: ""; + display: block; + position: absolute; + border-top: 2px solid var(--guardian-grey-color-3, #AAB7C4); + left: 24px; + right: 24px; + top: calc(50% - 1px); + height: 3px; + } + } + + .guardian-step { + height: 34px; + min-height: 34px; + } + } + + &.vertical-stepper { + flex-direction: column; + + .guardian-step-separator { + width: 100%; + height: 24px; + min-height: 24px; + position: relative; + + &::before { + content: ""; + display: block; + position: absolute; + border-left: 2px solid var(--guardian-grey-color-3, #AAB7C4); + left: 19px; + top: 0px; + bottom: 0px; + width: 2px; + } + } + + .guardian-step { + height: 56px; + min-height: 56px; + } + } + + .guardian-step { + position: relative; + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + cursor: pointer; + margin: 3px 0px; + border-radius: 8px; + padding: 8px; + white-space: nowrap; + cursor: pointer; + + .guardian-step-icon { + width: 24px; + height: 24px; + display: flex; + justify-content: center; + align-items: center; + + svg-icon svg { + fill: var(--guardian-grey-color-3, #AAB7C4); + color: var(--guardian-grey-color-3, #AAB7C4); + } + } + + .guardian-step-marker { + width: 24px; + height: 24px; + border: 1px solid var(--guardian-grey-color-3, #AAB7C4); + background: var(--guardian-grey-color, #EFF3F7); + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; + + svg-icon svg { + fill: var(--guardian-grey-color-3, #AAB7C4); + color: var(--guardian-grey-color-3, #AAB7C4); + } + } + + .guardian-step-template { + width: 24px; + height: 24px; + border: 1px solid var(--guardian-grey-color-3, #AAB7C4); + background: var(--guardian-grey-color, #EFF3F7); + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; + } + + .guardian-step-name { + height: 24px; + padding: 2px 8px; + font-family: var(--font-family-inter); + font-size: 14px; + font-weight: 500; + text-align: left; + color: var(--guardian-grey-color-3, #AAB7C4); + } + + &:hover { + background: #f0f3fc; + } + + &[highlighted="true"] { + background: #f0f3fc; + } + + &[action="true"] { + .guardian-step-icon { + svg-icon svg { + fill: var(--guardian-primary-color); + color: var(--guardian-primary-color); + } + } + + .guardian-step-marker { + background: var(--guardian-primary-color, #4169E2); + border: 1px solid var(--guardian-primary-color, #4169E2); + + svg-icon svg { + fill: var(--guardian-secondary-color, #FFFFFF); + color: var(--guardian-secondary-color, #FFFFFF); + } + } + + .guardian-step-template { + background: var(--guardian-primary-color, #4169E2); + border: 1px solid var(--guardian-primary-color, #4169E2); + } + + .guardian-step-name { + color: var(--guardian-primary-color, #4169E2); + } + } + } +} \ No newline at end of file diff --git a/indexer-frontend/src/app/themes/guardian/tabs.scss b/indexer-frontend/src/app/themes/guardian/tabs.scss new file mode 100644 index 0000000000..e5146b36cd --- /dev/null +++ b/indexer-frontend/src/app/themes/guardian/tabs.scss @@ -0,0 +1,47 @@ +.guardian-tabs { + .p-tabview { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + position: relative; + } + + .p-tabview-panels { + width: 100%; + height: 100%; + position: relative; + } + + .p-tabview-panel { + width: 100%; + height: 100%; + position: relative; + } + + .p-tabview-nav-content { + font-size: 14px; + } + + .p-tabview .p-tabview-nav li .p-tabview-nav-link { + padding: 12px 16px; + } + + .p-tabview .p-tabview-nav li .p-tabview-nav-link:not(.p-disabled):focus { + box-shadow: none; + } + + .p-tabview .p-tabview-nav li.p-highlight .p-tabview-nav-link { + background: #ffffff; + border-color: var(--guardian-primary-color, #4169E2); + color: var(--guardian-primary-color, #4169E2); + } + + .p-tabview .p-tabview-panels { + padding: 0px; + } + + .p-tabview .p-tabview-nav { + border-color: #E1E7EF; + } +} \ No newline at end of file diff --git a/indexer-frontend/src/app/themes/guardian/textarea.scss b/indexer-frontend/src/app/themes/guardian/textarea.scss new file mode 100644 index 0000000000..d69c9d53b0 --- /dev/null +++ b/indexer-frontend/src/app/themes/guardian/textarea.scss @@ -0,0 +1,35 @@ +.guardian-textarea-container { + display: flex; + flex-direction: column; + width: 100%; + + label { + font-family: var(--font-family-inter); + font-size: 12px; + font-weight: 500; + line-height: 14px; + text-align: left; + margin-bottom: 6px; + } + + textarea { + width: 100%; + border-radius: 8px; + outline: none; + border: 1px solid var(--guardian-grey-color-2, #E1E7EF); + font-family: var(--font-family-inter); + font-size: 14px; + font-weight: 400; + color: #000; + padding: 8px 16px; + + &:enabled:hover { + border-color: var(--guardian-primary-color, #4169E2); + } + + &:enabled:focus { + border-color: var(--guardian-primary-color, #4169E2); + box-shadow: none; + } + } +} \ No newline at end of file diff --git a/indexer-frontend/src/app/themes/guardian/tree.scss b/indexer-frontend/src/app/themes/guardian/tree.scss new file mode 100644 index 0000000000..ecf337272a --- /dev/null +++ b/indexer-frontend/src/app/themes/guardian/tree.scss @@ -0,0 +1,11 @@ +.guardian-tree { + .p-tree { + border: none; + background: none; + padding: 0; + } + + .p-tree-container { + width: fit-content; + } +} \ No newline at end of file diff --git a/indexer-frontend/src/app/themes/guardian/variables.scss b/indexer-frontend/src/app/themes/guardian/variables.scss new file mode 100644 index 0000000000..e51b16dae2 --- /dev/null +++ b/indexer-frontend/src/app/themes/guardian/variables.scss @@ -0,0 +1,63 @@ +& { + --guardian-font-family: var(--font-family-inter); + --guardian-font-style: normal; + --guardian-header-color: #000; + --guardian-font-color: #23252E; + + --guardian-background: #FFFFFF; + --guardian-hover: #F0F3FC; + + --guardian-primary-color: var(--color-primary, #4169E2); + --guardian-primary-background: #e1e7fa; + + --guardian-secondary-color: #FFFFFF; + --guardian-secondary-background: #FFFFFF; + + --guardian-success-color: #19BE47; + --guardian-success-background: #CAFDD9; + + --guardian-failure-color: #FF432A; + --guardian-failure-background: #FFEAEA; + + --guardian-warning-color: #DA9B22; + + --guardian-disabled-color: #848FA9; + --guardian-disabled-background: #EFF3F7; + --guardian-disabled-icon: #C4D0E1; + + --guardian-dry-run-color: #4169E2; + --guardian-dry-run-background: #f5f7fd; + + --guardian-grey-color: #EFF3F7; + --guardian-grey-background: #F9FAFC; + --guardian-grey-color-2: #E1E7EF; + --guardian-grey-color-3: #AAB7C4; + + --guardian-delete-color: #FF432A; + --guardian-delete-background: #FFEAEA; + + --guardian-grid-color: #848FA9; + --guardian-border-color: #E1E7EF; + + --guardian-close-color: #848FA9; + --guardian-shadow: #00000014; + + --guardian-small-font-size: 12px; + --guardian-primary-font-size: 14px; + --guardian-header-1-font-size: 16px; + --guardian-header-2-font-size: 18px; + --guardian-header-3-font-size: 20px; + --guardian-header-4-font-size: 22px; + --guardian-header-5-font-size: 24px; + + --guardian-entity-color: #DA9B22; + + --formula-constant-color: #4169E2; + --formula-constant-background: #e1e7fa; + --formula-variable-color: #DA9B22; + --formula-variable-background: #FFF6E3; + --formula-function-color: #E50299; + --formula-function-background: #fbd9ef; + --formula-text-color: #19BE47; + --formula-text-background: #d7f5e2 +} \ No newline at end of file diff --git a/indexer-frontend/src/app/views/collections/formulas/formulas.component.html b/indexer-frontend/src/app/views/collections/formulas/formulas.component.html new file mode 100644 index 0000000000..4bbf0316d2 --- /dev/null +++ b/indexer-frontend/src/app/views/collections/formulas/formulas.component.html @@ -0,0 +1,26 @@ +
+
+

{{ 'header.formulas' | transloco }}

+
+ +
+ +
+ @for (filter of filters; track $index) { + @if (filter.type === 'input') { + + {{ filter.label | transloco }} + + + } + } +
+
+
+
+
+ +
diff --git a/indexer-frontend/src/app/views/collections/formulas/formulas.component.scss b/indexer-frontend/src/app/views/collections/formulas/formulas.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/indexer-frontend/src/app/views/collections/formulas/formulas.component.ts b/indexer-frontend/src/app/views/collections/formulas/formulas.component.ts new file mode 100644 index 0000000000..adc54abf02 --- /dev/null +++ b/indexer-frontend/src/app/views/collections/formulas/formulas.component.ts @@ -0,0 +1,147 @@ +import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ActivatedRoute, Router } from '@angular/router'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatSortModule } from '@angular/material/sort'; +import { MatPaginatorModule } from '@angular/material/paginator'; +import { MatTableModule } from '@angular/material/table'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatSelectModule } from '@angular/material/select'; +import { MatInputModule } from '@angular/material/input'; +import { MatButtonModule } from '@angular/material/button'; +import { LoadingComponent } from '@components/loading/loading.component'; +import { BaseGridComponent, Filter } from '../base-grid/base-grid.component'; +import { TranslocoModule } from '@jsverse/transloco'; +import { EntitiesService } from '@services/entities.service'; +import { FiltersService } from '@services/filters.service'; +import { PaginatorModule } from 'primeng/paginator'; +import { ChipsModule } from 'primeng/chips'; +import { ColumnType, TableComponent } from '@components/table/table.component'; +import { InputGroupModule } from 'primeng/inputgroup'; +import { InputGroupAddonModule } from 'primeng/inputgroupaddon'; +import { InputTextModule } from 'primeng/inputtext'; + +@Component({ + selector: 'formulas', + templateUrl: './formulas.component.html', + styleUrls: [ + '../base-grid/base-grid.component.scss', + './formulas.component.scss', + ], + standalone: true, + imports: [ + CommonModule, + MatPaginatorModule, + MatTableModule, + MatSortModule, + MatFormFieldModule, + MatSelectModule, + MatInputModule, + FormsModule, + MatButtonModule, + LoadingComponent, + TranslocoModule, + TableComponent, + PaginatorModule, + ChipsModule, + ReactiveFormsModule, + InputTextModule, + InputGroupModule, + InputGroupAddonModule, + ], +}) +export class FormulasComponent extends BaseGridComponent { + columns: any[] = [ + { + type: ColumnType.TEXT, + field: 'consensusTimestamp', + title: 'grid.consensus_timestamp', + width: '250px', + sort: true, + }, + { + type: ColumnType.TEXT, + field: 'topicId', + title: 'grid.topic_id', + width: '150px', + link: { + field: 'topicId', + url: '/topics', + }, + }, + { + type: ColumnType.TEXT, + field: 'options.owner', + title: 'grid.owner', + width: '650px', + }, + { + type: ColumnType.TEXT, + field: 'options.name', + title: 'grid.name', + width: '200px', + }, + { + type: ColumnType.BUTTON, + title: 'grid.open', + btn_label: 'grid.open', + width: '100px', + callback: this.onOpen.bind(this), + }, + ]; + + constructor( + private entitiesService: EntitiesService, + private filtersService: FiltersService, + route: ActivatedRoute, + router: Router + ) { + super(route, router); + this.filters.push( + new Filter({ + type: 'input', + field: 'topicId', + label: 'grid.topic_id', + }) + ); + this.filters.push( + new Filter({ + type: 'input', + field: 'options.owner', + label: 'grid.owner', + }) + ); + } + + protected loadData(): void { + const filters = this.getFilters(); + this.loadingData = true; + this.entitiesService.getFormulas(filters).subscribe({ + next: (result) => { + this.setResult(result); + setTimeout(() => { + this.loadingData = false; + }, 500); + }, + error: ({ message }) => { + this.loadingData = false; + console.error(message); + }, + }); + } + + protected loadFilters(): void { + this.loadingFilters = true; + this.filtersService.getVcFilters().subscribe({ + next: (result) => { + setTimeout(() => { + this.loadingFilters = false; + }, 500); + }, + error: ({ message }) => { + this.loadingFilters = false; + console.error(message); + }, + }); + } +} diff --git a/indexer-frontend/src/app/views/details/base-details/base-details.component.ts b/indexer-frontend/src/app/views/details/base-details/base-details.component.ts index f5e74e8af7..77581fa8b7 100644 --- a/indexer-frontend/src/app/views/details/base-details/base-details.component.ts +++ b/indexer-frontend/src/app/views/details/base-details/base-details.component.ts @@ -101,6 +101,8 @@ export abstract class BaseDetailsComponent { return () => this.onOpenContracts(); case Activity.Users: return () => this.onOpenUsers(); + case Activity.Formulas: + return () => this.onOpenFormulas(); default: throw new Error(`Unknown activity: ${activity}`); } @@ -158,6 +160,11 @@ export abstract class BaseDetailsComponent { this.router.navigate(['/registry-users']); } + protected onOpenFormulas() { + this.router.navigate(['/formulas']); + } + + protected handleActivities(activity: any) { this.totalActivity = 0; this.activityItems = []; diff --git a/indexer-frontend/src/app/views/details/formula-details/formula-details.component.html b/indexer-frontend/src/app/views/details/formula-details/formula-details.component.html new file mode 100644 index 0000000000..95516cbb06 --- /dev/null +++ b/indexer-frontend/src/app/views/details/formula-details/formula-details.component.html @@ -0,0 +1,83 @@ +
+
+
+

{{ 'details.formula.header' | transloco }} {{id}}

+
+ @if (loading || subLoading) { +
+ +
+ } @else { + @if (row) { + + @if (target) { + + + + + + @if (formulaData) { + + } + + + + @if (first._ipfs) { +
+
+
+
+ {{ 'details.cid' | transloco }}: +
+
+ {{file.cid}} +
+
+ @if (first._ipfsStatus) { +
+ +
+ } +
+ @if (!first._ipfsStatus) { +
+
+ @if (first._ipfs.length<2) { +
+ {{ 'details.document_not_loaded_header' | transloco }} +
+
+ {{ 'details.document_not_loaded_message' | transloco }} +
+ } @else { +
+ {{ 'details.documents_not_loaded_header' | transloco }} +
+
+ {{ 'details.documents_not_loaded_message' | transloco }} +
+ } + +
+
+ } +
+ } +
+ } + + +
+ +
+
+
+ } @else { +
{{ 'details.not_found' | transloco }}
+ } + } +
+
diff --git a/indexer-frontend/src/app/views/details/formula-details/formula-details.component.scss b/indexer-frontend/src/app/views/details/formula-details/formula-details.component.scss new file mode 100644 index 0000000000..b72bc30419 --- /dev/null +++ b/indexer-frontend/src/app/views/details/formula-details/formula-details.component.scss @@ -0,0 +1,3 @@ +.document-container-group textarea { + min-height: calc(100vh - 450px); +} \ No newline at end of file diff --git a/indexer-frontend/src/app/views/details/formula-details/formula-details.component.ts b/indexer-frontend/src/app/views/details/formula-details/formula-details.component.ts new file mode 100644 index 0000000000..460085dd03 --- /dev/null +++ b/indexer-frontend/src/app/views/details/formula-details/formula-details.component.ts @@ -0,0 +1,171 @@ +import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ActivatedRoute, Router } from '@angular/router'; +import { LoadingComponent } from '@components/loading/loading.component'; +import { MatTabsModule } from '@angular/material/tabs'; +import { NgxEchartsDirective } from 'ngx-echarts'; +import { MatInputModule } from '@angular/material/input'; +import { BaseDetailsComponent } from '../base-details/base-details.component'; +import { TranslocoModule } from '@jsverse/transloco'; +import { EntitiesService } from '@services/entities.service'; +import { TabViewModule } from 'primeng/tabview'; +import { ProgressSpinnerModule } from 'primeng/progressspinner'; +import { ButtonModule } from 'primeng/button'; +import { + OverviewFormComponent, + OverviewFormField, +} from '@components/overview-form/overview-form.component'; +import { ActivityComponent } from '@components/activity/activity.component'; +import { InputTextareaModule } from 'primeng/inputtextarea'; +import { OrganizationChartModule } from 'primeng/organizationchart'; +import CID from 'cids'; +import { FormulaViewComponent } from '@components/formula-view/formula-view.component'; + +@Component({ + selector: 'formula-details', + templateUrl: './formula-details.component.html', + styleUrls: [ + '../base-details/base-details.component.scss', + './formula-details.component.scss', + ], + standalone: true, + imports: [ + CommonModule, + LoadingComponent, + MatTabsModule, + NgxEchartsDirective, + MatInputModule, + TranslocoModule, + TabViewModule, + ProgressSpinnerModule, + ButtonModule, + OverviewFormComponent, + ActivityComponent, + InputTextareaModule, + OrganizationChartModule, + FormulaViewComponent + ], +}) +export class FormulaDetailsComponent extends BaseDetailsComponent { + public subLoading: boolean = false; + + tabs: any[] = ['overview', 'view', 'document', 'raw']; + overviewFields: OverviewFormField[] = [ + { + label: 'details.formula.overview.topic_id', + path: 'topicId', + link: '/topics', + }, + { + label: 'details.formula.overview.owner', + path: 'options.owner', + }, + { + label: 'details.formula.overview.name', + path: 'options.name', + }, + { + label: 'details.formula.overview.description', + path: 'options.description', + }, + { + label: 'details.formula.overview.policy', + path: 'analytics.policyId', + link: '/policies', + }, + ]; + formulaData: any; + + constructor( + entitiesService: EntitiesService, + route: ActivatedRoute, + router: Router + ) { + super(entitiesService, route, router); + } + + protected override loadData(): void { + if (this.id) { + this.loading = true; + this.entitiesService.getFormula(this.id).subscribe({ + next: (result) => { + this.setResult(result); + setTimeout(() => { + this.loading = false; + }, 500); + }, + error: ({ message }) => { + this.loading = false; + console.error(message); + }, + }); + } else { + this.setResult(); + } + } + + protected override setFiles(item: any) { + if (item) { + if (Array.isArray(item.files)) { + item._ipfs = []; + item._ipfsStatus = true; + for (let i = 0; i < item.files.length; i++) { + const url = item.files[i]; + const document = item.analytics?.config; + const json = this.getJson(document); + const cid = new CID(url); + const ipfs = { + version: cid.version, + cid: url, + global: cid.toV1().toString('base32'), + document, + json, + } + if (!document) { + item._ipfsStatus = false; + } + item._ipfs.push(ipfs); + } + } + } + } + + protected override onNavigate(): void { + if (this.id && this.tab === 'view' && !this.formulaData) { + this.subLoading = true; + this.entitiesService.getFormulaRelationships(this.id).subscribe({ + next: (result) => { + this.formulaData = result; + }, + complete: () => (this.subLoading = false), + }); + } + } + + + protected getJson(item: any): string { + try { + return JSON.stringify(item, null, 4); + } catch (error) { + console.log(error); + return ''; + } + } + + protected override getTabIndex(name: string): number { + if (this.target) { + const tabIndex = this.tabs.findIndex((item) => item === name); + return tabIndex >= 0 ? tabIndex : 0; + } else { + return 0; + } + } + + protected override getTabName(index: number): string { + if (this.target) { + return this.tabs[index] || 'raw'; + } else { + return 'raw'; + } + } +} diff --git a/indexer-frontend/src/app/views/details/policy-details/policy-details.component.ts b/indexer-frontend/src/app/views/details/policy-details/policy-details.component.ts index 7e8dc7963d..2c02839625 100644 --- a/indexer-frontend/src/app/views/details/policy-details/policy-details.component.ts +++ b/indexer-frontend/src/app/views/details/policy-details/policy-details.component.ts @@ -196,4 +196,12 @@ export class PolicyDetailsComponent extends BaseDetailsComponent { }, }); } + + public override onOpenFormulas() { + this.router.navigate(['/formulas'], { + queryParams: { + topicId: this.row.topicId, + }, + }); + } } diff --git a/indexer-frontend/src/app/views/details/vc-document-details/vc-document-details.component.html b/indexer-frontend/src/app/views/details/vc-document-details/vc-document-details.component.html index 5321c43da2..b4a3eb59c4 100644 --- a/indexer-frontend/src/app/views/details/vc-document-details/vc-document-details.component.html +++ b/indexer-frontend/src/app/views/details/vc-document-details/vc-document-details.component.html @@ -60,7 +60,8 @@

{{ 'details.vc.header' | transloco }} {{id}}

+ [private-fields]="privateFields" + [formulas]="formulasResults">
diff --git a/indexer-frontend/src/app/views/details/vc-document-details/vc-document-details.component.ts b/indexer-frontend/src/app/views/details/vc-document-details/vc-document-details.component.ts index 694e457649..e33d179eab 100644 --- a/indexer-frontend/src/app/views/details/vc-document-details/vc-document-details.component.ts +++ b/indexer-frontend/src/app/views/details/vc-document-details/vc-document-details.component.ts @@ -23,6 +23,7 @@ import { OverviewFormField, } from '@components/overview-form/overview-form.component'; import { ButtonModule } from 'primeng/button'; +import { FormulasTree } from '../../../models/formula-tree'; @Component({ selector: 'vc-document-details', @@ -128,6 +129,8 @@ export class VcDocumentDetailsComponent extends BaseDetailsComponent { 'policyId': true, 'ref': true }; + formulas?: FormulasTree | null; + formulasResults?: any | null; constructor( entitiesService: EntitiesService, @@ -149,6 +152,14 @@ export class VcDocumentDetailsComponent extends BaseDetailsComponent { } catch (error) { console.log(error); } + try { + if (result?.formulasData) { + this.formulas = FormulasTree.from(result.formulasData); + this.formulasResults = this.formulas?.getFields(this.schema?.iri); + } + } catch (error) { + console.log(error); + } } protected override setFiles(result?: any) { diff --git a/indexer-frontend/src/assets/i18n/en.json b/indexer-frontend/src/assets/i18n/en.json index 5274756210..a4c66b3aec 100644 --- a/indexer-frontend/src/assets/i18n/en.json +++ b/indexer-frontend/src/assets/i18n/en.json @@ -44,6 +44,7 @@ "search_placeholder": "Search by keyword", "statistics": "Statistics", "labels": "Labels", + "formulas": "Formulas", "loading_progress": "Loading data...", "left": "left" }, @@ -121,6 +122,7 @@ "relationships": "Relationships", "raw_data": "Raw data", "labels": "Labels", + "formulas": "Formulas", "tabs": { "activity": "Activity" }, @@ -137,7 +139,8 @@ "tools": "Tools", "modules": "Modules", "vcs": "Verifiable Credentials", - "vps": "Verifiable Presentations" + "vps": "Verifiable Presentations", + "formulas": "Formulas" }, "hedera": { "topic_id": "Topic Id", @@ -252,6 +255,25 @@ "owner": "Owner" } }, + "formula": { + "header": "Formula", + "tabs": { + "overview": "Overview", + "view": "View", + "document": "Document", + "raw_data": "Raw data", + "formulas": "Formulas", + "files": "Files" + }, + "overview": { + "topic_id": "Topic Id", + "policy_topic_id": "Policy Topic Id", + "name": "Name", + "description": "Description", + "owner": "Owner", + "policy": "Policy" + } + }, "statistic": { "header": "Statistic", "tabs": { diff --git a/indexer-frontend/src/styles.scss b/indexer-frontend/src/styles.scss index d8c4b97828..f9eb6f453d 100644 --- a/indexer-frontend/src/styles.scss +++ b/indexer-frontend/src/styles.scss @@ -3,6 +3,7 @@ @import 'primeicons/primeicons.css'; @import 'assets/primeng/lara-light-blue/theme.css'; @import 'primeng/resources/primeng.css'; +@import 'mathlive/fonts.css'; html, body { @@ -40,8 +41,7 @@ body { @font-face { font-family: 'Archivo Black'; - src: url('assets/fonts/Archivo_Black/ArchivoBlack-Regular.ttf') - format('truetype'); + src: url('assets/fonts/Archivo_Black/ArchivoBlack-Regular.ttf') format('truetype'); font-weight: normal; font-style: normal; } @@ -69,11 +69,14 @@ h1 { .p-button { padding: 6px 16px; + .p-button-label { font-weight: 500; } + &.p-button-link { user-select: all; + .p-button-label { font-weight: 400; } @@ -139,16 +142,20 @@ p-tabview { } @keyframes custom-spin { + 100%, 0% { stroke: var(--primary-color); } + 40% { stroke: var(--primary-color); } + 66% { stroke: var(--primary-color); } + 80%, 90% { stroke: var(--primary-color); @@ -159,10 +166,12 @@ p-tabview { width: 5px; height: 5px; } + *::-webkit-scrollbar-thumb { background-color: #babac0; border-radius: 5px; } + *::-webkit-scrollbar-button { display: none; } @@ -185,16 +194,20 @@ a { .p-selectbutton { .p-button { border: 1px solid var(--color-gray-2); + .p-button-icon-left { color: var(--color-gray-3); } } + .p-button:focus { box-shadow: none; } + .p-button.p-highlight { background: var(--color-gray-2); border-color: var(--color-gray-2); + .p-button-icon-left { color: var(--color-gray-4); } @@ -207,4 +220,4 @@ a { .p-inputtextarea { width: 100%; -} +} \ No newline at end of file diff --git a/indexer-frontend/yarn.lock b/indexer-frontend/yarn.lock index c297e75777..c3dddf46c2 100644 --- a/indexer-frontend/yarn.lock +++ b/indexer-frontend/yarn.lock @@ -1268,6 +1268,14 @@ resolved "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz" integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== +"@cortex-js/compute-engine@0.24.1": + version "0.24.1" + resolved "https://registry.npmjs.org/@cortex-js/compute-engine/-/compute-engine-0.24.1.tgz" + integrity sha512-B9pLaZFC1vBrL64yR2LQWy2sj1E/+wtwIVokPuPqX8YWXwj9W2AMVyS9pSMN8gEdjpAC50nSmtL6110Rgi7dKA== + dependencies: + complex.js "^2.1.1" + decimal.js "^10.4.3" + "@discoveryjs/json-ext@0.5.7": version "0.5.7" resolved "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz" @@ -1284,7 +1292,7 @@ integrity sha512-0MBh53o6XtI6ctDnRMeQ+xoCN8kD2qI1rY1KgF/xdWQwoFeKou7puvDfV8/Wv4Ctx2rRpET/gGdz3YlNtNACSA== "@indexer/interfaces@file:../indexer-interfaces": - version "2.27.0" + version "3.0.0" resolved "file:../indexer-interfaces" "@isaacs/cliui@^8.0.2": @@ -3208,6 +3216,11 @@ common-path-prefix@^3.0.0: resolved "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz" integrity sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w== +complex.js@^2.1.1: + version "2.4.2" + resolved "https://registry.npmjs.org/complex.js/-/complex.js-2.4.2.tgz" + integrity sha512-qtx7HRhPGSCBtGiST4/WGHuW+zeaND/6Ld+db6PbrulIB1i2Ev/2UPiqcmpQNPSyfBKraC0EOvOKCB5dGZKt3g== + compressible@~2.0.16: version "2.0.18" resolved "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz" @@ -3425,6 +3438,11 @@ debug@2.6.9: dependencies: ms "2.0.0" +decimal.js@^10.4.3: + version "10.5.0" + resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz" + integrity sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw== + default-gateway@^6.0.3: version "6.0.3" resolved "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz" @@ -5027,6 +5045,13 @@ make-fetch-happen@^13.0.0: promise-retry "^2.0.1" ssri "^10.0.0" +mathlive@^0.103.0: + version "0.103.0" + resolved "https://registry.npmjs.org/mathlive/-/mathlive-0.103.0.tgz" + integrity sha512-gNJDMUOz0WePyDpLO/w1sGkrz0JrMwrT1QoFdGloVjjerWlUeKWn1JGM6XlO5SQ1AqXWemRKvlyVXft+QsxX8w== + dependencies: + "@cortex-js/compute-engine" "0.24.1" + media-typer@0.3.0: version "0.3.0" resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" diff --git a/indexer-interfaces/src/interfaces/details/formula.details.ts b/indexer-interfaces/src/interfaces/details/formula.details.ts new file mode 100644 index 0000000000..e5a2ec1cbd --- /dev/null +++ b/indexer-interfaces/src/interfaces/details/formula.details.ts @@ -0,0 +1,93 @@ +import { DetailsActivity } from './details.interface.js'; +import { Message } from '../message.interface.js'; +import { ISchema } from './schema.details.js'; + +/** + * Formula options + */ +export interface FormulaOptions { + /** + * UUID + */ + uuid: string; + /** + * Name + */ + name: string; + /** + * Description + */ + description: string; + /** + * Owner + */ + owner: string; + /** + * Policy topic identifier + */ + policyTopicId: string; + /** + * Instance topic identifier + */ + policyInstanceTopicId: string; +} + +/** + * Formula analytics + */ +export interface FormulaAnalytics { + /** + * Text search + */ + textSearch?: string; + /** + * Owner + */ + owner?: string; + /** + * Policy id + */ + policyId?: string; + /** + * Config + */ + config?: any; +} + +/** + * Formula Relationships + */ +export interface FormulaRelationships { + /** + * Message ID + */ + id: string; + /** + * Formula + */ + item?: Formula; + /** + * Schemas + */ + schemas?: ISchema[]; + /** + * Formulas + */ + formulas?: Formula[]; +} + +/** + * Formula activity + */ +// tslint:disable-next-line:no-empty-interface +export interface FormulaActivity {} + +/** + * Formula + */ +export type Formula = Message; + +/** + * Formula details + */ +export type FormulaDetails = DetailsActivity; diff --git a/indexer-interfaces/src/interfaces/details/index.ts b/indexer-interfaces/src/interfaces/details/index.ts index eaeef4f11c..afedff5113 100644 --- a/indexer-interfaces/src/interfaces/details/index.ts +++ b/indexer-interfaces/src/interfaces/details/index.ts @@ -14,4 +14,5 @@ export * from './topic.details.js'; export * from './token.details.js'; export * from './nft.details.js'; export * from './statistic.details.js'; -export * from './label.details.js'; \ No newline at end of file +export * from './label.details.js'; +export * from './formula.details.js'; \ No newline at end of file diff --git a/indexer-interfaces/src/interfaces/details/policy.details.ts b/indexer-interfaces/src/interfaces/details/policy.details.ts index d88d8204a5..5f9380c40b 100644 --- a/indexer-interfaces/src/interfaces/details/policy.details.ts +++ b/indexer-interfaces/src/interfaces/details/policy.details.ts @@ -121,6 +121,10 @@ export interface PolicyActivity { * Schemas */ schemas: number; + /** + * Formulas + */ + formulas: number; } /** diff --git a/indexer-interfaces/src/interfaces/details/vc.details.ts b/indexer-interfaces/src/interfaces/details/vc.details.ts index c76be180e8..a57f6c2236 100644 --- a/indexer-interfaces/src/interfaces/details/vc.details.ts +++ b/indexer-interfaces/src/interfaces/details/vc.details.ts @@ -53,4 +53,13 @@ export type VC = Message; /** * VC details */ -export type VCDetails = DetailsHistory & { schema?: any }; +export type VCDetails = DetailsHistory & { + schema?: any, + formulasData?: { + document: any, + policy: any, + formulas: any[], + relationships: any[], + schemas: any[] + } +}; diff --git a/indexer-interfaces/src/interfaces/formulas.interface.ts b/indexer-interfaces/src/interfaces/formulas.interface.ts new file mode 100644 index 0000000000..999db44040 --- /dev/null +++ b/indexer-interfaces/src/interfaces/formulas.interface.ts @@ -0,0 +1,45 @@ +export enum FormulaItemType { + Constant = 'constant', + Variable = 'variable', + Formula = 'formula', + Text = 'text' +} + +export interface IFormulaLink { + type: 'formula' | 'schema'; + entityId: string; + item: string; +} + +export interface IFormulaItem { + uuid: string; + name: string; + description: string; + type: FormulaItemType; + value?: any; + link?: IFormulaLink | null; + relationships?: string[]; +} + +export interface IFormulaFile { + name?: string; + url?: string; + type?: string; +} + +export interface IFormulaConfig { + formulas?: IFormulaItem[]; + files?: IFormulaFile[]; +} + +export interface IFormula { + id?: string; + uuid?: string; + name?: string; + description?: string; + policyId?: string; + policyTopicId?: string; + owner?: string; + status?: string; + config?: IFormulaConfig; +} \ No newline at end of file diff --git a/indexer-interfaces/src/interfaces/index.ts b/indexer-interfaces/src/interfaces/index.ts index 94cdc715ef..eeae1e29eb 100644 --- a/indexer-interfaces/src/interfaces/index.ts +++ b/indexer-interfaces/src/interfaces/index.ts @@ -11,5 +11,6 @@ export * from './raw-nft.interface.js'; export * from './raw-token.interface.js'; export * from './raw-topic.interface.js'; export * from './search-policy.interface.js'; +export * from './formulas.interface.js'; export * from './network-explorer-settings.interface.js'; export * from './data-loading-progress.interface.js'; diff --git a/indexer-interfaces/src/interfaces/schema/schema-field.interface.ts b/indexer-interfaces/src/interfaces/schema/schema-field.interface.ts index 3c13f0acc4..66251d1b53 100644 --- a/indexer-interfaces/src/interfaces/schema/schema-field.interface.ts +++ b/indexer-interfaces/src/interfaces/schema/schema-field.interface.ts @@ -86,6 +86,11 @@ export interface SchemaField { */ path?: string; + /** + * Full field path + */ + fullPath?: string; + /** * Remote link */ diff --git a/indexer-interfaces/src/models/schema.ts b/indexer-interfaces/src/models/schema.ts index 209c02f041..cae9aaf102 100644 --- a/indexer-interfaces/src/models/schema.ts +++ b/indexer-interfaces/src/models/schema.ts @@ -5,6 +5,22 @@ import { SchemaHelper } from '../helpers/index.js'; * Schema class */ export class Schema { + /** + * IRI + */ + public iri?: string; + /** + * Name + */ + public name?: string; + /** + * Description + */ + public description?: string; + /** + * Type + */ + public type?: string; /** * Document */ @@ -34,6 +50,10 @@ export class Schema { this.document = null; } if (this.document) { + this.iri = this.document.$id || ''; + this.name = this.document.title || ''; + this.type = this.iri.replace(/^\#/, ''); + this.description = this.document.description || ''; this.parseDocument(); } } @@ -57,5 +77,65 @@ export class Schema { this.fields, schemaCache ); + this.setPaths(this.fields, '', this.iri + '/'); + } + + /** + * Parse document + * @private + */ + private setPaths(fields: SchemaField[], path: string, fullPath: string): void { + for (const f of fields) { + f.path = path + f.name; + f.fullPath = fullPath + f.name; + if (Array.isArray(f.fields)) { + this.setPaths(f.fields, f.path + '.', f.fullPath + '.'); + } + } + } + + /** + * Get all fields + */ + public getFields(): SchemaField[] { + return this._getFields([], this.fields); + } + + /** + * Get all fields + */ + private _getFields(result: SchemaField[], fields?: SchemaField[]): SchemaField[] { + if (Array.isArray(fields)) { + for (const field of fields) { + result.push(field); + this._getFields(result, field.fields); + } + } + return result; + } + + /** + * Get field + */ + public getField(path: string): SchemaField | null { + return this._getField(path, this.fields); + } + + /** + * Get field + */ + private _getField(path: string, fields?: SchemaField[]): SchemaField | null { + if (Array.isArray(fields)) { + for (const field of fields) { + if (field.path === path) { + return field; + } + const result = this._getField(path, field.fields); + if (result) { + return result; + } + } + } + return null; } } diff --git a/indexer-interfaces/src/types/message-action.type.ts b/indexer-interfaces/src/types/message-action.type.ts index 06ec996060..f59b590b7c 100644 --- a/indexer-interfaces/src/types/message-action.type.ts +++ b/indexer-interfaces/src/types/message-action.type.ts @@ -39,4 +39,5 @@ export enum MessageAction { CreateStatisticAssessment = 'create-assessment-document', PublishPolicyLabel = 'publish-policy-label', CreateLabelDocument = 'create-label-document', + PublishFormula = 'publish-formula', } diff --git a/indexer-interfaces/src/types/message-type.type.ts b/indexer-interfaces/src/types/message-type.type.ts index c623d7eb3c..29746241e4 100644 --- a/indexer-interfaces/src/types/message-type.type.ts +++ b/indexer-interfaces/src/types/message-type.type.ts @@ -22,5 +22,6 @@ export enum MessageType { GUARDIAN_ROLE = 'Guardian-Role-Document', USER_PERMISSIONS = 'User-Permissions', POLICY_STATISTIC = 'Policy-Statistic', - POLICY_LABEL = 'Policy-Label' + POLICY_LABEL = 'Policy-Label', + FORMULA = 'Formula' } \ No newline at end of file diff --git a/indexer-service/src/api/analytics.service.ts b/indexer-service/src/api/analytics.service.ts index 222086e27c..ab385654d0 100644 --- a/indexer-service/src/api/analytics.service.ts +++ b/indexer-service/src/api/analytics.service.ts @@ -136,7 +136,7 @@ export class AnalyticsService { try { const { topicId } = msg; const em = DataBaseHelper.getEntityManager(); - const [messages, count] = (await em.findAndCount( + const [messages] = (await em.findAndCount( Message, { topicId, @@ -152,22 +152,23 @@ export class AnalyticsService { )) as any; for (const message of messages) { - let VCdocuments: VCDetails[] = []; + const VCdocuments: VCDetails[] = []; for (const fileName of message.files) { try { const file = await DataBaseHelper.loadFile(fileName); VCdocuments.push(JSON.parse(file) as VCDetails); + // tslint:disable-next-line:no-empty } catch (error) { } } message.documents = VCdocuments; - var messageCache = messagesCache.find((cache: MessageCache) => cache.consensusTimestamp == message.consensusTimestamp); + const messageCache = messagesCache.find((cache: MessageCache) => cache.consensusTimestamp === message.consensusTimestamp); if (messageCache) { message.sequenceNumber = messageCache.sequenceNumber; } } - + return new MessageResponse(messages); } catch (error) { return new MessageError(error); diff --git a/indexer-service/src/api/entities.service.ts b/indexer-service/src/api/entities.service.ts index 97e63a97e9..582c9e381d 100644 --- a/indexer-service/src/api/entities.service.ts +++ b/indexer-service/src/api/entities.service.ts @@ -55,7 +55,11 @@ import { StatisticDetails, Label, LabelDetails, - LabelDocumentDetails + LabelDocumentDetails, + Formula, + FormulaDetails, + FormulaRelationships, + PolicyActivity } from '@indexer/interfaces'; import { parsePageParams } from '../utils/parse-page-params.js'; import axios from 'axios'; @@ -112,7 +116,20 @@ function parseKeywordFilter(keywordsString: string) { return filter; } -async function loadDocuments(row: Message, tryLoad: boolean): Promise { +async function getPolicy(row: Message): Promise { + const em = DataBaseHelper.getEntityManager(); + const policyMessage = await em.findOne(Message, { + type: MessageType.INSTANCE_POLICY, + consensusTimestamp: row.analytics?.policyId, + } as any); + return policyMessage; +} + +async function loadDocuments( + row: Message, + tryLoad: boolean, + prepare?: (file: string) => string +): Promise { try { const result = { ...row }; if (!result?.files?.length) { @@ -127,7 +144,11 @@ async function loadDocuments(row: Message, tryLoad: boolean): Promise { result.documents = []; for (const fileName of result.files) { const file = await DataBaseHelper.loadFile(fileName); - result.documents.push(file); + if (prepare) { + result.documents.push(prepare(file)); + } else { + result.documents.push(file); + } } return result; } catch (error) { @@ -135,6 +156,20 @@ async function loadDocuments(row: Message, tryLoad: boolean): Promise { } } +async function loadSchemaDocument(row: Message): Promise { + try { + const result = { ...row }; + if (!result?.files?.length) { + return result; + } + const file = await DataBaseHelper.loadFile(result.files[0]); + result.documents = [file]; + return result; + } catch (error) { + return row; + } +} + async function loadSchema( row: Message, tryLoad: boolean, @@ -185,6 +220,50 @@ async function loadSchema( } } +async function loadFormulas( + row: Message, +): Promise { + try { + const policyId = row.analytics.policyId; + if (!policyId) { + return null; + } + + const em = DataBaseHelper.getEntityManager(); + const formulasMessages = await em.find(Message, { + type: MessageType.FORMULA, + 'analytics.policyId': policyId, + } as any); + + return formulasMessages; + } catch (error) { + return null; + } +} + +async function loadSchemas(topicId: string,): Promise { + try { + const em = DataBaseHelper.getEntityManager(); + const schemas = await em.find(Message, { + type: MessageType.SCHEMA, + action: { + $in: [ + MessageAction.PublishSchema, + MessageAction.PublishSystemSchema, + ], + }, + topicId, + } as any); + + for (let i = 0; i < schemas.length; i++) { + schemas[i] = await loadSchemaDocument(schemas[i]); + } + return schemas; + } catch (error) { + return []; + } +} + async function checkDocuments(row: Message, timeout: number): Promise { if (row?.files?.length) { const fns: Promise[] = []; @@ -268,6 +347,59 @@ function getContext(file: string): any { return null; } } + +async function findRelationships(target: Message): Promise { + if (!target) { + return []; + } + + const em = DataBaseHelper.getEntityManager(); + const map = new Map(); + map.set(target.consensusTimestamp, target); + + await addRelationships(target, map, em); + + const documents = []; + for (const message of map.values()) { + if (message) { + const document = await loadDocuments(message, false); + if (document) { + documents.push(document) + } + } + } + return documents; +} + +async function addRelationships( + doc: Message, + relationships: Map, + em: any +) { + if (Array.isArray(doc?.options?.relationships)) { + for (const id of doc.options.relationships) { + await addRelationship(id, relationships, em); + } + } +} + +async function addRelationship( + messageId: string, + relationships: Map, + em: any +) { + if (!messageId || relationships.has(messageId)) { + return; + } + + const doc = (await em.findOne(Message, { + consensusTimestamp: messageId, + type: MessageType.VC_DOCUMENT, + })); + + relationships.set(messageId, doc); + await addRelationships(doc, relationships, em); +} //#endregion @Controller() @@ -640,11 +772,17 @@ export class EntityService { type: MessageType.ROLE_DOCUMENT, 'analytics.policyId': row.consensusTimestamp, } as any); - const activity: any = { + const formulas = await em.count(Message, { + type: MessageType.FORMULA, + topicId: row.topicId, + } as any); + + const activity: PolicyActivity = { schemas, vcs, vps, roles, + formulas }; if (!item) { return new MessageResponse({ @@ -1303,6 +1441,133 @@ export class EntityService { } } //#endregion + //#region FORMULAS + @MessagePattern(IndexerMessageAPI.GET_FORMULAS) + async getFormulas( + @Payload() msg: PageFilters + ): Promise>> { + try { + const options = parsePageParams(msg); + const filters = parsePageFilters(msg); + filters.type = MessageType.FORMULA; + filters.action = MessageAction.PublishFormula; + const em = DataBaseHelper.getEntityManager(); + const [rows, count] = (await em.findAndCount( + Message, + filters, + options + )) as [Formula[], number]; + const result = { + items: rows.map((item) => { + delete item.analytics; + return item; + }), + pageIndex: options.offset / options.limit, + pageSize: options.limit, + total: count, + order: options.orderBy, + }; + return new MessageResponse>(result); + } catch (error) { + return new MessageError(error, error.code); + } + } + + @MessagePattern(IndexerMessageAPI.GET_FORMULA) + async getFormula( + @Payload() msg: { messageId: string } + ): Promise> { + try { + const { messageId } = msg; + const em = DataBaseHelper.getEntityManager(); + const item = await em.findOne(Message, { + consensusTimestamp: messageId, + type: MessageType.FORMULA, + action: MessageAction.PublishFormula + }); + const row = await em.findOne(MessageCache, { + consensusTimestamp: messageId, + }); + + const activity: any = {}; + + if (!item) { + return new MessageResponse({ + id: messageId, + row, + activity, + }); + } + + return new MessageResponse({ + id: messageId, + uuid: item.uuid, + item, + row, + activity, + }); + } catch (error) { + return new MessageError(error, error.code); + } + } + + @MessagePattern(IndexerMessageAPI.GET_FORMULA_RELATIONSHIPS) + async getFormulaRelationships( + @Payload() msg: { messageId: string } + ): Promise> { + try { + const { messageId } = msg; + const em = DataBaseHelper.getEntityManager(); + const item = await em.findOne(Message, { + consensusTimestamp: messageId, + type: MessageType.FORMULA, + action: MessageAction.PublishFormula + }); + const row = await em.findOne(MessageCache, { + consensusTimestamp: messageId, + }); + + const schemas = await em.find(Message, { + type: MessageType.SCHEMA, + action: { + $in: [ + MessageAction.PublishSchema, + MessageAction.PublishSystemSchema, + ], + }, + topicId: row.topicId, + } as any); + + for (let i = 0; i < schemas.length; i++) { + schemas[i] = await loadSchemaDocument(schemas[i]); + } + + const formulas = await em.find(Message, { + type: MessageType.FORMULA, + action: MessageAction.PublishFormula, + topicId: row.topicId, + } as any); + + if (!item) { + return new MessageResponse({ + id: messageId, + schemas, + formulas + }); + } + + return new MessageResponse({ + id: messageId, + item, + schemas, + formulas + }); + } catch (error) { + console.log(error); + return new MessageError(error, error.code); + } + } + //#endregion //#endregion //#region DOCUMENTS @@ -1595,7 +1860,6 @@ export class EntityService { item = await loadDocuments(item, true); const schema = await loadSchema(item, true); - const history = await em.find( Message, { @@ -1612,6 +1876,23 @@ export class EntityService { history[i] = await loadDocuments(history[i], false); } + //formulas + let formulasData:any = null; + const formulas = await loadFormulas(item); + if(formulas && formulas.length) { + const policy = await getPolicy(item); + const relationships = await findRelationships(item); + const schemas = await loadSchemas(policy?.topicId); + const document = item; + formulasData = { + policy, + formulas, + relationships, + schemas, + document + } + } + return new MessageResponse({ id: messageId, uuid: item.uuid, @@ -1619,6 +1900,7 @@ export class EntityService { history, row, schema, + formulasData }); } catch (error) { return new MessageError(error, error.code); diff --git a/indexer-service/src/app.ts b/indexer-service/src/app.ts index 0407b0df3b..2a2f5eea4d 100644 --- a/indexer-service/src/app.ts +++ b/indexer-service/src/app.ts @@ -11,23 +11,7 @@ import { FiltersService } from './api/filters.service.js'; import { LandingService } from './api/landing.service.js'; import { AnalyticsService } from './api/analytics.service.js'; import { SettingsService } from './api/settings.service.js'; -import { - SynchronizationAnalytics, - SynchronizationContracts, - SynchronizationDid, - SynchronizationModules, - SynchronizationPolicy, - SynchronizationProjects, - SynchronizationRegistries, - SynchronizationRoles, - SynchronizationSchemas, - SynchronizationTools, - SynchronizationTopics, - SynchronizationVCs, - SynchronizationVPs, - SynchronizationLabels, - SynchronizationAll -} from './helpers/synchronizers/index.js'; +import { SynchronizationAll } from './helpers/synchronizers/index.js'; import { fixtures } from './helpers/fixtures.js'; const channelName = ( @@ -75,14 +59,6 @@ async function updateIndexes() { } } -function getMask(mask: string | undefined): string { - return (mask || '0 * * * *'); -} - -function getBoolean(flag: string | undefined): boolean { - return (flag?.toLowerCase() === 'true'); -} - @Module({ imports: [ ClientsModule.register([ @@ -156,52 +132,7 @@ Promise.all([ /** * Sync tasks */ - if (process.env.SYNC_ALL_MASK) { - (new SynchronizationAll(getMask(process.env.SYNC_ALL_MASK))) - .start(getBoolean(process.env.START_SYNC_ALL)); - } else { - (new SynchronizationAnalytics(getMask(process.env.SYNC_ANALYTICS_MASK))) - .start(getBoolean(process.env.START_SYNC_ANALYTICS)); - - (new SynchronizationProjects(getMask(process.env.SYNC_ANALYTICS_MASK))) - .start(getBoolean(process.env.START_SYNC_ANALYTICS)); - - (new SynchronizationModules(getMask(process.env.SYNC_MODULES_MASK))) - .start(getBoolean(process.env.START_SYNC_MODULES)); - - (new SynchronizationRegistries(getMask(process.env.SYNC_REGISTRIES_MASK))) - .start(getBoolean(process.env.START_SYNC_REGISTRIES)); - - (new SynchronizationRoles(getMask(process.env.SYNC_ROLES_MASK))) - .start(getBoolean(process.env.START_SYNC_ROLES)); - - (new SynchronizationTools(getMask(process.env.SYNC_TOOLS_MASK))) - .start(getBoolean(process.env.START_SYNC_TOOLS)); - - (new SynchronizationTopics(getMask(process.env.SYNC_TOPICS_MASK))) - .start(getBoolean(process.env.START_SYNC_TOPICS)); - - (new SynchronizationSchemas(getMask(process.env.SYNC_SCHEMAS_MASK))) - .start(getBoolean(process.env.START_SYNC_SCHEMAS)); - - (new SynchronizationDid(getMask(process.env.SYNC_DID_DOCUMENTS_MASK))) - .start(getBoolean(process.env.START_SYNC_DID_DOCUMENTS)); - - (new SynchronizationVCs(getMask(process.env.SYNC_VC_DOCUMENTS_MASK))) - .start(getBoolean(process.env.START_SYNC_VC_DOCUMENTS)); - - (new SynchronizationVPs(getMask(process.env.SYNC_VP_DOCUMENTS_MASK))) - .start(getBoolean(process.env.START_SYNC_VP_DOCUMENTS)); - - (new SynchronizationPolicy(getMask(process.env.SYNC_POLICIES_MASK))) - .start(getBoolean(process.env.START_SYNC_POLICIES)); - - (new SynchronizationContracts(getMask(process.env.SYNC_CONTRACTS_MASK))) - .start(getBoolean(process.env.START_SYNC_CONTRACTS)); - - (new SynchronizationLabels(getMask(process.env.SYNC_LABELS_MASK))) - .start(getBoolean(process.env.START_SYNC_LABELS)); - } + SynchronizationAll.createAllTasks(); }, (reason) => { console.log(reason); diff --git a/indexer-service/src/helpers/parsers/formula.parser.ts b/indexer-service/src/helpers/parsers/formula.parser.ts new file mode 100644 index 0000000000..455bcaaeba --- /dev/null +++ b/indexer-service/src/helpers/parsers/formula.parser.ts @@ -0,0 +1,20 @@ +import JSZip from 'jszip'; +export interface IFormulaComponents { + formula: any; +} +export const FORMULA_FILE_NAME = 'formula.json'; +export async function parseFormulaFile(zipFile: any): Promise { + try { + const zip = new JSZip(); + const content = await zip.loadAsync(zipFile); + if (!content.files[FORMULA_FILE_NAME] || content.files[FORMULA_FILE_NAME].dir) { + throw new Error('Zip file is not a formula'); + } + const formulaString = await content.files[FORMULA_FILE_NAME].async('string'); + const formula = JSON.parse(formulaString); + return { formula }; + } catch (error) { + console.log('Failed to parse formula') + return null; + } +} diff --git a/indexer-service/src/helpers/parsers/index.ts b/indexer-service/src/helpers/parsers/index.ts index 33bce7b8b1..92994a2213 100644 --- a/indexer-service/src/helpers/parsers/index.ts +++ b/indexer-service/src/helpers/parsers/index.ts @@ -1,4 +1,5 @@ export * from './module.parser.js'; export * from './policy.parser.js'; export * from './tool.parser.js'; -export * from './label.parser.js'; \ No newline at end of file +export * from './label.parser.js'; +export * from './formula.parser.js'; \ No newline at end of file diff --git a/indexer-service/src/helpers/synchronizers/index.ts b/indexer-service/src/helpers/synchronizers/index.ts index b0cd6a2580..0518e89dec 100644 --- a/indexer-service/src/helpers/synchronizers/index.ts +++ b/indexer-service/src/helpers/synchronizers/index.ts @@ -13,4 +13,5 @@ export * from './synchronize-vp.js'; export * from './synchronize-contracts.js'; export * from './synchronize-projects.js'; export * from './synchronize-all.js'; -export * from './synchronize-labels.js'; \ No newline at end of file +export * from './synchronize-labels.js'; +export * from './synchronize-formula.js'; \ No newline at end of file diff --git a/indexer-service/src/helpers/synchronizers/synchronize-all.ts b/indexer-service/src/helpers/synchronizers/synchronize-all.ts index e3c5a34d15..af0e18c83b 100644 --- a/indexer-service/src/helpers/synchronizers/synchronize-all.ts +++ b/indexer-service/src/helpers/synchronizers/synchronize-all.ts @@ -2,6 +2,7 @@ import { SynchronizationTask } from '../synchronization-task.js'; import { SynchronizationAnalytics } from './synchronize-analytics.js'; import { SynchronizationContracts } from './synchronize-contracts.js'; import { SynchronizationDid } from './synchronize-dids.js'; +import { SynchronizationFormulas } from './synchronize-formula.js'; import { SynchronizationLabels } from './synchronize-labels.js'; import { SynchronizationModules } from './synchronize-module.js'; import { SynchronizationPolicy } from './synchronize-policy.js'; @@ -14,6 +15,14 @@ import { SynchronizationTopics } from './synchronize-topic.js'; import { SynchronizationVCs } from './synchronize-vcs.js'; import { SynchronizationVPs } from './synchronize-vp.js'; +function getMask(mask: string | undefined): string { + return (mask || '0 * * * *'); +} + +function getBoolean(flag: string | undefined): boolean { + return (flag?.toLowerCase() === 'true'); +} + export class SynchronizationAll extends SynchronizationTask { public readonly name: string = 'all'; @@ -31,6 +40,7 @@ export class SynchronizationAll extends SynchronizationTask { private readonly synchronizationPolicy: SynchronizationPolicy; private readonly synchronizationContracts: SynchronizationContracts; private readonly synchronizationLabels: SynchronizationLabels; + private readonly synchronizationFormulas: SynchronizationFormulas; constructor(mask: string) { super('all', mask); @@ -49,6 +59,7 @@ export class SynchronizationAll extends SynchronizationTask { this.synchronizationPolicy = (new SynchronizationPolicy(this.getMask(process.env.SYNC_POLICIES_MASK))); this.synchronizationContracts = (new SynchronizationContracts(this.getMask(process.env.SYNC_CONTRACTS_MASK))); this.synchronizationLabels = (new SynchronizationLabels(this.getMask(process.env.SYNC_LABELS_MASK))); + this.synchronizationFormulas = (new SynchronizationFormulas(this.getMask(process.env.SYNC_FORMULAS_MASK))); } public override async sync(): Promise { @@ -66,6 +77,7 @@ export class SynchronizationAll extends SynchronizationTask { await this.runTask(this.synchronizationPolicy); await this.runTask(this.synchronizationContracts); await this.runTask(this.synchronizationLabels); + await this.runTask(this.synchronizationFormulas); } private async runTask(task: SynchronizationTask) { @@ -83,4 +95,64 @@ export class SynchronizationAll extends SynchronizationTask { private getMask(mask: string | undefined): string { return (mask || '0 * * * *'); } + + public static createAllTasks() { + if (process.env.SYNC_ALL_MASK) { + SynchronizationAll.createSyncTasks(); + } else { + SynchronizationAll.createAsyncTasks(); + } + } + + public static createSyncTasks() { + (new SynchronizationAll(getMask(process.env.SYNC_ALL_MASK))) + .start(getBoolean(process.env.START_SYNC_ALL)); + } + + public static createAsyncTasks() { + (new SynchronizationAnalytics(getMask(process.env.SYNC_ANALYTICS_MASK))) + .start(getBoolean(process.env.START_SYNC_ANALYTICS)); + + (new SynchronizationProjects(getMask(process.env.SYNC_ANALYTICS_MASK))) + .start(getBoolean(process.env.START_SYNC_ANALYTICS)); + + (new SynchronizationModules(getMask(process.env.SYNC_MODULES_MASK))) + .start(getBoolean(process.env.START_SYNC_MODULES)); + + (new SynchronizationRegistries(getMask(process.env.SYNC_REGISTRIES_MASK))) + .start(getBoolean(process.env.START_SYNC_REGISTRIES)); + + (new SynchronizationRoles(getMask(process.env.SYNC_ROLES_MASK))) + .start(getBoolean(process.env.START_SYNC_ROLES)); + + (new SynchronizationTools(getMask(process.env.SYNC_TOOLS_MASK))) + .start(getBoolean(process.env.START_SYNC_TOOLS)); + + (new SynchronizationTopics(getMask(process.env.SYNC_TOPICS_MASK))) + .start(getBoolean(process.env.START_SYNC_TOPICS)); + + (new SynchronizationSchemas(getMask(process.env.SYNC_SCHEMAS_MASK))) + .start(getBoolean(process.env.START_SYNC_SCHEMAS)); + + (new SynchronizationDid(getMask(process.env.SYNC_DID_DOCUMENTS_MASK))) + .start(getBoolean(process.env.START_SYNC_DID_DOCUMENTS)); + + (new SynchronizationVCs(getMask(process.env.SYNC_VC_DOCUMENTS_MASK))) + .start(getBoolean(process.env.START_SYNC_VC_DOCUMENTS)); + + (new SynchronizationVPs(getMask(process.env.SYNC_VP_DOCUMENTS_MASK))) + .start(getBoolean(process.env.START_SYNC_VP_DOCUMENTS)); + + (new SynchronizationPolicy(getMask(process.env.SYNC_POLICIES_MASK))) + .start(getBoolean(process.env.START_SYNC_POLICIES)); + + (new SynchronizationContracts(getMask(process.env.SYNC_CONTRACTS_MASK))) + .start(getBoolean(process.env.START_SYNC_CONTRACTS)); + + (new SynchronizationLabels(getMask(process.env.SYNC_LABELS_MASK))) + .start(getBoolean(process.env.START_SYNC_LABELS)); + + (new SynchronizationFormulas(getMask(process.env.SYNC_FORMULAS_MASK))) + .start(getBoolean(process.env.START_SYNC_FORMULAS)); + } } \ No newline at end of file diff --git a/indexer-service/src/helpers/synchronizers/synchronize-formula.ts b/indexer-service/src/helpers/synchronizers/synchronize-formula.ts new file mode 100644 index 0000000000..b05617f58e --- /dev/null +++ b/indexer-service/src/helpers/synchronizers/synchronize-formula.ts @@ -0,0 +1,120 @@ +import { DataBaseHelper, Message } from '@indexer/common'; +import { MessageType, MessageAction, FormulaAnalytics } from '@indexer/interfaces'; +import { SynchronizationTask } from '../synchronization-task.js'; +import { Collection } from 'mongodb'; +import { textSearch } from '../text-search-options.js'; +import { parseFormulaFile } from '../parsers/index.js'; +import { loadFiles } from '../load-files.js'; + +export class SynchronizationFormulas extends SynchronizationTask { + public readonly name: string = 'formulas'; + + constructor(mask: string) { + super('formulas', mask); + } + + private async loadFormulas( + collection: Collection, + fileIds: Set + ) { + console.log(`Sync formulas: load formulas`) + const formulas = collection.find({ + type: MessageType.FORMULA, + action: MessageAction.PublishFormula, + ...this.filter(), + }, { + limit: 100000 + }); + const allDocuments: Message[] = []; + while (await formulas.hasNext()) { + const formula = await formulas.next(); + allDocuments.push(formula); + fileIds.add(formula.files?.[0]); + } + return allDocuments; + } + + private async loadPolicies(collection: Collection) { + const policyMap = new Map(); + const policies = collection.find({ type: MessageType.INSTANCE_POLICY }); + while (await policies.hasNext()) { + const policy = await policies.next(); + if (policy.options?.instanceTopicId) { + policyMap.set(policy.options.instanceTopicId, policy); + } + } + return policyMap + } + + public override async sync(): Promise { + const em = DataBaseHelper.getEntityManager(); + const collection = em.getCollection('message'); + + const fileIds: Set = new Set(); + const needUpdate = await this.loadFormulas(collection, fileIds); + + console.log(`Sync VCs: load policies`) + const policyMap = await this.loadPolicies(collection); + + console.log(`Sync formulas: load files`) + const fileMap = await loadFiles(fileIds, true); + + console.log(`Sync formulas: update data`) + for (const document of needUpdate) { + const row = em.getReference(Message, document._id); + row.analytics = await this.createAnalytics(document, policyMap, fileMap); + em.persist(row); + } + + console.log(`Sync formulas: flush`) + await em.flush(); + } + + private async createAnalytics( + document: Message, + policyMap: Map, + fileMap: Map, + ): Promise { + const analytics: FormulaAnalytics = { + textSearch: textSearch(document), + config: null + }; + const formulaFileId = document.files[0]; + const formulaFileBuffer = fileMap.get(formulaFileId); + if (!formulaFileBuffer) { + return analytics; + } + const formulaData = await parseFormulaFile(formulaFileBuffer); + if (!formulaData) { + return analytics; + } + analytics.config = formulaData.formula; + + const policyMessage = policyMap.get(document.options?.policyInstanceTopicId); + if (policyMessage) { + analytics.policyId = policyMessage.consensusTimestamp; + analytics.textSearch += `|${policyMessage.consensusTimestamp}`; + } + + return analytics; + } + + private filter() { + return { + $or: [ + { + analytics: { $exists: false }, + }, + { + analytics: null, + }, + { + analytics: undefined, + }, + { + 'analytics.config': null, + }, + ], + }; + } +} \ No newline at end of file diff --git a/indexer-service/src/helpers/synchronizers/synchronize-vcs.ts b/indexer-service/src/helpers/synchronizers/synchronize-vcs.ts index d7b9610d34..ab33b7a1aa 100644 --- a/indexer-service/src/helpers/synchronizers/synchronize-vcs.ts +++ b/indexer-service/src/helpers/synchronizers/synchronize-vcs.ts @@ -42,6 +42,8 @@ export class SynchronizationVCs extends SynchronizationTask { const documents = collection.find({ type: { $in: [MessageType.VC_DOCUMENT] }, ...this.filter(), + }, { + limit: 100000 }); const allDocuments: Message[] = []; const fileIds: Set = new Set(); diff --git a/indexer-service/src/helpers/synchronizers/synchronize-vp.ts b/indexer-service/src/helpers/synchronizers/synchronize-vp.ts index 9be70e2a48..ca2d2f3946 100644 --- a/indexer-service/src/helpers/synchronizers/synchronize-vp.ts +++ b/indexer-service/src/helpers/synchronizers/synchronize-vp.ts @@ -82,6 +82,8 @@ export class SynchronizationVPs extends SynchronizationTask { const documents = collection.find({ type: { $in: [MessageType.VP_DOCUMENT] }, ...this.filter(), + }, { + limit: 100000 }); const allDocuments: Message[] = []; while (await documents.hasNext()) { diff --git a/indexer-worker-service/src/loaders/hedera-service.ts b/indexer-worker-service/src/loaders/hedera-service.ts index 8eec524e8f..cacce02b24 100644 --- a/indexer-worker-service/src/loaders/hedera-service.ts +++ b/indexer-worker-service/src/loaders/hedera-service.ts @@ -10,12 +10,12 @@ export class HederaService { public static readonly REST_API_MAX_LIMIT: number = 100; public static async init() { - this.mirrorNodeUrl = Environment.mirrorNode; + HederaService.mirrorNodeUrl = Environment.mirrorNode; } public static async getMessages(topicId: string, lastNumber: number): Promise { try { - const url = this.mirrorNodeUrl + 'topics/' + topicId + '/messages'; + const url = HederaService.mirrorNodeUrl + 'topics/' + topicId + '/messages'; const option: any = { params: { limit: HederaService.REST_API_MAX_LIMIT @@ -44,7 +44,7 @@ export class HederaService { public static async getToken(tokenId: string): Promise { try { - const url = this.mirrorNodeUrl + 'tokens/' + tokenId; + const url = HederaService.mirrorNodeUrl + 'tokens/' + tokenId; const option: any = { responseType: 'json', timeout: 2 * 60 * 1000, @@ -63,7 +63,7 @@ export class HederaService { } public static async getSerials(tokenId: string, lastNumber: number): Promise { - const url = this.mirrorNodeUrl + 'tokens/' + tokenId + '/nfts'; + const url = HederaService.mirrorNodeUrl + 'tokens/' + tokenId + '/nfts'; const option: any = { params: { order: 'asc', diff --git a/indexer-worker-service/src/loaders/ipfs-service.ts b/indexer-worker-service/src/loaders/ipfs-service.ts index 1fce42dbd8..04597fe8c5 100644 --- a/indexer-worker-service/src/loaders/ipfs-service.ts +++ b/indexer-worker-service/src/loaders/ipfs-service.ts @@ -11,8 +11,8 @@ export class IPFSService { public static node: BaseNode; public static async init() { - this.node = new HttpNode(); - await this.node.start(); + IPFSService.node = new HttpNode(); + await IPFSService.node.start(); } public static parseCID(file: string): CID | null { @@ -28,14 +28,14 @@ export class IPFSService { } private static async loadFile(cid: string): Promise { - const check = await this.node.check(cid); + const check = await IPFSService.node.check(cid); if (check.check === true) { - const file = await this.node.get(cid); + const file = await IPFSService.node.get(cid); console.log(`IPFS loaded: ${cid}`); return file; } else if (check.check === undefined) { console.log(`IPFS check: ${cid}`, check.error); - const file = await this.node.get(cid); + const file = await IPFSService.node.get(cid); console.log(`IPFS loaded: ${cid}`); return file; } else { @@ -52,7 +52,7 @@ export class IPFSService { reject(new Error('Timeout exceeded')); }, IPFSService.LOAD_TIMEOUT); }); - return await Promise.race([this.loadFile(cid), timeoutPromise]); + return await Promise.race([IPFSService.loadFile(cid), timeoutPromise]); } catch (error) { console.log(`IPFS error: ${cid}`, error.message); return undefined; diff --git a/indexer-worker-service/src/loaders/ipfs/http-node.ts b/indexer-worker-service/src/loaders/ipfs/http-node.ts index 19eebded30..85e57bb7f9 100644 --- a/indexer-worker-service/src/loaders/ipfs/http-node.ts +++ b/indexer-worker-service/src/loaders/ipfs/http-node.ts @@ -6,10 +6,6 @@ export class HttpNode implements BaseNode { private readonly LOAD_TIMEOUT: number = 60 * 1000; private readonly CHECK_TIMEOUT: number = 15 * 1000; - constructor() { - - } - private parseCID(cid: string): string { return new CID(cid).toV1().toString('base32'); } @@ -24,7 +20,7 @@ export class HttpNode implements BaseNode { } public async stop() { - + return; } public async get(cid: string, timeout?: number): Promise { diff --git a/indexer-worker-service/src/utils/parser.ts b/indexer-worker-service/src/utils/parser.ts index 4ae291a91e..c1b06208d7 100644 --- a/indexer-worker-service/src/utils/parser.ts +++ b/indexer-worker-service/src/utils/parser.ts @@ -251,6 +251,17 @@ export class Parser { message.files.push(json.cid); } break; + case MessageType.FORMULA: + message.options.uuid = json.uuid; + message.options.name = json.name; + message.options.description = json.description; + message.options.owner = json.owner; + message.options.policyTopicId = json.policyTopicId; + message.options.policyInstanceTopicId = json.policyInstanceTopicId; + if (json.cid) { + message.files.push(json.cid); + } + break; default: return null; } diff --git a/interfaces/src/helpers/permissions-helper.ts b/interfaces/src/helpers/permissions-helper.ts index 4388ffdf47..c708d1a1d0 100644 --- a/interfaces/src/helpers/permissions-helper.ts +++ b/interfaces/src/helpers/permissions-helper.ts @@ -520,7 +520,7 @@ export class UserPermissions { return this.check(Permissions.SCHEMAS_RULE_EXECUTE); } - //SCHEMA RULES + //SCHEMA LABELS public get STATISTICS_LABEL_CREATE(): boolean { return this.check(Permissions.STATISTICS_LABEL_CREATE); } @@ -529,6 +529,15 @@ export class UserPermissions { return this.check(Permissions.STATISTICS_LABEL_READ); } + //SCHEMA FORMULAS + public get FORMULAS_FORMULA_CREATE(): boolean { + return this.check(Permissions.FORMULAS_FORMULA_CREATE); + } + + public get FORMULAS_FORMULA_READ(): boolean { + return this.check(Permissions.FORMULAS_FORMULA_READ); + } + public static isPolicyAdmin(user: any): boolean { return ( UserPermissions.has(user, Permissions.POLICIES_MIGRATION_CREATE) || diff --git a/interfaces/src/interface/formulas.interface.ts b/interfaces/src/interface/formulas.interface.ts new file mode 100644 index 0000000000..5477886e8a --- /dev/null +++ b/interfaces/src/interface/formulas.interface.ts @@ -0,0 +1,49 @@ +import { EntityStatus } from '../type'; + +export enum FormulaItemType { + Constant = 'constant', + Variable = 'variable', + Formula = 'formula', + Text = 'text' +} + +export interface IFormulaLink { + type: 'formula' | 'schema'; + entityId: string; + item: string; +} + +export interface IFormulaItem { + uuid: string; + name: string; + description: string; + type: FormulaItemType; + value?: any; + link?: IFormulaLink | null; + relationships?: string[]; +} + +export interface IFormulaFile { + name?: string; + url?: string; + type?: string; +} + +export interface IFormulaConfig { + formulas?: IFormulaItem[]; + files?: IFormulaFile[]; +} + +export interface IFormula { + id?: string; + uuid?: string; + name?: string; + description?: string; + policyId?: string; + policyTopicId?: string; + policyInstanceTopicId?: string; + owner?: string; + creator?: string; + status?: EntityStatus; + config?: IFormulaConfig; +} \ No newline at end of file diff --git a/interfaces/src/interface/index.ts b/interfaces/src/interface/index.ts index 63a3c24457..f257d14753 100644 --- a/interfaces/src/interface/index.ts +++ b/interfaces/src/interface/index.ts @@ -45,3 +45,4 @@ export * from './statistic.interface.js' export * from './schema-rules.interface.js' export * from './retirement-message.interface.js' export * from './policy-label.interface.js' +export * from './formulas.interface.js' diff --git a/interfaces/src/models/schema.ts b/interfaces/src/models/schema.ts index c64b5fe3d9..d129a0537c 100644 --- a/interfaces/src/models/schema.ts +++ b/interfaces/src/models/schema.ts @@ -449,4 +449,41 @@ export class Schema implements ISchema { } return result; } + + /** + * Get field + */ + public getField(path: string): SchemaField | null { + return this._getField(path, this.fields); + } + + /** + * Get field + */ + private _getField(path: string, fields?: SchemaField[]): SchemaField | null { + if (Array.isArray(fields)) { + for (const field of fields) { + if (field.path === path) { + return field; + } + const result = this._getField(path, field.fields); + if (result) { + return result; + } + } + } + return null; + } + + /** + * Create Schema + */ + public static from(response: ISchema): Schema | null { + try { + return new Schema(response); + } catch (error) { + console.error(error); + return null; + } + } } diff --git a/interfaces/src/type/messages/message-api.type.ts b/interfaces/src/type/messages/message-api.type.ts index b74c0f9b74..e52f849523 100644 --- a/interfaces/src/type/messages/message-api.type.ts +++ b/interfaces/src/type/messages/message-api.type.ts @@ -263,6 +263,17 @@ export enum MessageAPI { GET_POLICY_LABEL_DOCUMENTS = 'GET_POLICY_LABEL_DOCUMENTS', GET_POLICY_LABEL_DOCUMENT = 'GET_POLICY_LABEL_DOCUMENT', GET_POLICY_LABEL_DOCUMENT_RELATIONSHIPS = 'GET_POLICY_LABEL_DOCUMENT_RELATIONSHIPS', + CREATE_FORMULA = 'CREATE_FORMULA', + GET_FORMULAS = 'GET_FORMULAS', + GET_FORMULA = 'GET_FORMULA', + UPDATE_FORMULA = 'UPDATE_FORMULA', + DELETE_FORMULA = 'DELETE_FORMULA', + IMPORT_FORMULA_FILE = 'IMPORT_FORMULA_FILE', + EXPORT_FORMULA_FILE = 'EXPORT_FORMULA_FILE', + PREVIEW_FORMULA_FILE = 'PREVIEW_FORMULA_FILE', + GET_FORMULA_RELATIONSHIPS = 'GET_FORMULA_RELATIONSHIPS', + GET_FORMULAS_DATA = 'GET_FORMULAS_DATA', + PUBLISH_FORMULA = 'PUBLISH_FORMULA', } /** diff --git a/interfaces/src/type/permissions.type.ts b/interfaces/src/type/permissions.type.ts index ee5016eb03..da746a28af 100644 --- a/interfaces/src/type/permissions.type.ts +++ b/interfaces/src/type/permissions.type.ts @@ -25,6 +25,7 @@ export enum PermissionCategories { ACCESS = 'ACCESS', DELEGATION = 'DELEGATION', STATISTICS = 'STATISTICS', + FORMULAS = 'FORMULAS', } /** @@ -66,7 +67,8 @@ export enum PermissionEntities { ROLE = 'ROLE', STATISTIC = 'STATISTIC', RULE = 'RULE', - LABEL = 'LABEL' + LABEL = 'LABEL', + FORMULA = 'FORMULA', } /** @@ -227,7 +229,10 @@ export enum Permissions { //SCHEMA RULES SCHEMAS_RULE_CREATE = 'SCHEMAS_RULE_CREATE', SCHEMAS_RULE_READ = 'SCHEMAS_RULE_READ', - SCHEMAS_RULE_EXECUTE = 'SCHEMAS_RULE_EXECUTE' + SCHEMAS_RULE_EXECUTE = 'SCHEMAS_RULE_EXECUTE', + //FORMULAS + FORMULAS_FORMULA_CREATE = 'FORMULAS_FORMULA_CREATE', + FORMULAS_FORMULA_READ = 'FORMULAS_FORMULA_READ' } /** @@ -1146,7 +1151,10 @@ export const PermissionsArray: { category: PermissionCategories.STATISTICS, entity: PermissionEntities.STATISTIC, action: PermissionActions.CREATE, - disabled: false + disabled: false, + dependOn: [ + Permissions.STATISTICS_STATISTIC_READ + ] }, { name: Permissions.STATISTICS_LABEL_READ, @@ -1160,7 +1168,10 @@ export const PermissionsArray: { category: PermissionCategories.STATISTICS, entity: PermissionEntities.LABEL, action: PermissionActions.CREATE, - disabled: false + disabled: false, + dependOn: [ + Permissions.STATISTICS_LABEL_READ + ] }, //SCHEMA RULE { @@ -1175,15 +1186,40 @@ export const PermissionsArray: { category: PermissionCategories.SCHEMAS, entity: PermissionEntities.RULE, action: PermissionActions.CREATE, - disabled: false + disabled: false, + dependOn: [ + Permissions.SCHEMAS_RULE_READ + ] }, { name: Permissions.SCHEMAS_RULE_EXECUTE, category: PermissionCategories.SCHEMAS, entity: PermissionEntities.RULE, action: PermissionActions.EXECUTE, + disabled: false, + dependOn: [ + Permissions.SCHEMAS_RULE_READ + ] + }, + + //SCHEMA RULE + { + name: Permissions.FORMULAS_FORMULA_READ, + category: PermissionCategories.FORMULAS, + entity: PermissionEntities.FORMULA, + action: PermissionActions.READ, disabled: false }, + { + name: Permissions.FORMULAS_FORMULA_CREATE, + category: PermissionCategories.FORMULAS, + entity: PermissionEntities.FORMULA, + action: PermissionActions.CREATE, + disabled: false, + dependOn: [ + Permissions.FORMULAS_FORMULA_READ + ] + }, //ACCESS { name: Permissions.ACCESS_POLICY_ALL, @@ -1326,6 +1362,8 @@ export const SRDefaultPermission: Permissions[] = [ Permissions.SCHEMAS_RULE_CREATE, Permissions.SCHEMAS_RULE_READ, Permissions.SCHEMAS_RULE_EXECUTE, + Permissions.FORMULAS_FORMULA_CREATE, + Permissions.FORMULAS_FORMULA_READ, ]; export const AuditDefaultPermission: Permissions[] = [ diff --git a/swagger-indexer.yaml b/swagger-indexer.yaml index 21a24bf9b6..db42983101 100644 --- a/swagger-indexer.yaml +++ b/swagger-indexer.yaml @@ -1534,6 +1534,146 @@ paths: schema: $ref: '#/components/schemas/InternalServerErrorDTO' tags: *ref_0 + /entities/formulas: + get: + operationId: EntityApi_getFormulas + summary: Get formulas + description: Returns formulas + parameters: + - name: pageIndex + required: false + in: query + description: Page index + example: 0 + schema: + type: number + - name: pageSize + required: false + in: query + description: Page size + example: 10 + schema: + type: number + maximum: 100 + - name: orderField + required: false + in: query + description: Order field + example: consensusTimestamp + schema: + type: string + - name: orderDir + required: false + in: query + description: Order direction + examples: + ASC: + value: ASC + description: Ascending ordering + DESC: + value: DESC + description: Descending ordering + schema: + type: string + - name: keywords + required: false + in: query + description: Keywords to search + examples: + 0.0.1960: + description: Search formulas, which are related to specific topic identifier + value: '["0.0.1960"]' + schema: + type: string + - name: topicId + required: false + in: query + description: Policy topic identifier + example: 0.0.1960 + schema: + type: string + - name: options.owner + required: false + in: query + description: formula owner + example: >- + did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265 + schema: + type: string + responses: + '200': + description: formulas + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PageDTO' + - properties: + items: + type: array + items: + $ref: '#/components/schemas/FormulaDTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_0 + /entities/formulas/{messageId}: + get: + operationId: EntityApi_getFormula + summary: Get formula + description: Returns formula + parameters: + - name: messageId + required: true + in: path + description: Message identifier + example: '1706823227.586179534' + schema: + type: string + responses: + '200': + description: Formula details + content: + application/json: + schema: + $ref: '#/components/schemas/FormulaDetailsDTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_0 + /entities/formulas/{messageId}/relationships: + get: + operationId: EntityApi_getFormulaRelationships + summary: Get formula relationships + description: Returns formula relationships + parameters: + - name: messageId + required: true + in: path + description: Message identifier + example: '1706823227.586179534' + schema: + type: string + responses: + '200': + description: Formula relationships + content: + application/json: + schema: + $ref: '#/components/schemas/FormulaRelationshipsDTO' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_0 /entities/did-documents: get: operationId: EntityApi_getDidDocuments @@ -2726,6 +2866,7 @@ components: - User-Permissions - Policy-Statistic - Policy-Label + - Formula example: Standard Registry action: type: string @@ -2768,6 +2909,7 @@ components: - create-assessment-document - publish-policy-label - create-label-document + - publish-formula example: Init options: $ref: '#/components/schemas/RegistryOptionsDTO' @@ -3225,6 +3367,7 @@ components: - User-Permissions - Policy-Statistic - Policy-Label + - Formula example: DID-Document action: type: string @@ -3267,6 +3410,7 @@ components: - create-assessment-document - publish-policy-label - create-label-document + - publish-formula example: create-did-document options: $ref: '#/components/schemas/RegistryUserOptionsDTO' @@ -3380,6 +3524,7 @@ components: - User-Permissions - Policy-Statistic - Policy-Label + - Formula example: DID-Document action: type: string @@ -3422,6 +3567,7 @@ components: - create-assessment-document - publish-policy-label - create-label-document + - publish-formula example: create-did-document options: $ref: '#/components/schemas/RegistryUserOptionsDTO' @@ -3713,6 +3859,7 @@ components: - User-Permissions - Policy-Statistic - Policy-Label + - Formula example: Policy action: type: string @@ -3755,6 +3902,7 @@ components: - create-assessment-document - publish-policy-label - create-label-document + - publish-formula example: publish-policy options: $ref: '#/components/schemas/PolicyOptionsDTO' @@ -3798,11 +3946,16 @@ components: type: number description: Roles example: 10 + formulas: + type: number + description: Formulas + example: 10 required: - schemas - vcs - vps - roles + - formulas PolicyDetailsDTO: type: object properties: @@ -3963,6 +4116,7 @@ components: - User-Permissions - Policy-Statistic - Policy-Label + - Formula example: Tool action: type: string @@ -4005,6 +4159,7 @@ components: - create-assessment-document - publish-policy-label - create-label-document + - publish-formula example: publish-tool options: $ref: '#/components/schemas/ToolOptionsDTO' @@ -4193,6 +4348,7 @@ components: - User-Permissions - Policy-Statistic - Policy-Label + - Formula example: Module action: type: string @@ -4235,6 +4391,7 @@ components: - create-assessment-document - publish-policy-label - create-label-document + - publish-formula example: publish-module options: $ref: '#/components/schemas/ModuleOptionsDTO' @@ -4416,6 +4573,7 @@ components: - User-Permissions - Policy-Statistic - Policy-Label + - Formula example: Schema action: type: string @@ -4458,6 +4616,7 @@ components: - create-assessment-document - publish-policy-label - create-label-document + - publish-formula example: publish-schema options: $ref: '#/components/schemas/SchemaOptionsDTO' @@ -4609,6 +4768,7 @@ components: - User-Permissions - Policy-Statistic - Policy-Label + - Formula example: Schema action: type: string @@ -4651,6 +4811,7 @@ components: - create-assessment-document - publish-policy-label - create-label-document + - publish-formula example: publish-schema options: $ref: '#/components/schemas/SchemaOptionsDTO' @@ -5005,6 +5166,7 @@ components: - User-Permissions - Policy-Statistic - Policy-Label + - Formula example: VP-Document action: type: string @@ -5047,6 +5209,7 @@ components: - create-assessment-document - publish-policy-label - create-label-document + - publish-formula example: create-vp-document options: $ref: '#/components/schemas/VPOptionsDTO' @@ -5219,6 +5382,7 @@ components: - User-Permissions - Policy-Statistic - Policy-Label + - Formula example: Role-Document action: type: string @@ -5261,6 +5425,7 @@ components: - create-assessment-document - publish-policy-label - create-label-document + - publish-formula example: create-vc-document options: $ref: '#/components/schemas/RoleOptionsDTO' @@ -5444,6 +5609,7 @@ components: - User-Permissions - Policy-Statistic - Policy-Label + - Formula example: Policy-Label action: type: string @@ -5486,6 +5652,7 @@ components: - create-assessment-document - publish-policy-label - create-label-document + - publish-formula example: publish-policy-label options: $ref: '#/components/schemas/StatisticOptionsDTO' @@ -5686,6 +5853,7 @@ components: - User-Permissions - Policy-Statistic - Policy-Label + - Formula example: VC-Document action: type: string @@ -5728,6 +5896,7 @@ components: - create-assessment-document - publish-policy-label - create-label-document + - publish-formula example: create-vc-document options: $ref: '#/components/schemas/VCOptionsDTO' @@ -5894,6 +6063,7 @@ components: - User-Permissions - Policy-Statistic - Policy-Label + - Formula example: Policy-Label action: type: string @@ -5936,6 +6106,7 @@ components: - create-assessment-document - publish-policy-label - create-label-document + - publish-formula example: publish-policy-label options: $ref: '#/components/schemas/LabelOptionsDTO' @@ -6116,6 +6287,7 @@ components: - User-Permissions - Policy-Statistic - Policy-Label + - Formula example: VP-Document action: type: string @@ -6158,6 +6330,7 @@ components: - create-assessment-document - publish-policy-label - create-label-document + - publish-formula example: create-vp-document options: $ref: '#/components/schemas/VPOptionsDTO' @@ -6179,33 +6352,60 @@ components: - type - action - options - DIDOptionsDTO: + FormulaOptionsDTO: type: object properties: - relationships: - description: Relationships - example: - - '1706823227.586179534' - type: array - items: - type: string - did: + uuid: type: string - description: DID + description: UUID + example: 93938a10-d032-4a9b-9425-092e58bffbf7 + name: + type: string + description: Name + example: Formula Name + description: + type: string + description: Description + example: Formula Description + owner: + type: string + description: Owner example: >- did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265 + policyTopicId: + type: string + description: Policy topic identifier + example: 0.0.4481265 + policyInstanceTopicId: + type: string + description: Policy instance topic identifier + example: 0.0.4481265 required: - - relationships - - did - DIDAnalyticsDTO: + - uuid + - name + - description + - owner + - policyTopicId + - policyInstanceTopicId + FormulaAnalyticsDTO: type: object properties: textSearch: type: string description: Text search + owner: + type: string + description: Owner + example: >- + did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265 + config: + type: object + description: Formula Config required: - textSearch - DIDGridDTO: + - owner + - config + FormulaDTO: type: object properties: id: @@ -6294,7 +6494,8 @@ components: - User-Permissions - Policy-Statistic - Policy-Label - example: DID-Document + - Formula + example: Formula action: type: string description: Action @@ -6336,11 +6537,12 @@ components: - create-assessment-document - publish-policy-label - create-label-document - example: create-did-document + - publish-formula + example: publish-formula options: - $ref: '#/components/schemas/DIDOptionsDTO' + $ref: '#/components/schemas/FormulaOptionsDTO' analytics: - $ref: '#/components/schemas/DIDAnalyticsDTO' + $ref: '#/components/schemas/FormulaAnalyticsDTO' required: - id - topicId @@ -6360,7 +6562,44 @@ components: - action - options - analytics - DIDDetailsItemDTO: + FormulaActivityDTO: + type: object + properties: + schemas: + type: number + description: Schemas + example: 10 + vps: + type: number + description: VPs + example: 10 + required: + - schemas + - vps + FormulaDetailsDTO: + type: object + properties: + id: + type: string + description: Message identifier + example: '1706823227.586179534' + uuid: + type: string + description: UUID + example: 93938a10-d032-4a9b-9425-092e58bffbf7 + item: + $ref: '#/components/schemas/FormulaDTO' + row: + $ref: '#/components/schemas/RawMessageDTO' + activity: + $ref: '#/components/schemas/FormulaActivityDTO' + required: + - id + - uuid + - item + - row + - activity + MessageDTO: type: object properties: id: @@ -6404,19 +6643,19 @@ components: description: Status message files: description: Files - example: *ref_4 + example: *ref_3 type: array items: type: string topics: description: Topics - example: *ref_5 + example: *ref_4 type: array items: type: string tokens: description: Tokens - example: *ref_6 + example: *ref_5 type: array items: type: string @@ -6424,31 +6663,286 @@ components: type: number description: SequenceNumber example: 0 - type: + required: + - id + - topicId + - consensusTimestamp + - owner + - uuid + - status + - statusReason + - lang + - responseType + - statusMessage + - files + - topics + - tokens + - sequenceNumber + FormulaRelationshipsDTO: + type: object + properties: + id: type: string - description: Type - enum: - - EVC-Document - - VC-Document - - DID-Document - - Schema - - schema-document - - Policy - - Instance-Policy - - VP-Document - - Standard Registry - - Topic - - Token - - Module - - Tool - - Tag - - Role-Document - - Synchronization Event - - Contract - - Guardian-Role-Document - - User-Permissions - - Policy-Statistic + description: Message identifier + example: '1706823227.586179534' + item: + $ref: '#/components/schemas/MessageDTO' + schemas: + type: array + items: + $ref: '#/components/schemas/MessageDTO' + formulas: + type: array + items: + $ref: '#/components/schemas/MessageDTO' + required: + - id + - item + - schemas + - formulas + DIDOptionsDTO: + type: object + properties: + relationships: + description: Relationships + example: + - '1706823227.586179534' + type: array + items: + type: string + did: + type: string + description: DID + example: >- + did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265 + required: + - relationships + - did + DIDAnalyticsDTO: + type: object + properties: + textSearch: + type: string + description: Text search + required: + - textSearch + DIDGridDTO: + type: object + properties: + id: + type: string + description: Identifier + example: 667c240639282050117a1985 + topicId: + type: string + description: Topic identifier + example: 0.0.4481265 + consensusTimestamp: + type: string + description: Message identifier + example: '1706823227.586179534' + owner: + type: string + description: Owner + example: 0.0.1 + uuid: + type: string + description: UUID + example: 93938a10-d032-4a9b-9425-092e58bffbf7 + status: + type: string + description: Status + example: NEW + statusReason: + type: string + description: Status + example: Revoked + lang: + type: string + description: Lang + example: en-US + responseType: + type: string + description: Response type + example: str + statusMessage: + type: string + description: Status message + files: + description: Files + example: *ref_3 + type: array + items: + type: string + topics: + description: Topics + example: *ref_4 + type: array + items: + type: string + tokens: + description: Tokens + example: *ref_5 + type: array + items: + type: string + sequenceNumber: + type: number + description: SequenceNumber + example: 0 + required: + - id + - topicId + - consensusTimestamp + - owner + - uuid + - status + - statusReason + - lang + - responseType + - statusMessage + - files + - topics + - tokens + - sequenceNumber + FormulaRelationshipsDTO: + type: object + properties: + id: + type: string + description: Message identifier + example: '1706823227.586179534' + item: + $ref: '#/components/schemas/MessageDTO' + schemas: + type: array + items: + $ref: '#/components/schemas/MessageDTO' + formulas: + type: array + items: + $ref: '#/components/schemas/MessageDTO' + required: + - id + - item + - schemas + - formulas + DIDOptionsDTO: + type: object + properties: + relationships: + description: Relationships + example: + - '1706823227.586179534' + type: array + items: + type: string + did: + type: string + description: DID + example: >- + did:hedera:testnet:8Go53QCUXZ4nzSQMyoWovWCxseogGTMLDiHg14Fkz4VN_0.0.4481265 + required: + - relationships + - did + DIDAnalyticsDTO: + type: object + properties: + textSearch: + type: string + description: Text search + required: + - textSearch + DIDGridDTO: + type: object + properties: + id: + type: string + description: Identifier + example: 667c240639282050117a1985 + topicId: + type: string + description: Topic identifier + example: 0.0.4481265 + consensusTimestamp: + type: string + description: Message identifier + example: '1706823227.586179534' + owner: + type: string + description: Owner + example: 0.0.1 + uuid: + type: string + description: UUID + example: 93938a10-d032-4a9b-9425-092e58bffbf7 + status: + type: string + description: Status + example: NEW + statusReason: + type: string + description: Status + example: Revoked + lang: + type: string + description: Lang + example: en-US + responseType: + type: string + description: Response type + example: str + statusMessage: + type: string + description: Status message + files: + description: Files + example: *ref_4 + type: array + items: + type: string + topics: + description: Topics + example: *ref_5 + type: array + items: + type: string + tokens: + description: Tokens + example: *ref_6 + type: array + items: + type: string + sequenceNumber: + type: number + description: SequenceNumber + example: 0 + type: + type: string + description: Type + enum: + - EVC-Document + - VC-Document + - DID-Document + - Schema + - schema-document + - Policy + - Instance-Policy + - VP-Document + - Standard Registry + - Topic + - Token + - Module + - Tool + - Tag + - Role-Document + - Synchronization Event + - Contract + - Guardian-Role-Document + - User-Permissions + - Policy-Statistic - Policy-Label + - Formula example: DID-Document action: type: string @@ -6491,6 +6985,164 @@ components: - create-assessment-document - publish-policy-label - create-label-document + - publish-formula + example: create-did-document + options: + $ref: '#/components/schemas/DIDOptionsDTO' + analytics: + $ref: '#/components/schemas/DIDAnalyticsDTO' + required: + - id + - topicId + - consensusTimestamp + - owner + - uuid + - status + - statusReason + - lang + - responseType + - statusMessage + - files + - topics + - tokens + - sequenceNumber + - type + - action + - options + - analytics + DIDDetailsItemDTO: + type: object + properties: + id: + type: string + description: Identifier + example: 667c240639282050117a1985 + topicId: + type: string + description: Topic identifier + example: 0.0.4481265 + consensusTimestamp: + type: string + description: Message identifier + example: '1706823227.586179534' + owner: + type: string + description: Owner + example: 0.0.1 + uuid: + type: string + description: UUID + example: 93938a10-d032-4a9b-9425-092e58bffbf7 + status: + type: string + description: Status + example: NEW + statusReason: + type: string + description: Status + example: Revoked + lang: + type: string + description: Lang + example: en-US + responseType: + type: string + description: Response type + example: str + statusMessage: + type: string + description: Status message + files: + description: Files + example: *ref_3 + type: array + items: + type: string + topics: + description: Topics + example: *ref_4 + type: array + items: + type: string + tokens: + description: Tokens + example: *ref_5 + type: array + items: + type: string + sequenceNumber: + type: number + description: SequenceNumber + example: 0 + type: + type: string + description: Type + enum: + - EVC-Document + - VC-Document + - DID-Document + - Schema + - schema-document + - Policy + - Instance-Policy + - VP-Document + - Standard Registry + - Topic + - Token + - Module + - Tool + - Tag + - Role-Document + - Synchronization Event + - Contract + - Guardian-Role-Document + - User-Permissions + - Policy-Statistic + - Policy-Label + - Formula + example: DID-Document + action: + type: string + description: Action + enum: + - create-did-document + - create-vc-document + - create-policy + - publish-policy + - delete-policy + - create-schema + - publish-schema + - delete-schema + - create-topic + - create-vp-document + - publish-system-schema + - Init + - change-message-status + - revoke-document + - delete-document + - token-issue + - create-token + - create-multi-policy + - mint + - publish-module + - publish-tag + - delete-tag + - publish-tool + - create-tool + - create-contract + - discontinue-policy + - deferred-discontinue-policy + - migrate-vc-document + - migrate-vp-document + - create-role + - update-role + - delete-role + - set-role + - publish-policy-statistic + - create-assessment-document + - publish-policy-label + - create-label-document + - publish-formula example: create-did-document options: $ref: '#/components/schemas/DIDOptionsDTO' @@ -6549,6 +7201,109 @@ components: - item - row - history + RelationshipDTO: + type: object + properties: + id: + type: string + description: Message identifier + example: '1706823227.586179534' + uuid: + type: string + description: UUID + example: 93938a10-d032-4a9b-9425-092e58bffbf7 + type: + type: string + description: Type + enum: + - EVC-Document + - VC-Document + - DID-Document + - Schema + - schema-document + - Policy + - Instance-Policy + - VP-Document + - Standard Registry + - Topic + - Token + - Module + - Tool + - Tag + - Role-Document + - Synchronization Event + - Contract + - Guardian-Role-Document + - User-Permissions + - Policy-Statistic + - Policy-Label + - Formula + category: + type: number + description: Category + example: 1 + name: + type: string + description: Name + example: Monitoring Report Document + required: + - id + - uuid + - type + - category + - name + RelationshipLinkDTO: + type: object + properties: + source: + type: string + description: Source message identifier + example: '1706823227.586179534' + target: + type: string + description: Target message identifier + example: '1706823227.586179534' + required: + - source + - target + RelationshipsDTO: + type: object + properties: + id: + type: string + description: Message identifier + example: '1706823227.586179534' + item: + $ref: '#/components/schemas/MessageDTO' + target: + $ref: '#/components/schemas/RelationshipDTO' + relationships: + type: array + items: + $ref: '#/components/schemas/RelationshipDTO' + links: + type: array + items: + $ref: '#/components/schemas/RelationshipLinkDTO' + categories: + description: Categories + example: + - name: Registry + - name: Policy + - name: Schema + - name: Role + - name: VC + - name: VP + type: array + items: + type: string + required: + - id + - item + - target + - relationships + - links + - categories VPDetailsDTO: type: object properties: @@ -6668,6 +7423,7 @@ components: - User-Permissions - Policy-Statistic - Policy-Label + - Formula example: VC-Document action: type: string @@ -6710,6 +7466,7 @@ components: - create-assessment-document - publish-policy-label - create-label-document + - publish-formula example: create-vc-document options: $ref: '#/components/schemas/VCOptionsDTO' @@ -6818,6 +7575,9 @@ components: - law - tags additionalProperties: false + formulasData: + type: object + description: Formulas data required: - id - uuid @@ -6825,6 +7585,7 @@ components: - row - history - schema + - formulasData NFTDTO: type: object properties: @@ -7041,6 +7802,7 @@ components: - User-Permissions - Policy-Statistic - Policy-Label + - Formula example: Topic action: type: string @@ -7083,6 +7845,7 @@ components: - create-assessment-document - publish-policy-label - create-label-document + - publish-formula example: create-topic options: $ref: '#/components/schemas/TopicOptionsDTO' @@ -7347,6 +8110,7 @@ components: - User-Permissions - Policy-Statistic - Policy-Label + - Formula example: Contract action: type: string @@ -7389,6 +8153,7 @@ components: - create-assessment-document - publish-policy-label - create-label-document + - publish-formula example: create-contract options: $ref: '#/components/schemas/ContractOptionsDTO' diff --git a/swagger.yaml b/swagger.yaml index 40bc9705db..ca81b77f2b 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -13086,18 +13086,20 @@ paths: parameters: [] requestBody: required: true - description: Configuration. + description: Options. content: application/json: schema: - $ref: '#/components/schemas/SchemaRuleDataDTO' + $ref: '#/components/schemas/SchemaRuleOptionsDTO' responses: '200': description: Successful operation. content: application/json: schema: - $ref: '#/components/schemas/SchemaRuleDataDTO' + type: array + items: + $ref: '#/components/schemas/SchemaRuleDataDTO' '401': description: Unauthorized. '403': @@ -13213,6 +13215,400 @@ paths: tags: *ref_28 security: - bearer: [] + /formulas: + post: + operationId: FormulasApi_createFormula + summary: Creates a new formula. + description: Creates a new formula. + parameters: [] + requestBody: + required: true + description: Configuration. + content: + application/json: + schema: + $ref: '#/components/schemas/FormulaDTO' + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/FormulaDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: &ref_29 + - formulas + security: + - bearer: [] + get: + operationId: FormulasApi_getFormulas + summary: Return a list of all formulas. + description: Returns all formulas. + parameters: + - name: pageIndex + required: false + in: query + description: >- + The number of pages to skip before starting to collect the result + set + example: 0 + schema: + type: number + - name: pageSize + required: false + in: query + description: The numbers of items to return + example: 20 + schema: + type: number + - name: policyId + required: false + in: query + description: Policy Id + example: '000000000000000000000001' + schema: + type: string + responses: + '200': + description: Successful operation. + headers: + X-Total-Count: + schema: + type: integer + description: Total items in the collection. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/FormulaDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_29 + security: + - bearer: [] + /formulas/{formulaId}: + get: + operationId: FormulasApi_getFormulaById + summary: Retrieves formula. + description: Retrieves formula for the specified ID. + parameters: + - name: formulaId + required: true + in: path + description: Formula Identifier + example: '000000000000000000000001' + schema: + type: string + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/FormulaDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_29 + security: + - bearer: [] + put: + operationId: FormulasApi_updateFormula + summary: Updates formula. + description: Updates formula configuration for the specified formula ID. + parameters: + - name: formulaId + required: true + in: path + description: Formula Identifier + example: '000000000000000000000001' + schema: + type: string + requestBody: + required: true + description: Object that contains a configuration. + content: + application/json: + schema: + $ref: '#/components/schemas/FormulaDTO' + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/FormulaDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_29 + security: + - bearer: [] + delete: + operationId: FormulasApi_deleteFormula + summary: Deletes the formula. + description: Deletes the formula with the provided ID. + parameters: + - name: formulaId + required: true + in: path + description: Formula Identifier + example: '000000000000000000000001' + 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_29 + security: + - bearer: [] + /formulas/{formulaId}/relationships: + get: + operationId: FormulasApi_getSchemaRuleRelationships + summary: Retrieves Formula relationships. + description: Retrieves Formula relationships for the specified ID. + parameters: + - name: formulaId + required: true + in: path + description: Formula Identifier + example: '000000000000000000000001' + schema: + type: string + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/FormulaRelationshipsDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_29 + security: + - bearer: [] + /formulas/{policyId}/import/file: + post: + operationId: FormulasApi_importFormula + summary: Imports new formula from a zip file. + description: Imports new formula from the provided zip file into the local DB. + parameters: + - name: policyId + required: true + in: path + description: Policy Id + example: '000000000000000000000001' + schema: + type: string + requestBody: + required: true + description: A zip file containing formula to be imported. + content: + application/json: + schema: + type: string + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/FormulaDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_29 + security: + - bearer: [] + /formulas/{formulaId}/export/file: + get: + operationId: FormulasApi_exportFormula + summary: Returns a zip file containing formula. + description: Returns a zip file containing formula. + parameters: + - name: formulaId + required: true + in: path + description: Formula Identifier + example: '000000000000000000000001' + schema: + type: string + responses: + '200': + description: Successful operation. Response zip file. + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_29 + security: + - bearer: [] + /formulas/import/file/preview: + post: + operationId: FormulasApi_previewFormula + summary: Imports a zip file containing formula. + description: Imports a zip file containing formula. + parameters: [] + requestBody: + required: true + description: File. + content: + application/json: + schema: + type: string + responses: + '200': + description: Formula preview. + content: + application/json: + schema: + $ref: '#/components/schemas/FormulaDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_29 + security: + - bearer: [] + /formulas/{formulaId}/publish: + put: + operationId: FormulasApi_publishPolicyLabel + summary: Publishes formula. + description: Publishes formula for the specified formula ID. + parameters: + - name: formulaId + required: true + in: path + description: Formula Identifier + example: '000000000000000000000001' + schema: + type: string + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/FormulaDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_29 + security: + - bearer: [] + /formulas/data: + post: + operationId: FormulasApi_getSchemaRuleData + summary: '' + description: '' + parameters: [] + requestBody: + required: true + description: Options. + content: + application/json: + schema: + $ref: '#/components/schemas/FormulasOptionsDTO' + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/FormulasDataDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_29 + security: + - bearer: [] /policy-labels: post: operationId: PolicyLabelsApi_createPolicyLabel @@ -13243,7 +13639,7 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerErrorDTO' - tags: &ref_29 + tags: &ref_30 - policy-labels security: - bearer: [] @@ -13299,7 +13695,7 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerErrorDTO' - tags: *ref_29 + tags: *ref_30 security: - bearer: [] /policy-labels/{definitionId}: @@ -13332,7 +13728,7 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerErrorDTO' - tags: *ref_29 + tags: *ref_30 security: - bearer: [] put: @@ -13371,7 +13767,7 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerErrorDTO' - tags: *ref_29 + tags: *ref_30 security: - bearer: [] delete: @@ -13403,7 +13799,7 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerErrorDTO' - tags: *ref_29 + tags: *ref_30 security: - bearer: [] /policy-labels/{definitionId}/publish: @@ -13436,7 +13832,7 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerErrorDTO' - tags: *ref_29 + tags: *ref_30 security: - bearer: [] /policy-labels/push/{definitionId}/publish: @@ -13469,7 +13865,7 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerErrorDTO' - tags: *ref_29 + tags: *ref_30 security: - bearer: [] /policy-labels/{definitionId}/relationships: @@ -13502,7 +13898,7 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerErrorDTO' - tags: *ref_29 + tags: *ref_30 security: - bearer: [] /policy-labels/{policyId}/import/file: @@ -13542,7 +13938,7 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerErrorDTO' - tags: *ref_29 + tags: *ref_30 security: - bearer: [] /policy-labels/{definitionId}/export/file: @@ -13571,7 +13967,7 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerErrorDTO' - tags: *ref_29 + tags: *ref_30 security: - bearer: [] /policy-labels/import/file/preview: @@ -13604,7 +14000,7 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerErrorDTO' - tags: *ref_29 + tags: *ref_30 security: - bearer: [] /policy-labels/components: @@ -13633,7 +14029,7 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerErrorDTO' - tags: *ref_29 + tags: *ref_30 security: - bearer: [] /policy-labels/{definitionId}/tokens: @@ -13689,7 +14085,7 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerErrorDTO' - tags: *ref_29 + tags: *ref_30 security: - bearer: [] /policy-labels/{definitionId}/tokens/{documentId}: @@ -13736,7 +14132,7 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerErrorDTO' - tags: *ref_29 + tags: *ref_30 security: - bearer: [] /policy-labels/{definitionId}/documents: @@ -13776,7 +14172,7 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerErrorDTO' - tags: *ref_29 + tags: *ref_30 security: - bearer: [] get: @@ -13831,7 +14227,7 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerErrorDTO' - tags: *ref_29 + tags: *ref_30 security: - bearer: [] /policy-labels/{definitionId}/documents/{documentId}: @@ -13871,7 +14267,7 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerErrorDTO' - tags: *ref_29 + tags: *ref_30 security: - bearer: [] /policy-labels/{definitionId}/documents/{documentId}/relationships: @@ -13911,7 +14307,7 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerErrorDTO' - tags: *ref_29 + tags: *ref_30 security: - bearer: [] /worker-tasks: @@ -13960,7 +14356,7 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerErrorDTO' - tags: &ref_30 + tags: &ref_31 - worker-tasks security: - bearer: [] @@ -13983,7 +14379,7 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerErrorDTO' - tags: *ref_30 + tags: *ref_31 security: - bearer: [] /worker-tasks/delete/{taskId}: @@ -14012,7 +14408,7 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerErrorDTO' - tags: *ref_30 + tags: *ref_31 security: - bearer: [] info: @@ -14497,19 +14893,19 @@ components: example: username role: type: string - enum: &ref_39 + enum: &ref_40 - STANDARD_REGISTRY - USER - AUDITOR example: USER permissionsGroup: - example: &ref_40 + example: &ref_41 - {} type: array items: type: string permissions: - example: &ref_41 + example: &ref_42 - POLICIES_POLICY_READ type: array items: @@ -14694,35 +15090,35 @@ components: type: object properties: idLvl: - oneOf: &ref_31 + oneOf: &ref_32 - type: string - type: number - enum: &ref_32 + enum: &ref_33 - 0 - 1 example: 0 eventsLvl: - oneOf: &ref_33 + oneOf: &ref_34 - type: string - type: number - enum: &ref_34 + enum: &ref_35 - 0 - 1 example: 0 propLvl: - oneOf: &ref_35 + oneOf: &ref_36 - type: string - type: number - enum: &ref_36 + enum: &ref_37 - 0 - 1 - 2 example: 0 childrenLvl: - oneOf: &ref_37 + oneOf: &ref_38 - type: string - type: number - enum: &ref_38 + enum: &ref_39 - 0 - 1 - 2 @@ -14786,20 +15182,20 @@ components: type: object properties: idLvl: - oneOf: *ref_31 - enum: *ref_32 + oneOf: *ref_32 + enum: *ref_33 example: 0 eventsLvl: - oneOf: *ref_33 - enum: *ref_34 + oneOf: *ref_34 + enum: *ref_35 example: 0 propLvl: - oneOf: *ref_35 - enum: *ref_36 + oneOf: *ref_36 + enum: *ref_37 example: 0 childrenLvl: - oneOf: *ref_37 - enum: *ref_38 + oneOf: *ref_38 + enum: *ref_39 example: 0 moduleId1: type: string @@ -14913,20 +15309,20 @@ components: type: object properties: idLvl: - oneOf: *ref_31 - enum: *ref_32 + oneOf: *ref_32 + enum: *ref_33 example: 0 eventsLvl: - oneOf: *ref_33 - enum: *ref_34 + oneOf: *ref_34 + enum: *ref_35 example: 0 propLvl: - oneOf: *ref_35 - enum: *ref_36 + oneOf: *ref_36 + enum: *ref_37 example: 0 childrenLvl: - oneOf: *ref_37 - enum: *ref_38 + oneOf: *ref_38 + enum: *ref_39 example: 0 documentId1: type: string @@ -14961,20 +15357,20 @@ components: type: object properties: idLvl: - oneOf: *ref_31 - enum: *ref_32 + oneOf: *ref_32 + enum: *ref_33 example: 0 eventsLvl: - oneOf: *ref_33 - enum: *ref_34 + oneOf: *ref_34 + enum: *ref_35 example: 0 propLvl: - oneOf: *ref_35 - enum: *ref_36 + oneOf: *ref_36 + enum: *ref_37 example: 0 childrenLvl: - oneOf: *ref_37 - enum: *ref_38 + oneOf: *ref_38 + enum: *ref_39 example: 0 toolId1: type: string @@ -15725,15 +16121,15 @@ components: example: username role: type: string - enum: *ref_39 + enum: *ref_40 example: USER permissionsGroup: - example: *ref_40 + example: *ref_41 type: array items: type: string permissions: - example: *ref_41 + example: *ref_42 type: array items: type: string @@ -17022,7 +17418,7 @@ components: #did:hedera:testnet:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA_0.0.0000001 permissions: type: string - enum: &ref_42 + enum: &ref_43 - ANALYTIC_POLICY_READ - ANALYTIC_MODULE_READ - ANALYTIC_TOOL_READ @@ -17077,6 +17473,8 @@ components: - SCHEMAS_RULE_READ - SCHEMAS_RULE_CREATE - SCHEMAS_RULE_EXECUTE + - FORMULAS_FORMULA_READ + - FORMULAS_FORMULA_CREATE - ACCESS_POLICY_ALL - ACCESS_POLICY_ASSIGNED - ACCESS_POLICY_PUBLISHED @@ -17095,7 +17493,7 @@ components: properties: name: type: string - enum: *ref_42 + enum: *ref_43 example: ANALYTIC_POLICY_READ category: type: string @@ -17123,6 +17521,7 @@ components: - ACCESS - DELEGATION - STATISTICS + - FORMULAS example: ANALYTIC entity: type: string @@ -17163,6 +17562,7 @@ components: - STATISTIC - RULE - LABEL + - FORMULA example: POLICY action: type: string @@ -17380,6 +17780,21 @@ components: type: array items: $ref: '#/components/schemas/SchemaDTO' + SchemaRuleOptionsDTO: + type: object + properties: + policyId: + type: string + example: '000000000000000000000001' + schemaId: + type: string + example: '000000000000000000000001' + documentId: + type: string + example: '000000000000000000000001' + parentId: + type: string + example: '000000000000000000000001' SchemaRuleDataDTO: type: object properties: @@ -17391,6 +17806,99 @@ components: type: array items: $ref: '#/components/schemas/VcDocumentDTO' + FormulaDTO: + type: object + properties: + id: + type: string + example: '000000000000000000000001' + uuid: + type: string + example: 00000000-0000-0000-0000-000000000000 + name: + type: string + example: Tool name + description: + type: string + example: Description + creator: + type: string + example: >- + #did:hedera:testnet:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA_0.0.0000001 + owner: + type: string + example: >- + #did:hedera:testnet:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA_0.0.0000001 + messageId: + type: string + example: '0000000000.000000001' + policyId: + type: string + example: '000000000000000000000001' + policyTopicId: + type: string + example: 0.0.1 + policyInstanceTopicId: + type: string + example: 0.0.1 + status: + type: string + enum: + - DRAFT + - PUBLISHED + - ERROR + - ACTIVE + example: DRAFT + config: + type: object + nullable: true + required: + - name + FormulaRelationshipsDTO: + type: object + properties: + policy: + $ref: '#/components/schemas/PolicyDTO' + schemas: + type: array + items: + $ref: '#/components/schemas/SchemaDTO' + formulas: + type: array + items: + $ref: '#/components/schemas/FormulaDTO' + FormulasDataDTO: + type: object + properties: + formulas: + type: array + items: + $ref: '#/components/schemas/FormulaDTO' + document: + $ref: '#/components/schemas/VcDocumentDTO' + relationships: + type: array + items: + $ref: '#/components/schemas/VcDocumentDTO' + schemas: + type: array + items: + $ref: '#/components/schemas/SchemaDTO' + FormulasOptionsDTO: + type: object + properties: + policyId: + type: string + example: '000000000000000000000001' + schemaId: + type: string + example: '000000000000000000000001' + documentId: + type: string + example: '000000000000000000000001' + parentId: + type: string + example: '000000000000000000000001' PolicyLabelDTO: type: object properties: diff --git a/worker-service/src/api/worker.ts b/worker-service/src/api/worker.ts index 5917d70e8f..a2e3555b75 100644 --- a/worker-service/src/api/worker.ts +++ b/worker-service/src/api/worker.ts @@ -19,7 +19,6 @@ function rejectTimeout(t: number): Promise { }) } - function getAnalytycsHeaders() { const headers: any = {}; const token = process.env.ANALYTICS_SERVICE_TOKEN;