diff --git a/api-gateway/src/api/service/policy-labels.ts b/api-gateway/src/api/service/policy-labels.ts index b5d0371a1..2724f7914 100644 --- a/api-gateway/src/api/service/policy-labels.ts +++ b/api-gateway/src/api/service/policy-labels.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 } from '@guardian/interfaces'; import { ApiBody, ApiInternalServerErrorResponse, ApiOkResponse, ApiOperation, ApiTags, ApiQuery, ApiExtraModels, ApiParam } from '@nestjs/swagger'; -import { Examples, InternalServerErrorDTO, PolicyLabelDTO, PolicyLabelRelationshipsDTO, pageHeader } from '#middlewares'; +import { Examples, InternalServerErrorDTO, PolicyLabelDTO, PolicyLabelRelationshipsDTO, VcDocumentDTO, pageHeader } from '#middlewares'; import { Guardians, InternalException, EntityOwner } from '#helpers'; import { AuthUser, Auth } from '#auth'; @@ -490,4 +490,113 @@ export class PolicyLabelsApi { await InternalException(error, this.logger); } } + + /** + * Get documents + */ + @Get('/:labelId/documents') + @Auth(Permissions.STATISTICS_STATISTIC_READ) + @ApiOperation({ + summary: 'Return a list of all documents.', + description: 'Returns all documents.', + }) + @ApiParam({ + name: 'labelId', + type: String, + description: 'policy label Identifier', + required: true, + example: Examples.DB_ID + }) + @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 + }) + @ApiOkResponse({ + description: 'Successful operation.', + isArray: true, + headers: pageHeader, + type: VcDocumentDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(VcDocumentDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async getPolicyLabelDocuments( + @AuthUser() user: IAuthUser, + @Response() res: any, + @Param('labelId') labelId: string, + @Query('pageIndex') pageIndex?: number, + @Query('pageSize') pageSize?: number + ): Promise { + try { + const owner = new EntityOwner(user); + const guardians = new Guardians(); + const { items, count } = await guardians.getPolicyLabelDocuments(labelId, owner, pageIndex, pageSize); + return res.header('X-Total-Count', count).send(items); + } catch (error) { + await InternalException(error, this.logger); + } + } + + + /** + * Get document + */ + @Get('/:labelId/documents/:documentId') + @Auth(Permissions.STATISTICS_STATISTIC_READ) + @ApiOperation({ + summary: 'Return a list of all documents.', + description: 'Returns all documents.', + }) + @ApiParam({ + name: 'labelId', + type: String, + description: 'policy label Identifier', + required: true, + example: Examples.DB_ID + }) + @ApiParam({ + name: 'documentId', + type: String, + description: 'Document Identifier', + required: true, + example: Examples.DB_ID + }) + @ApiOkResponse({ + description: 'Successful operation.', + isArray: true, + headers: pageHeader, + type: VcDocumentDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(VcDocumentDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async getPolicyLabelDocument( + @AuthUser() user: IAuthUser, + @Param('labelId') labelId: string, + @Param('documentId') documentId: string, + ): Promise { + try { + const owner = new EntityOwner(user); + const guardians = new Guardians(); + return await guardians.getPolicyLabelDocument(documentId, labelId, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } } diff --git a/api-gateway/src/helpers/guardians.ts b/api-gateway/src/helpers/guardians.ts index 7cfd970d0..ffb168724 100644 --- a/api-gateway/src/helpers/guardians.ts +++ b/api-gateway/src/helpers/guardians.ts @@ -3344,4 +3344,40 @@ export class Guardians extends NatsService { public async searchComponents(options: any, owner: IOwner) { return await this.sendMessage(MessageAPI.SEARCH_POLICY_LABEL_COMPONENTS, { options, owner }); } + + /** + * Return documents + * + * @param labelId + * @param owner + * @param pageIndex + * @param pageSize + * + * @returns {ResponseAndCount} + */ + public async getPolicyLabelDocuments( + labelId: string, + owner: IOwner, + pageIndex?: number, + pageSize?: number + ): Promise> { + return await this.sendMessage(MessageAPI.GET_POLICY_LABEL_DOCUMENTS, { labelId, owner, pageIndex, pageSize }); + } + + /** + * Return documents + * + * @param documentId + * @param labelId + * @param owner + * + * @returns {any} + */ + public async getPolicyLabelDocument( + documentId: string, + labelId: string, + owner: IOwner, + ): Promise { + return await this.sendMessage(MessageAPI.GET_POLICY_LABEL_DOCUMENT, { documentId, labelId, owner }); + } } diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index e26d52e2f..88d8542e3 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -54,6 +54,9 @@ import { StatisticDefinitionConfigurationComponent } from './modules/statistics/ import { StatisticDefinitionsComponent } from './modules/statistics/policy-statistics/statistic-definitions/statistic-definitions.component'; import { SchemaRuleConfigurationComponent } from './modules/statistics/schema-rules/schema-rule-configuration/schema-rule-configuration.component'; import { SchemaRulesComponent } from './modules/statistics/schema-rules/schema-rules/schema-rules.component'; +import { PolicyLabelAssessmentConfigurationComponent } from './modules/statistics/policy-labels/policy-label-assessment-configuration/policy-label-assessment-configuration.component'; +import { PolicyLabelAssessmentsComponent } from './modules/statistics/policy-labels/policy-label-assessments/policy-label-assessments.component'; +import { PolicyLabelAssessmentViewComponent } from './modules/statistics/policy-labels/policy-label-assessment-view/policy-label-assessment-view.component'; @Injectable({ providedIn: 'root' @@ -630,6 +633,50 @@ const routes: Routes = [ ] } }, + { + path: 'policy-label/:labelId/assessment', + component: PolicyLabelAssessmentConfigurationComponent, + canActivate: [PermissionsGuard], + data: { + roles: [ + UserRole.STANDARD_REGISTRY, + UserRole.USER + ], + permissions: [ + Permissions.STATISTICS_LABEL_READ + ] + } + }, + { + path: 'policy-label/:labelId/assessments', + component: PolicyLabelAssessmentsComponent, + canActivate: [PermissionsGuard], + data: { + roles: [ + UserRole.STANDARD_REGISTRY, + UserRole.USER + ], + permissions: [ + Permissions.STATISTICS_LABEL_READ + ] + } + }, + { + path: 'policy-label/:labelId/assessment/:assessmentId', + component: PolicyLabelAssessmentViewComponent, + canActivate: [PermissionsGuard], + data: { + roles: [ + UserRole.STANDARD_REGISTRY, + UserRole.USER + ], + permissions: [ + Permissions.STATISTICS_LABEL_READ + ] + } + }, + + { path: '', component: HomeComponent }, diff --git a/frontend/src/app/modules/common/models/label-validator.ts b/frontend/src/app/modules/common/models/label-validator.ts index 2510e65f7..0aaeb7867 100644 --- a/frontend/src/app/modules/common/models/label-validator.ts +++ b/frontend/src/app/modules/common/models/label-validator.ts @@ -46,6 +46,7 @@ export interface IValidatorStep { type: string, config: any, auto: boolean, + disabled?: boolean, subIndexes?: ISubStep[], update: () => void; } @@ -144,7 +145,27 @@ class ValidateNamespace { } public getField(schema: string, path: string): any { + const fullPath = [...(path || '').split('.')]; + const document = this.documents?.find((doc) => doc.schema === schema); + if (!document) { + return undefined; + } + return this.getFieldValue(document, fullPath); + } + private getFieldValue(document: any, fullPath: string[]): any { + let value: any = document?.document?.credentialSubject; + if (Array.isArray(value)) { + value = value[0]; + } + for (let i = 0; i < fullPath.length; i++) { + if (value) { + value = value[fullPath[i]] + } else { + return undefined; + } + } + return value; } } @@ -415,6 +436,7 @@ class RuleValidator { for (const variable of this.variables) { const value = this.namespace.getField(variable.schemaId, variable.path); (variable as any).value = value; + (variable as any).isArray = Array.isArray(value); this.scope.setVariable(variable.id, (variable as any).value); } } @@ -616,6 +638,7 @@ class StatisticValidator { for (const variable of this.variables) { const value = this.namespace.getField(variable.schemaId, variable.path); (variable as any).value = value; + (variable as any).isArray = Array.isArray(value); this.scope.setVariable(variable.id, (variable as any).value); } } diff --git a/frontend/src/app/modules/statistics/policy-labels/policy-label-assessment-configuration/policy-label-assessment-configuration.component.html b/frontend/src/app/modules/statistics/policy-labels/policy-label-assessment-configuration/policy-label-assessment-configuration.component.html new file mode 100644 index 000000000..f021481e9 --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/policy-label-assessment-configuration/policy-label-assessment-configuration.component.html @@ -0,0 +1,313 @@ +
+
+
+
+ +
+ Before starting work you need to get DID + here +
+ +
+
+ +
+ +
+ {{item?.name}} +
+ Policy Name: {{policy.name}} + Version: {{policy.version}} +
+
+
+ +
+ + +
+
+
+
+ + + + +
+
{{item.name}}
+
+
+
+
+
{{current.title}}
+ +
+ +
{{item.index}}
+
{{item.name}}
+
+
+
+ + +
+ +
+ + + + + + {{column.title}} + + + + + {{column.title}} + + + + + + + + + + + + + + {{row.__schemaName}} + + + + {{row.__id}} + + + + {{getCellValue(row, column)}} + + + + + + + + {{getCellValue(row, column)}} + + + + + + +
+ +
+
+
+ +
+ + No Documents +
+
+
+ +
+ + +
+
+ {{variable.fieldDescription}} +
+ +
+
+ {{getVariableValue(v)}} +
+
+
+ +
+ {{getVariableValue(variable.value)}} +
+
+ +
+
+ + +
+
+
+
+ {{variable.fieldDescription}} +
+ +
+
+ {{getVariableValue(v)}} +
+
+
+ +
+ {{getVariableValue(variable.value)}} +
+
+
+
+
+ {{score.description}} +
+
+
+
+ +
+ +
+
+
+
+ + +
+
+ {{formula.description}} +
+
+ {{formula.value}} +
+
+
+ + + +
+
+
+ + +
+
Label created successfully.
+
+
+
+ + +
+
Sorry, but your document does not meet the requirements.
+
+
+ + +
+
+
+ + +
+
+ + + +
+
\ No newline at end of file diff --git a/frontend/src/app/modules/statistics/policy-labels/policy-label-assessment-configuration/policy-label-assessment-configuration.component.scss b/frontend/src/app/modules/statistics/policy-labels/policy-label-assessment-configuration/policy-label-assessment-configuration.component.scss new file mode 100644 index 000000000..2a0d01f16 --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/policy-label-assessment-configuration/policy-label-assessment-configuration.component.scss @@ -0,0 +1,270 @@ +.guardian-page { + position: relative; + padding: 0px; + user-select: none; + background: var(--guardian-grey-background, #F9FAFC); + + .header-container { + padding: 56px 48px 10px 48px; + background: var(--guardian-primary-color, #4169E2); + min-height: 178px; + height: 178px; + + .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; + } + } + } + + .actions-container { + min-height: 64px; + height: 64px; + width: 100%; + display: flex; + justify-content: flex-end; + padding: 12px 48px; + background: var(--guardian-background, #FFFFFF); + border-top: 1px solid var(--guardian-border-color, #E1E7EF); + position: relative; + + button { + height: 40px; + width: 135px; + margin-left: 16px; + } + } + + .body-container { + display: flex; + width: 100%; + height: 100%; + overflow: hidden; + padding-right: 16px; + position: relative; + + .preview-menu { + min-width: 250px; + width: 250px; + max-width: 250px; + border: 1px solid #E1E7EF; + border-radius: 8px; + padding: 8px 0px; + + .preview-menu-item { + width: 250px; + height: 56px; + display: flex; + flex-direction: row; + padding-left: 22px; + align-items: center; + position: relative; + + &[highlighted="true"] { + background: #F0F3FC; + + .preview-menu-item-name { + color: #4169E2; + } + } + + &[last="false"] { + margin-bottom: 24px; + + &::after { + content: ""; + display: block; + position: absolute; + left: 53px; + bottom: -22px; + width: 3px; + height: 20px; + border-left: 2px solid #C4D0E1; + pointer-events: none; + } + } + + .preview-menu-item-status { + width: 8px; + height: 8px; + overflow: hidden; + border-radius: 50%; + background: #AAB7C4; + margin-right: 12px; + + &[status="true"] { + background: #19BE47; + } + + &[status="false"] { + background: #FF432A; + } + } + + .preview-menu-item-icon { + width: 24px; + height: 24px; + overflow: hidden; + margin-right: 8px; + } + + .preview-menu-item-name { + width: 160px; + overflow: hidden; + font-family: Inter; + font-size: 16px; + font-weight: 500; + line-height: 16px; + text-align: left; + color: #848FA9; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } + } + + .preview-result { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + + .preview-result-item { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + } + + .preview-result-icon { + width: 48px; + height: 48px; + padding: 8px; + overflow: hidden; + } + + .preview-result-text { + font-family: Inter; + font-size: 16px; + font-weight: 600; + text-align: center; + color: #848FA9; + } + } + + .preview-node-container { + display: flex; + flex-direction: column; + position: absolute; + left: 250px; + right: 0; + top: 0; + bottom: 0; + + .preview-node-body { + padding: 16px 24px; + position: relative; + height: 100%; + + .preview-node-header { + font-size: 24px; + font-weight: 600; + line-height: 32px; + text-align: left; + color: var(--guardian-font-color, #23252E); + margin-bottom: 24px; + } + } + + .sub-indexes { + width: 100%; + height: 48px; + min-height: 48px; + max-height: 48px; + display: flex; + flex-direction: row; + justify-content: center; + background: #fff; + align-items: center; + margin-bottom: 16px; + + .sub-index { + width: 24px; + height: 24px; + min-width: 24px; + min-height: 24px; + background: var(--guardian-grey-color-3, #AAB7C4); + border-radius: 50%; + font-family: Inter; + font-size: 14px; + font-weight: 500; + line-height: 14px; + display: flex; + justify-content: center; + align-items: center; + color: #fff; + + &[action="true"] { + background: var(--guardian-primary-color, #4169E2); + } + } + + .sub-name { + font-family: Inter; + font-size: 14px; + font-weight: 500; + line-height: 14px; + text-align: left; + color: var(--guardian-grey-color-3, #AAB7C4); + padding-left: 8px; + + &[action="true"] { + color: var(--guardian-primary-color, #4169E2); + } + } + + .sub-index-separator { + width: 100%; + height: 12px; + position: relative; + + &::after { + content: ""; + display: block; + position: absolute; + left: 15px; + right: 16px; + top: 5px; + height: 3px; + border-top: 1px solid #C4D0E1; + pointer-events: none; + } + } + } + } + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/statistics/policy-labels/policy-label-assessment-configuration/policy-label-assessment-configuration.component.ts b/frontend/src/app/modules/statistics/policy-labels/policy-label-assessment-configuration/policy-label-assessment-configuration.component.ts new file mode 100644 index 000000000..9876bbd60 --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/policy-label-assessment-configuration/policy-label-assessment-configuration.component.ts @@ -0,0 +1,481 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { GenerateUUIDv4, IStatistic, IVCDocument, Schema, UserPermissions } from '@guardian/interfaces'; +import { forkJoin, Subscription } from 'rxjs'; +import { PolicyStatisticsService } from 'src/app/services/policy-statistics.service'; +import { ProfileService } from 'src/app/services/profile.service'; +import { SchemaService } from 'src/app/services/schema.service'; +import { DialogService } from 'primeng/dynamicdialog'; +import { Formula } from 'src/app/utils'; +import { IDocument, IFormula, IOption, IScore, IVariable } from '../../../common/models/assessment'; +import { IColumn } from '../../../common/models/grid'; +import { PolicyLabelsService } from 'src/app/services/policy-labels.service'; +import { IValidateResult, IValidatorNode, IValidatorStep, LabelValidators } from 'src/app/modules/common/models/label-validator'; + +@Component({ + selector: 'app-policy-label-assessment-configuration', + templateUrl: './policy-label-assessment-configuration.component.html', + styleUrls: ['./policy-label-assessment-configuration.component.scss'], +}) +export class PolicyLabelAssessmentConfigurationComponent implements OnInit { + public readonly title: string = 'Configuration'; + + public loading: boolean = true; + public isConfirmed: boolean = false; + public user: UserPermissions = new UserPermissions(); + public owner: string; + public policy: any; + public labelId: string; + public item: any; + + public documents: any[]; + public document: any | null; + public documentsCount: number; + public pageIndex: number; + public pageSize: number; + + public validator: LabelValidators; + public tree: any; + public steps: any[]; + public current: IValidatorStep | null; + public menu: IValidatorNode[]; + public result: IValidateResult | null; + + public defaultColumns: IColumn[] = [{ + id: 'checkbox', + title: '', + type: 'text', + size: '56', + minSize: '56', + tooltip: false + }, { + id: 'id', + title: 'ID', + type: 'text', + size: 'auto', + minSize: '150', + tooltip: false + }]; + public columns: IColumn[] = [{ + id: 'tokenId', + title: 'Token ID', + type: 'text', + size: 'auto', + minSize: '150', + tooltip: false + }, { + id: 'date', + title: 'Date', + type: 'text', + size: 'auto', + minSize: '150', + tooltip: false + }, { + id: 'amount', + title: 'Amount', + type: 'text', + size: 'auto', + minSize: '150', + tooltip: false + }]; + public userColumns: any[] = []; + public schemas = new Map(); + + private subscription = new Subscription(); + + constructor( + private profileService: ProfileService, + private policyLabelsService: PolicyLabelsService, + private schemaService: SchemaService, + private dialogService: DialogService, + private router: Router, + private route: ActivatedRoute + ) { + } + + ngOnInit() { + this.documents = []; + this.pageIndex = 0; + this.pageSize = 10; + this.documentsCount = 0; + 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.labelId = this.route.snapshot.params['labelId']; + this.loading = true; + forkJoin([ + this.policyLabelsService.getLabel(this.labelId), + this.policyLabelsService.getRelationships(this.labelId), + ]).subscribe(([item, relationships]) => { + this.item = item; + this.policy = relationships?.policy || {}; + this.loadDocuments(); + }, (e) => { + this.loading = false; + }); + } + + private loadDocuments() { + this.loading = true; + this.policyLabelsService + .getDocuments(this.labelId) + .subscribe((documents) => { + const { page, count } = this.policyLabelsService.parsePage(documents); + this.documents = page; + this.documentsCount = count; + this.document = null; + this.updateDocuments(); + this.updateMetadata(); + setTimeout(() => { + this.loading = false; + }, 1000); + }, (e) => { + this.loading = false; + }); + } + + public updateDocuments() { + for (const doc of this.documents) { + doc.__id = doc.messageId; + doc.__cols = new Map(); + } + } + + private updateMetadata() { + this.validator = new LabelValidators(this.item); + this.tree = this.validator.getTree(); + this.steps = this.validator.getSteps(); + + this.addDefaultSteps(); + + this.menu = [] + for (const child of this.tree.children) { + this.createMenu(child, this.menu); + } + this.current = this.validator.start(); + } + + private addDefaultSteps() { + this.tree.children.unshift({ + name: 'Target', + item: this, + selectable: true, + children: [] + }) + this.steps.unshift({ + name: 'Target', + title: 'Target', + item: this, + type: 'target', + config: null, + auto: false, + disabled: true, + update: this.onTarget.bind(this) + }) + + this.tree.children.push({ + name: 'Result', + item: this.validator, + selectable: true, + children: [] + }) + this.steps.push({ + name: 'Result', + title: 'Result', + item: this.validator, + type: 'result', + config: this.validator, + auto: false, + update: this.onResult.bind(this) + }) + } + + private createMenu(node: any, result: any[]) { + result.push(node); + for (const child of node.children) { + this.createMenu(child, result); + } + return result; + } + + private onTarget() { + return; + } + + private onResult() { + this.result = this.validator.getResult(); + } + + public isSelected(menuItem: any): boolean { + return menuItem.item === this.current?.item; + } + + public onPrev(): void { + this.current = this.validator.prev(); + this.updateStep(); + } + + public onNext(): void { + if (this.current?.type === 'target') { + this.loading = true; + this.policyLabelsService + .getDocument(this.document.id, this.labelId) + .subscribe((documents) => { + this.validator.setData(documents?.relatedDocuments || []); + this.current = this.validator.next(); + setTimeout(() => { + this.loading = false; + }, 1000); + }, (e) => { + this.loading = false; + }); + } else { + this.current = this.validator.next(); + } + this.updateStep(); + } + + public onSubmit() { + const result = this.validator.getResult(); + debugger; + } + + public onSelectDocument(item: IDocument) { + this.document = item; + this.steps[0].disabled = !this.document; + } + + 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.loadDocuments(); + } + + public getCellValue(row: IDocument, column: IColumn): any { + if (row.__cols.has(column)) { + return row.__cols.get(column); + } else { + let value: any; + if (typeof column.id === 'string') { + value = this.getFieldValue(row, [column.id]); + } else { + value = this.getFieldValue(row, column.id); + } + if (Array.isArray(value)) { + value = `[${value.join(',')}]`; + } + row.__cols.set(column, value); + return value; + } + } + + private getFieldValue(document: any, fullPath: string[]): any { + if (!document) { + return null; + } + + let vc = document?.document?.verifiableCredential; + if (Array.isArray(vc)) { + vc = vc[vc.length - 1]; + } + let cs: any = vc?.credentialSubject; + if (Array.isArray(cs)) { + cs = cs[0]; + } + let value: any = cs; + for (let i = 0; i < fullPath.length; i++) { + if (value) { + value = value[fullPath[i]] + } else { + return undefined; + } + } + return value; + } + + public onScore() { + this.updateStep(); + } + + public updateStep() { + if (this.current?.type === 'scores') { + let valid = true; + if (Array.isArray(this.current.config)) { + for (const score of this.current.config) { + let validScore = score.value !== undefined; + valid = valid && validScore; + } + } + this.current.disabled = !valid; + } + } + + + // public onCreate() { + // const report = this.generateVcDocument(); + // this.loading = true; + // this.policyLabelsService + // .createAssessment(this.labelId, report) + // .subscribe((assessment) => { + // this.router.navigate([ + // '/policy-label', + // this.labelId, + // 'assessment', + // assessment.id + // ]); + // }, (e) => { + // this.loading = false; + // }); + // } + + // 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.loadDocuments(); + // } + + // public getCellValue(row: IDocument, column: IColumn): any { + // if (row.__cols.has(column)) { + // return row.__cols.get(column); + // } else { + // let value: any = (typeof column.id === 'string') ? + // ((row.targetDocument as any)[column.id]) : + // (this.getFieldValue(row, column.id)); + // if (Array.isArray(value)) { + // value = `[${value.join(',')}]`; + // } + // row.__cols.set(column, value); + // return value; + // } + // } + + // private getFieldValue(document: IDocument, fullPath: string[]): any { + // if (!document) { + // return null; + // } + // const schemaId = fullPath[0]; + // if (document.targetDocument.schema === schemaId) { + // return this.getFieldValueByPath(document.targetDocument, fullPath); + // } + // const result: any[] = []; + // for (const doc of document.relatedDocuments) { + // if (doc.schema === schemaId) { + // result.push(this.getFieldValueByPath(doc, fullPath)) + // } + // } + // for (const doc of document.unrelatedDocuments) { + // if (doc.schema === schemaId) { + // result.push(this.getFieldValueByPath(doc, fullPath)) + // } + // } + // if (result.length > 1) { + // return result; + // } else if (result.length === 1) { + // return result[0]; + // } else { + // return undefined; + // } + // } + + // private getFieldValueByPath(document: IVCDocument, path: string[]): any { + // if (document.schema === path[0]) { + // let value: any = document?.document?.credentialSubject; + // if (Array.isArray(value)) { + // value = value[0]; + // } + // for (let i = 1; i < path.length; i++) { + // if (value) { + // value = value[path[i]] + // } else { + // return undefined; + // } + // } + // return value; + // } else { + // return undefined; + // } + // } + + // public changeCol(col: any) { + // col.selected = !col.selected; + // this.columns = [ + // ...this.userColumns.filter((c) => c.selected) + // ]; + // } + + // private generateVcDocument() { + // if (!this.document) { + // return null; + // } + // const document: any = {}; + // const target = this.document.targetDocument.messageId; + // const relationships = new Set(); + // const report = { + // document, + // target, + // relationships: Array.from(relationships) + // }; + // return report; + // } + + // public getVariableValue(value: any): any { + // if (value === undefined) { + // return 'N/A'; + // } else { + // return value; + // } + // } + + public getVariableValue(value: any): any { + if (value === undefined) { + return 'N/A'; + } else { + return value; + } + } + + public onBack() { + this.router.navigate(['/policy-statistics']); + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/statistics/policy-labels/policy-label-assessment-view/policy-label-assessment-view.component.html b/frontend/src/app/modules/statistics/policy-labels/policy-label-assessment-view/policy-label-assessment-view.component.html new file mode 100644 index 000000000..e99386759 --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/policy-label-assessment-view/policy-label-assessment-view.component.html @@ -0,0 +1,270 @@ +
+
+
+
+ +
+ Before starting work you need to get DID + here +
+ +
+
+ +
+ +
+ {{definition?.name}} +
+ Policy Name: {{policy.name}} + Version: {{policy.version}} +
+
+
+ +
+
+
+
+
+ + +
+
Overview
+
+
+
+
+ + +
+
Document
+
+
+
+
+ + +
+
Relationships
+
+
+
+
+ +
+ +
+
+
+ + + + +
+
\ No newline at end of file diff --git a/frontend/src/app/modules/statistics/policy-labels/policy-label-assessment-view/policy-label-assessment-view.component.scss b/frontend/src/app/modules/statistics/policy-labels/policy-label-assessment-view/policy-label-assessment-view.component.scss new file mode 100644 index 000000000..16f684e4e --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/policy-label-assessment-view/policy-label-assessment-view.component.scss @@ -0,0 +1,385 @@ +.guardian-page { + position: relative; + padding: 0px; + user-select: none; + background: var(--guardian-grey-background, #F9FAFC); + + .header-container { + padding: 56px 48px 10px 48px; + background: var(--guardian-primary-color, #4169E2); + min-height: 178px; + height: 178px; + + .guardian-user-back-button { + width: 200px; + + 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); + } + + .body-container { + display: flex; + width: 100%; + height: 100%; + overflow: hidden; + position: relative; + } + + .nav-body-container { + display: flex; + width: 100%; + height: 100%; + padding: 24px 48px 24px 48px; + overflow: auto; + position: relative; + + .step-body-container { + display: flex; + width: 100%; + min-height: 100px; + height: fit-content; + border-radius: 8px; + box-shadow: 0px 4px 8px 0px var(--guardian-shadow, #00000014); + background: var(--guardian-background, #FFFFFF); + ; + padding: 24px; + flex-direction: column; + margin-bottom: 16px; + + .step-body-header { + font-size: 24px; + font-weight: 600; + line-height: 32px; + text-align: left; + color: var(--guardian-font-color, #23252E); + margin-bottom: 24px; + } + } + } + + .fields-container { + .field-container { + margin-bottom: 16px; + + .field-name { + font-family: Inter; + font-size: 12px; + font-weight: 500; + line-height: 14px; + text-align: left; + color: var(--guardian-font-color, #23252E); + padding: 8px 0px; + } + + .field-value { + padding: 12px 16px 12px 16px; + border-radius: 8px; + border: 1px solid var(--guardian-border-color, #E1E7EF); + background: var(--guardian-grey-background, #F9FAFC); + font-family: Inter; + font-size: 14px; + font-weight: 400; + line-height: 16px; + text-align: left; + color: var(--guardian-font-color, #23252E); + width: 100%; + min-height: 42px; + overflow: hidden; + text-overflow: ellipsis; + user-select: text; + } + + .field-value-array { + .field-value { + margin-bottom: 16px; + + &:last-child { + margin-bottom: 0px; + } + } + } + } + } + + .scores-container { + .score-container { + border: 1px solid var(--guardian-grey-color-3, #AAB7C4); + padding: 24px; + border-radius: 8px; + margin-bottom: 24px; + + .score-name { + font-family: Inter; + font-size: 14px; + font-weight: 700; + line-height: 18px; + text-align: left; + color: var(--guardian-font-color, #23252E); + margin-bottom: 16px; + padding: 8px 0px; + } + } + } + + .options-container { + .option-container { + display: flex; + flex-direction: row; + + border-radius: 6px; + + &:not([disabled]) { + cursor: pointer; + } + + &:not([disabled]):hover { + background: var(--guardian-hover, #F0F3FC); + } + + .option-checkbox { + cursor: pointer; + min-height: 40px; + width: 42px; + min-width: 42px; + display: flex; + justify-content: flex-start; + align-items: center; + padding-left: 4px; + } + + .option-name { + cursor: pointer; + min-height: 40px; + width: 100%; + display: flex; + justify-content: flex-start; + align-items: center; + } + } + } +} + +.tree-container { + position: absolute; + left: 0px; + top: 0px; + bottom: 0px; + right: 0px; + z-index: 1; + user-select: none; + display: flex; + + .tree-node { + background: var(--guardian-background, #FFFFFF); + border: 1px solid #bac0ce; + box-shadow: 0px 4px 4px 0px var(--guardian-shadow, #00000014); + border-radius: 6px; + width: 150px; + cursor: pointer; + overflow: hidden; + user-select: none; + + &.root-node { + .node-header { + background: #CAFDD9; + } + } + + &:hover { + border: 1px solid var(--guardian-primary-color, #4169E2); + } + + * { + pointer-events: none; + } + + &.selected-type-selected { + border: 1px solid var(--guardian-primary-color, #4169E2); + box-shadow: 0px 0px 0px 3px var(--guardian-primary-color, #4169E2), 0px 6px 6px 0px #00000021; + } + + &.selected-type-sub { + border: 1px solid var(--guardian-primary-color, #4169E2); + } + + &.selected-type-hidden { + opacity: 0.4; + } + + &[search-highlighted="true"] { + border: 1px solid var(--guardian-success-color, #19BE47); + box-shadow: 0px 0px 0px 3px var(--guardian-success-color, #19BE47), 0px 6px 6px 0px #00000021; + } + + &[search-highlighted="true"].selected-type-selected { + border: 1px solid var(--guardian-primary-color, #4169E2); + box-shadow: 0px 0px 0px 3px var(--guardian-primary-color, #4169E2), 0px 6px 6px 0px #00000021; + } + + .node-header { + width: 100%; + padding: 9px 8px 9px 16px; + background: var(--guardian-grey-background, #F9FAFC); + font-family: Inter; + font-size: 12px; + font-weight: 600; + line-height: 14px; + text-align: left; + color: var(--guardian-font-color, #23252E); + } + } +} + +.zoom-toolbar { + width: 48px; + height: 160px; + position: absolute; + right: 400px; + top: 0px; + z-index: 3; + padding: 16px 16px 4px 4px; + overflow: hidden; + + -webkit-transition: right 0.2s ease-in-out; + -moz-transition: right 0.2s ease-in-out; + -o-transition: right 0.2s ease-in-out; + transition: right 0.2s ease-in-out; + + &[hidden-schema="true"] { + right: 0px; + } + + .zoom-button { + width: 28px; + height: 28px; + margin-bottom: 8px; + border-radius: 8px; + background: var(--guardian-background, #FFFFFF); + box-shadow: 0px 0px 1px 3px var(--guardian-grey-color, #EFF3F7); + + .zoom-label { + width: 28px; + height: 28px; + border-radius: 8px; + font-family: Inter; + font-size: 8px; + font-weight: 700; + color: var(--guardian-disabled-color, #848FA9); + border: 1px solid var(--guardian-border-color, #E1E7EF); + display: flex; + justify-content: center; + align-items: center; + } + + button { + width: 28px; + height: 28px; + border: 1px solid var(--guardian-primary-color, #4169E2); + border-radius: 8px; + } + } +} + +.schema-fields { + position: absolute; + top: 0px; + bottom: 0px; + right: 0px; + width: 400px; + z-index: 3; + background: var(--guardian-background, #FFFFFF); + border-left: 1px solid var(--guardian-border-color, #E1E7EF); + box-shadow: 0px 0px 4px 0px var(--guardian-shadow, #00000014); + overflow: hidden; + -webkit-transition: width 0.2s ease-in-out; + -moz-transition: width 0.2s ease-in-out; + -o-transition: width 0.2s ease-in-out; + transition: width 0.2s ease-in-out; + transform: translateZ(0); + will-change: transform, width; + + &[hidden-schema="true"] { + width: 0px; + } + + .schema-close { + width: 32px; + height: 32px; + min-width: 32px; + max-width: 32px; + position: absolute; + padding: 4px; + border-radius: 6px; + cursor: pointer; + top: 17px; + right: 16px; + + &:hover { + background: var(--guardian-primary-background); + } + } + + .schema-fields-container { + width: 400px; + height: 100%; + display: flex; + flex-direction: column; + + .schema-name { + width: 400px; + padding: 24px 24px 24px 24px; + font-size: 16px; + font-weight: 600; + line-height: 18px; + text-align: left; + color: var(--guardian-font-color, #23252E); + position: relative; + } + + .schema-config { + width: 100%; + height: 100%; + padding: 0px 24px 24px 24px; + } + + .guardian-button { + height: 32px; + width: 155px; + margin-top: 10px; + } + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/statistics/policy-labels/policy-label-assessment-view/policy-label-assessment-view.component.ts b/frontend/src/app/modules/statistics/policy-labels/policy-label-assessment-view/policy-label-assessment-view.component.ts new file mode 100644 index 000000000..cdf228495 --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/policy-label-assessment-view/policy-label-assessment-view.component.ts @@ -0,0 +1,327 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { GenerateUUIDv4, Schema, UserPermissions } from '@guardian/interfaces'; +import { forkJoin, Subscription } from 'rxjs'; +import { PolicyStatisticsService } from 'src/app/services/policy-statistics.service'; +import { ProfileService } from 'src/app/services/profile.service'; +import { DialogService } from 'primeng/dynamicdialog'; +import { IFormula, IOption, IScore, IVariable } from '../../../common/models/assessment'; +import { TreeSource } from '../../../common/tree-graph/tree-source'; +import { TreeGraphComponent } from '../../../common/tree-graph/tree-graph.component'; +import { DocumentNode, SchemaData } from '../../../common/models/schema-node'; +import { TreeNode } from '../../../common/tree-graph/tree-node'; +import { VCViewerDialog } from '../../../schema-engine/vc-dialog/vc-dialog.component'; + +@Component({ + selector: 'app-policy-label-assessment-view', + templateUrl: './policy-label-assessment-view.component.html', + styleUrls: ['./policy-label-assessment-view.component.scss'], +}) +export class PolicyLabelAssessmentViewComponent implements OnInit { + public readonly title: string = 'Assessment'; + + public loading: boolean = true; + public navLoading: boolean = false; + public nodeLoading: boolean = true; + public isConfirmed: boolean = false; + public user: UserPermissions = new UserPermissions(); + public owner: string; + public definitionId: string; + public assessmentId: string; + public definition: any; + public policy: any; + public schemas: any[]; + public schema: any; + public assessment: any; + public target: any; + public relationships: any; + public schemasMap: Map; + public stepper = [true, false, false]; + + public preview: IVariable[]; + public scores: IScore[]; + public formulas: IFormula[]; + + public tree: TreeGraphComponent; + public nodes: DocumentNode[]; + public source: TreeSource; + public selectedNode: DocumentNode; + + private subscription = new Subscription(); + + public get zoom(): number { + if (this.tree) { + return Math.round(this.tree.zoom * 100); + } else { + return 100; + } + } + + constructor( + private profileService: ProfileService, + private policyStatisticsService: PolicyStatisticsService, + 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.definitionId = this.route.snapshot.params['definitionId']; + this.assessmentId = this.route.snapshot.params['assessmentId']; + this.loading = true; + forkJoin([ + this.policyStatisticsService.getDefinition(this.definitionId), + this.policyStatisticsService.getRelationships(this.definitionId), + this.policyStatisticsService.getAssessment(this.definitionId, this.assessmentId), + this.policyStatisticsService.getAssessmentRelationships(this.definitionId, this.assessmentId), + ]).subscribe(([ + definition, + definitionRelationships, + assessment, + assessmentRelationships + ]) => { + this.definition = definition; + this.policy = definitionRelationships?.policy || {}; + this.schemas = definitionRelationships?.schemas || []; + this.schema = definitionRelationships?.schema; + this.assessment = assessment || {}; + this.target = assessmentRelationships?.target || {}; + this.relationships = assessmentRelationships?.relationships || []; + this.updateMetadata(); + + setTimeout(() => { + this.loading = false; + }, 500); + }, (e) => { + this.loading = false; + }); + } + + public onBack() { + this.router.navigate([ + '/policy-statistics', + this.definitionId, + 'assessments' + ]); + } + + private updateMetadata() { + const config = this.definition.config || {}; + const variables = config.variables || []; + const formulas = config.formulas || []; + const scores = config.scores || []; + const preview = new Map(); + + this.preview = []; + this.scores = []; + this.formulas = []; + + let document: any = this.assessment?.document?.credentialSubject; + if (Array(document)) { + document = document[0]; + } + if (!document) { + document = {}; + } + + for (const variable of variables) { + const path = [...(variable.path || '').split('.')]; + const fullPath = [variable.schemaId, ...path]; + const field: IVariable = { + id: variable.id, + description: variable.fieldDescription || '', + schemaId: variable.schemaId, + path: path, + fullPath: fullPath, + value: document[variable.id], + isArray: false + } + this.preview.push(field); + preview.set(variable.id, field); + } + + for (const score of scores) { + const relationships: IVariable[] = []; + if (score.relationships) { + for (const ref of score.relationships) { + const field = preview.get(ref); + if (field) { + relationships.push(field); + } + } + } + const options: IOption[] = []; + if (score.options) { + for (const option of score.options) { + options.push({ + id: GenerateUUIDv4(), + description: option.description, + value: option.description //this is not a typo. + }); + } + } + this.scores.push({ + id: score.id, + description: score.description, + value: document[score.id], + relationships, + options + }); + } + + for (const formula of formulas) { + this.formulas.push({ + id: formula.id, + description: formula.description, + value: document[formula.id], + formula: formula.formula, + type: formula.type + }); + } + + // + this.schemasMap = new Map(); + for (const schema of this.schemas) { + try { + const item = new Schema(schema); + this.schemasMap.set(item.iri || item.id, item) + } catch (error) { + console.log(error); + } + } + + // + let root: DocumentNode | null = null; + let target: DocumentNode | null = null; + this.nodes = []; + + if (this.assessment) { + this.assessment.schemaName = 'Assessment'; + root = DocumentNode.from(this.assessment, 'root'); + this.nodes.push(root); + } + if (root && this.target) { + this.target.schemaName = this.schemasMap.get(this.target.schema)?.name || this.target.schema; + target = DocumentNode.from(this.target, 'sub'); + this.nodes.push(target); + root.addId(target.id); + } + if (target && this.relationships) { + for (const item of this.relationships) { + item.schemaName = this.schemasMap.get(item.schema)?.name || item.schema; + const node = DocumentNode.from(item, 'sub'); + if (node.id !== target.id) { + this.nodes.push(node); + target.addId(node.id); + } + } + } + this.source = new TreeSource(this.nodes); + if (this.tree) { + this.tree.setData(this.source); + this.tree.move(18, 46); + } + } + + public onStep(index: number) { + this.navLoading = true; + for (let i = 0; i < this.stepper.length; i++) { + this.stepper[i] = false; + } + this.stepper[index] = true; + setTimeout(() => { + this.tree?.move(18, 46); + setTimeout(() => { + this.navLoading = false; + }, 200); + }, 200); + } + + public initTree($event: TreeGraphComponent) { + this.tree = $event; + if (this.nodes) { + this.tree.setData(this.source); + this.tree.move(18, 46); + } + } + + public createNodes($event: any) { + this.tree.move(18, 46); + } + + public onSelectNode(node: TreeNode | null) { + this.nodeLoading = true; + this.selectedNode = node as DocumentNode; + setTimeout(() => { + this.nodeLoading = false; + }, 350); + } + + public onZoom(d: number) { + if (this.tree) { + this.tree.onZoom(d); + if (d === 0) { + this.tree.move(18, 46); + } + } + } + + public onClearNode() { + this.tree?.onSelectNode(null); + } + + public openVCDocument(document: any) { + const dialogRef = this.dialogService.open(VCViewerDialog, { + showHeader: false, + width: '1000px', + styleClass: 'guardian-dialog', + data: { + id: document.id, + row: document, + dryRun: false, + document: document.document, + title: document.schemaName || 'VC Document', + type: 'VC', + viewDocument: true, + schema: this.schema + } + }); + dialogRef.onClose.subscribe(async (result) => {}); + } +} diff --git a/frontend/src/app/modules/statistics/policy-labels/policy-label-assessments/policy-label-assessments.component.html b/frontend/src/app/modules/statistics/policy-labels/policy-label-assessments/policy-label-assessments.component.html new file mode 100644 index 000000000..7f9b3432f --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/policy-label-assessments/policy-label-assessments.component.html @@ -0,0 +1,99 @@ +
+
+
+
+ +
+ Before starting work you need to get DID + here +
+ +
+ +
+ +
+ {{title}} +
+ Policy Name: {{policy.name}} + Version: {{policy.version}} +
+
+ +
+ +
+ + + + + {{column.title}} + + + + + + + + + + + + {{row[column.id]}} + + + + + + +
+ +
+
+
+ +
+ + There were no assessment created yet + Please create new assessment to see the data +
+
+
+
\ No newline at end of file diff --git a/frontend/src/app/modules/statistics/policy-labels/policy-label-assessments/policy-label-assessments.component.scss b/frontend/src/app/modules/statistics/policy-labels/policy-label-assessments/policy-label-assessments.component.scss new file mode 100644 index 000000000..e99f4ea49 --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/policy-label-assessments/policy-label-assessments.component.scss @@ -0,0 +1,23 @@ +.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; + } +} + +.grid-btn { + width: 80px; + height: 30px; + margin-right: 16px; + + &:last-child { + margin-right: 0px; + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/statistics/policy-labels/policy-label-assessments/policy-label-assessments.component.ts b/frontend/src/app/modules/statistics/policy-labels/policy-label-assessments/policy-label-assessments.component.ts new file mode 100644 index 000000000..7ba6999c6 --- /dev/null +++ b/frontend/src/app/modules/statistics/policy-labels/policy-label-assessments/policy-label-assessments.component.ts @@ -0,0 +1,183 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { UserPermissions } from '@guardian/interfaces'; +import { forkJoin, Subscription } from 'rxjs'; +import { PolicyEngineService } from 'src/app/services/policy-engine.service'; +import { PolicyStatisticsService } from 'src/app/services/policy-statistics.service'; +import { ProfileService } from 'src/app/services/profile.service'; +import { DialogService } from 'primeng/dynamicdialog'; + +interface IColumn { + id: string; + title: string; + type: string; + size: string; + tooltip: boolean; + permissions?: (user: UserPermissions) => boolean; + canDisplay?: () => boolean; +} + +@Component({ + selector: 'app-policy-label-assessments', + templateUrl: './policy-label-assessments.component.html', + styleUrls: ['./policy-label-assessments.component.scss'], +}) +export class PolicyLabelAssessmentsComponent implements OnInit { + public readonly title: string = 'Assessments'; + + 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 definitionId: string; + public definition: any; + public columns: IColumn[]; + public policy: any; + public schemas: any[]; + + private subscription = new Subscription(); + + constructor( + private profileService: ProfileService, + private policyStatisticsService: PolicyStatisticsService, + private policyEngineService: PolicyEngineService, + private dialogService: DialogService, + private router: Router, + private route: ActivatedRoute + ) { + this.columns = [ { + id: 'definition', + title: 'Definition', + type: 'text', + size: 'auto', + tooltip: false + }, { + id: 'policy', + title: 'Policy', + type: 'text', + size: 'auto', + tooltip: false + }, { + id: 'topicId', + title: 'Topic', + type: 'text', + size: '150', + tooltip: false + }, { + id: 'target', + title: 'Target', + type: 'text', + size: '220', + tooltip: false + }, { + id: 'messageId', + title: 'Message ID', + type: 'text', + size: '220', + tooltip: false + }, { + id: 'options', + title: '', + type: 'text', + size: '135', + tooltip: false + }] + } + + 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.definitionId = this.route.snapshot.params['definitionId']; + this.isConfirmed = false; + this.loading = true; + forkJoin([ + this.profileService.getProfile(), + this.policyStatisticsService.getDefinition(this.definitionId), + this.policyStatisticsService.getRelationships(this.definitionId) + ]).subscribe(([profile, definition, relationships]) => { + this.isConfirmed = !!(profile && profile.confirmed); + this.user = new UserPermissions(profile); + this.owner = this.user.did; + this.definition = definition; + this.policy = relationships?.policy || {}; + this.schemas = relationships?.schemas || []; + if (this.isConfirmed) { + this.loadData(); + } else { + setTimeout(() => { + this.loading = false; + }, 500); + } + }, (e) => { + this.loading = false; + }); + } + + private loadData() { + const filters: any = {}; + this.loading = true; + this.policyStatisticsService + .getAssessments( + this.definitionId, + this.pageIndex, + this.pageSize, + filters + ) + .subscribe((response) => { + const { page, count } = this.policyStatisticsService.parsePage(response); + this.page = page; + this.pageCount = count; + for (const item of this.page) { + item.definition = this.definition?.name; + item.policy = this.policy?.name; + } + setTimeout(() => { + this.loading = false; + }, 500); + }, (e) => { + this.loading = false; + }); + } + + 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 onBack() { + this.router.navigate(['/policy-statistics']); + } + + public onOpen(row: any) { + this.router.navigate([ + '/policy-statistics', + this.definitionId, + 'assessment', + row.id + ]); + } +} diff --git a/frontend/src/app/modules/statistics/policy-labels/policy-labels/policy-labels.component.html b/frontend/src/app/modules/statistics/policy-labels/policy-labels/policy-labels.component.html index b02a8902a..14d02fe45 100644 --- a/frontend/src/app/modules/statistics/policy-labels/policy-labels/policy-labels.component.html +++ b/frontend/src/app/modules/statistics/policy-labels/policy-labels/policy-labels.component.html @@ -145,6 +145,20 @@ + + + +
{ return this.http.post(`${this.url}/components`, options); } + + public getDocuments( + labelId: string, + pageIndex?: number, + pageSize?: number, + ): Observable> { + const params = PolicyLabelsService.getOptions({}, pageIndex, pageSize); + return this.http.get(`${this.url}/${labelId}/documents`, { observe: 'response', params }); + } + + public getDocument( + documentId: string, + labelId: string, + ): Observable { + return this.http.get(`${this.url}/${labelId}/documents/${documentId}`); + } + + public createAssessment(labelId: string, item: any): Observable { + return this.http.post(`${this.url}/${labelId}/assessment`, item); + } } diff --git a/guardian-service/src/api/helpers/policy-labels-helpers.ts b/guardian-service/src/api/helpers/policy-labels-helpers.ts index 9be84cafe..f9b613ae1 100644 --- a/guardian-service/src/api/helpers/policy-labels-helpers.ts +++ b/guardian-service/src/api/helpers/policy-labels-helpers.ts @@ -1,4 +1,4 @@ -import { DatabaseServer, PolicyLabel, SchemaConverterUtils, TopicConfig, TopicHelper, Users } from '@guardian/common'; +import { DatabaseServer, PolicyLabel, SchemaConverterUtils, TopicConfig, TopicHelper, Users, VcDocument, VpDocument } from '@guardian/common'; import { GenerateUUIDv4, IOwner, IPolicyLabelConfig, PolicyType, SchemaCategory, SchemaHelper, SchemaStatus, TopicType } from '@guardian/interfaces'; import { generateSchemaContext } from './schema-publish-helper.js'; @@ -97,4 +97,43 @@ export async function generateSchema(config: PolicyLabel, owner: IOwner) { SchemaHelper.setVersion(schemaObject, '1.0.0', null); SchemaHelper.updateIRI(schemaObject); return schemaObject; +} + +export async function findRelationships(target: VcDocument | VpDocument): Promise { + if (!target) { + return []; + } + + const messageIds = new Set(); + messageIds.add(target.messageId); + + const result: VcDocument[] = []; + if (Array.isArray(target.relationships)) { + for (const relationship of target.relationships) { + await findRelationshipsById(relationship, messageIds, result); + } + } + + return result; +} + +export async function findRelationshipsById( + messageId: string | undefined, + map: Set, + result: VcDocument[] +): Promise { + if (!messageId || map.has(messageId)) { + return result; + } + map.add(messageId); + const doc = await DatabaseServer.getStatisticDocument({ messageId }); + if (doc) { + result.push(doc); + if (Array.isArray(doc.relationships)) { + for (const relationship of doc.relationships) { + await findRelationshipsById(relationship, map, result); + } + } + } + return result; } \ No newline at end of file diff --git a/guardian-service/src/api/policy-labels.service.ts b/guardian-service/src/api/policy-labels.service.ts index c20f3d9d7..1b3e89eb8 100644 --- a/guardian-service/src/api/policy-labels.service.ts +++ b/guardian-service/src/api/policy-labels.service.ts @@ -1,7 +1,7 @@ import { ApiResponse } from './helpers/api-response.js'; import { BinaryMessageResponse, DatabaseServer, LabelMessage, MessageAction, MessageError, MessageResponse, MessageServer, PinoLogger, PolicyImportExport, PolicyLabel, PolicyLabelImportExport, Users } from '@guardian/common'; import { EntityStatus, IOwner, MessageAPI, PolicyType, SchemaStatus } from '@guardian/interfaces'; -import { generateSchema, getOrCreateTopic, publishLabelConfig } from './helpers/policy-labels-helpers.js'; +import { findRelationships, generateSchema, getOrCreateTopic, publishLabelConfig } from './helpers/policy-labels-helpers.js'; import { publishSchema } from './helpers/index.js'; /** @@ -410,11 +410,13 @@ export async function policyLabelsAPI(logger: PinoLogger): Promise { try { const { options } = msg; - const filter: any = { $and: [ - { - status: EntityStatus.PUBLISHED - } - ] }; + const filter: any = { + $and: [ + { + status: EntityStatus.PUBLISHED + } + ] + }; if (options.text) { const keywords = options.text.split(' '); for (const keyword of keywords) { @@ -450,4 +452,113 @@ export async function policyLabelsAPI(logger: PinoLogger): Promise { return new MessageError(error); } }); + + + /** + * Get documents + * + * @param {any} msg - filters + * + * @returns {any} - documents + */ + ApiResponse(MessageAPI.GET_POLICY_LABEL_DOCUMENTS, + async (msg: { + labelId: string, + owner: IOwner, + pageIndex?: string, + pageSize?: string + }) => { + try { + + if (!msg) { + return new MessageError('Invalid parameters.'); + } + const { labelId, owner, pageIndex, pageSize } = msg; + + const otherOptions: any = {}; + 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; + } else { + otherOptions.orderBy = { createDate: 'DESC' }; + otherOptions.limit = 100; + otherOptions.offset = 0; + } + + const item = await DatabaseServer.getPolicyLabelById(labelId); + if (!(item && (item.creator === owner.creator || item.status === EntityStatus.PUBLISHED))) { + return new MessageError('Item does not exist.'); + } + + const policyId: string = item.policyId; + + const vps = await DatabaseServer.getVPs({ + type: "mint", + policyId, + owner: owner.creator, + }, otherOptions); + + return new MessageResponse({ + items: vps, + count: vps.length + }); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + + /** + * Get documents + * + * @param {any} msg - filters + * + * @returns {any} - documents + */ + ApiResponse(MessageAPI.GET_POLICY_LABEL_DOCUMENT, + async (msg: { + documentId: string, + labelId: string, + owner: IOwner + }) => { + try { + if (!msg) { + return new MessageError('Invalid parameters.'); + } + const { documentId, labelId, owner } = msg; + + const item = await DatabaseServer.getPolicyLabelById(labelId); + if (!(item && (item.creator === owner.creator || item.status === EntityStatus.PUBLISHED))) { + return new MessageError('Item does not exist.'); + } + + const policyId: string = item.policyId; + + const vp = await DatabaseServer.getVP({ + id: documentId, + type: "mint", + policyId, + owner: owner.creator, + }); + + if (!vp) { + return new MessageError('Item does not exist.'); + } + + const relationships = await findRelationships(vp); + + return new MessageResponse({ + targetDocument: vp, + relatedDocuments: relationships, + unrelatedDocuments: [] + }); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); } \ No newline at end of file diff --git a/interfaces/src/type/messages/message-api.type.ts b/interfaces/src/type/messages/message-api.type.ts index c167ed4a0..856bce4e5 100644 --- a/interfaces/src/type/messages/message-api.type.ts +++ b/interfaces/src/type/messages/message-api.type.ts @@ -255,6 +255,8 @@ export enum MessageAPI { IMPORT_POLICY_LABEL_FILE = 'IMPORT_POLICY_LABEL_FILE', PREVIEW_POLICY_LABEL_FILE = 'PREVIEW_POLICY_LABEL_FILE', SEARCH_POLICY_LABEL_COMPONENTS = 'SEARCH_POLICY_LABEL_COMPONENTS', + GET_POLICY_LABEL_DOCUMENTS = 'GET_POLICY_LABEL_DOCUMENTS', + GET_POLICY_LABEL_DOCUMENT = 'GET_POLICY_LABEL_DOCUMENT', } /**